Python数据结构与算法分析(第二版)学习笔记——基本数据结构

34 阅读6分钟

线性数据结构包括栈、列队、双端列队和列表,其元素顺序取决于添加和删除顺序。

栈分顶端和底端,添加和移除操作总是发生在顶端,后进先出(LIFO)。栈的操作就好像拿走一个顶端的托盘。

image.png 用Python实现栈,新元素会添加到栈道尾部。

class stack:
    def __init__(self):
        self.item = []

    def isEmpty(self):
        return self.item == []

    def push(self,item):
        self.item.append(item)

    def pop(self):
       return self.item.pop()

    def peek(self):
        return self.item[-1]

    def size(self):
        return len(self.item)

我们也可以选择列表的头部,用pop和insert访问下标为0的元素,不过他们的时间复杂度却不相同,在前者中,pop和append的时间复杂度是O(1),后者的pop和insert时间复杂度是O(n)。

匹配括号,匹配括号是左边的每个括号都有右边的括号对应,并且括号都有对应的匹配关系。

image.png 在书本给出的算法中,利用while循环和索引遍历括号串,遇到“(”就添加到栈,否则就从栈中删除以后“(”,如果在遍历括号串后,栈为空则括号匹配成功。需要注意的是,如果栈为空再试图去除一个元素会导致程序报错。书本用 balanced 判断栈是否为空,如果栈为空,那再加入一个“)”,必然会导致括号匹配失败, 返回balanced=false避免直接去除元素而导致错误。

def parChecker(symbolstring):
    s = stack()
    index = 0
    balanced = True

    while index < len(symbolstring) and balanced:
        symbol = symbolstring[index]
        if symbol == "(":
            s.push(symbol)
        else:
            if s.isEmpty():
                balanced = False
            else:
                s.pop()

        index += 1

        if balanced and s.isEmpty():
            return True
        else:
            return False

下面是我的解决方法,相比于书中的方法,如果出现多余的“)”,代表匹配已经失败,因此直接返回False。

def parChecker1(symbolstring):

    s = stack()
    for i in symbolstring:
        if i == "(":
            s.push(i)
        else:
            if s.isEmpty():
                return False
            else:
                s.pop()

    if s.isEmpty():
        return True
    else:

       return False

在前面的括号匹配基础上,我们可以实现更复杂的匹配符号,我们只要先判断符号串中的符号是否属于({[如果是就添加到栈中,如果符号属于}]),那就判断栈最外层的元素是否与其对应,如果是就去掉元素,否则匹配失败。

def parChecker3(symbolstring):
    s = Stack()
    index = 0
    balanced = True

    while index < len(symbolstring) and balanced:
        symbol = symbolstring[index]

        if symbol in "([{":
            s.push(symbol)
        else:
            if s.isEmpty():
                balanced = False
            else:
                if symbol == ")" and s.peek() == "(":
                    s.pop()
                elif symbol == "}" and s.peek() == "{":
                    s.pop()
                elif symbol == "]" and s.peek() == "[":
                    s.pop()

                else:
                    balanced = False

        index += 1
    if balanced and s.isEmpty():
        return True
    else:
        return False

十进制转化

image.png 十进制数转化为二进制,在十进制向二进制转化的过程中,得数不为0就把得数作为下一轮计算的被除数,余数存入栈中组成二进制数。

def dividedby3(number):
    s = Stack()

    while number > 0 :
        ys = number % 2
        s.push(ys)
        number = number // 2

    num = ""
    while not s.isEmpty():
        num += str(s.pop())
    return num

同时,还可以把2用基数n代替,用来表示其他进制的转换。

def dividedbyn(number,n):
    s = Stack()

    while number > 0 :
        ys = number % n
        s.push(ys)
        number = number // n

    num = ""
    while not s.isEmpty():
        num += str(s.pop())
    return num

如果要向16进制转化,由于只有10个阿拉伯数字,我们用前6个英文字母代替数字。同时,我们创建一个数字字符串来代表16位数字。

def dividedbyn(number,n):
    digits = "0123456789ABCDEF"
    s = Stack()

    while number > 0 :
        ys = number % n
        s.push(ys)
        number = number // n

    num = ""
    while not s.isEmpty():
        num += digits[s.pop()]
    return num

前序、中序、后续表达式

中序表达式,运算符合出现在操作数的中间,即我们常见的数学表达式。而通过对运算符的移动,可以得到前序表达式和后续表达式。

image.png

相比于中序表达式,前/后序表达式的运算顺序完全由符号位置决定。

image.png

中序表达式向后序表达式转换,先把中序表达式看成完全括号表达式,遇上左括号就去掉,运算符移到右括号的位置。

image.png

中序表达式向前序表达式转换,先把中序表达式看成完全括号表达式,遇上右括号就去掉,运算符移到左括号的位置。

向后续表达式转化:创建一个保存运算符的空栈和表达结果的空列表,用split将输入的字符串转化为列表,从左往右扫描列表。 如果是字母就输入结果列表,如果是左括号就压入栈,遇上右括号就回到栈删除左括号并向结果列表输出运算符,如果是运算符就输入到栈中,但此前要向结果列表输出同级及更高级的运算符。结束后输出栈中剩余的符号。

我们通过字典prec储存符号的大小,当遇上加减时,要先输出已经存储的乘除运算符,保障乘除在加减前执行,而左括号是一个特殊情况,只有遇上右括号才对其进行处理,因此要对左括号给予一个最小的权重,保证四则运算符号的处理过程不会影响括号。

image.png

from pythonds.basic import Stack
import string

def infixtopostfix(x):
    s = Stack()
    prec = {}
    prec["*"] = 3
    prec["/"] = 3
    prec["+"] = 2
    prec["-"] = 2
    prec["("]= 1
    finlist = []
    tokenlist = x.split()
    for token in tokenlist:
        if token in string.ascii_uppercase:
            finlist.append(token)
        elif token == "(":
            s.push(token)
        elif token == ")":
            las = s.pop()
            while las != "(":
                finlist.append(las)
                las = s.pop()
        else:
            while not s.isEmpty() and prec[token]<=prec[s.peek()]:
                finlist.append(s.pop())
            s.push(token)
    while not s.isEmpty():
        finlist.append(s.pop())
    return  "".join(finlist)

print(infixtopostfix("A *  B + C "))

我们输入一个后序表达式,对其进行运算,根据后序表达式的运算规则,要对运算符号前的两位数字进行处理,而运算符号的顺序决定了计算的顺序。

我们创建一个自定义函数来定义四则运算的过程。

image.png

def postfixEval(formal):
    s = Stack()
    tokenlist = formal.split()

    for i in tokenlist:
        if i in "0123456789":
            s.push(int(i))
        else:
            num2 = s.pop()
            num1 = s.pop()
            newnum = domath(i,num1,num2)
            s.push(newnum)

    return s.pop()
def domath(way,num1,num2):
    if way == "+":
        return num1 + num2
    elif way == "-":
        return num1 - num2
    elif way == "*":
        return num1*num2
    elif way == "/":
        return num1 / num2

print(postfixEval("1 1 +"))

列队

列队是一个有序集合,先进先出(IFO)发生在尾部,删除操作发生在头部,如排队。 列队的python实现:

class Queue():

    def __init__(self):
        self.items= []
    def isEmpty(self):
        return self.items == []
    def enqueue(self,item):
        self.items.insert(0,item)
    def dequeue(self,item):
        return self.items.pop()
    def size(self):
        return len(self.items)

传土豆是一个典型的列队游戏,规则同击鼓传花。我们有列队模拟一个环,握着土豆的孩子位于列队的头部,当土豆被传给下一个孩子,他就被移动到列队尾部,并等待下一次拿到土豆,当出入列的过程重复num次,拿到土豆的孩子就出局。

from pythonds.basic import Queue
def hotpotato(numberlist,num):
    q = Queue()
    for name in numberlist:
        q.enqueue(name)

    while q.size() > 1:
        for i in range(num):
            q.enqueue(q.dequeue())
        q.dequeue()

    return q.dequeue()

模拟打印,计算等待打印的平均时间。在这个任务中,需要有一个打印机类和一个任务类,在程序开始执行时,检验任务是否存在,如果任务存在,先把程序放入任务列队,同时记录任务出现的时间;然后,观察打印机是否存在任务,如果是,且任务列队存在任务,就取出任务,添加到打印机中,同时记录时间,进入打印机的时间减去任务出现的时间就是等待时间;还要记录打印该任务所需的时间,为开始下一个任务做准备。最后,用一个倒数计时模拟打印过程。

我们在程序中输入运行的时间和打印机每分钟打印的效率。 打印机类包含的属性有打印效率、当前任务、当前任务时间。方法有:模拟打印、是否工作、执行当前任务的耗时。

任务类属性包括,任务出现的时间、任务的打印页数,方法有返回任务等待时间、返回页数(让打印机计算任务用时)、返回任务页数

最后我们要用一个列表储存每次任务的等待时间、以便计算总体的平均等待时间。

from pythonds.basic import Queue
import random
class Printer:
    def __init__(self,ppm):
        self.pagerate = ppm
        self.currenttas = None
        self.timeremaining = 0

    def busy(self):
        if self.currenttas != None:
            return True
        else:
            return False

    def startnext(self,newtask):
        self.currenttas = newtask
        self.timeremaining  = newtask.getpage()*60/self.pagerate

    def tick(self):
        if self.currenttas != None:
            self.timeremaining -= 1
            if self.timeremaining <= 0:
                self.currenttas = None

class Task:
    def __init__(self,time):
        self.starttime = time
        self.pages = random.randrange(1,21)
    def getstamp(self):
        return self.starttime
    def getpage(self):
        return self.pages
    def waittime(self,currenttime):
        return currenttime - self.starttime

def simulation(numseconds,pagesperminute):
    labprinter = Printer(pagesperminute)
    taskqueue = Queue()
    tasklist = []

    for currentsecond in range(numseconds):
        if newprinter():
            task = Task(currentsecond)
            taskqueue.enqueue(task)

        if (not taskqueue.isEmpty()) and not labprinter.busy():
            nexttask = taskqueue.dequeue()

            tasklist.append(nexttask.waittime(currentsecond))
            labprinter.startnext(nexttask)
        labprinter.tick()
    ave = sum(tasklist)/len(tasklist)
    print("平均时间%6.2f,执行任务%2d"%(ave,taskqueue.size()))

def newprinter():
    num = random.randrange(1,181)
    if num == 180:
        return True
    else:
        return False

双端列队

双端列队是与列队相似的有序集合,有一前一后两端,对在哪端进出没有限制而是根据需求进行选择。

image.png

用python实现双端列队:

class Deque:
    def __init__(self):
        self.items = []
    
    def isEmpty(self):
        return self.items == []
    
    def addfront(self,item):
        self.items.append(item)
    def addrear(self,item):
        self.items.insert(0,item)
        
    def removefront(self):
        self.items.pop()
    def removerear(self):
        self.items.pop(0)
        
    def size(self):
        return len(self.items)

回文检测器,将字符串传入双端列表,并利用双端列表的性质从两段同时取字符,如果字符相等则说明这是一个回文字符串,否则就返回一个错误,表示这不是回文。需要注意的是,最后剩下一个字符和无字符都说明这是回文。

from pythonds.basic import Deque
def palchecker(astring):
    chardeque = Deque()
    for i in astring:
        chardeque.addfront(i)

    hw = True
    while hw and chardeque.size() > 1:
        
        f = chardeque.removefront()
        r = chardeque.removerear()
        if f != r:
            return False
    return hw

列表

列表是一个无序元素的集合,要实现无序列表可以利用链表。链表必须指明第一个元素的位置,然后再指向第二个、第三个。

节点(node)是链表的基本数据结构,节点必须包含列表元素即节点的数据对象。节点包含创建其自身和指向下一个元素的方法,同时,我们既要能取得其自身,也要能够取得下一个元素。

class Node:
    def __init__(self,initdata):
        self.data = initdata
        self.next = None
getnext
    def getdata(self):
        return self.data
    def getnext(self):
        return self.next
    def setdata(self,newdata):
        self.data = newdata
    def setNext(self,newnext):
        self.next =newnext

列表的python实现:



class unorderlist:
    def __init__(self):
        self.head = None

    def isEmpty(self):
        return self.head == None

    def add(self,item):
        temp = Node(item)
        temp.setNext(self.head)
        self.head = temp
    def length(self):
        current = self.head
        count = 0
        while count != None:
            count += 1
            current = current.getNext()
        return count

    def search(self,item):
        current = self.head
        find = False
        while current != None and not find:
            if current == item:
                find = True
            else:
                current = current.getNext()
        return find

    def remove(self,item):
        current = self.head
        pronode = None
        find = False
        while current != None and find == False:
            if current == item:
                find = True
            else:
                pronode = current
                current = current.getNext()
        if pronode == None:
            self.head = current.getNext()
        else:
            current.setNext(current.getNext())

有序列表

在有序列表中,元素的位置取决于其基本特征,通常是升序或降序。 在用Python实现有序列表的过程中,重点考虑add方法和search方法,因为这是一个有序列表,如果他升序排列,那在遍历过程汇总,如果要找的数字大于遍历取得的数值,那就可以证明找不到该元素。而在添加元素的时候,要考虑列表元素的位置。

class Node:
    def __init__(self,initdata):
        self.data = initdata
        self.next = None

    def getdata(self):
        return self.data
    def getnext(self):
        return self.next
    def setdata(self,newdata):
        self.data = newdata
    def setNext(self,newnext):
        self.next =newnext
class orderlist:
    def __init__(self):
        self.head = None


    def length(self):
        current = self.head
        count = 0
        while count != None:
            count += 1
            current = current.getNext()
        return count

    def remove(self,item):
        current = self.head
        pronode = None
        find = False
        while current != None and find == False:
            if current == item:
                find = True
            else:
                pronode = current
                current = current.getNext()
        if pronode == None:
            self.head = current.getNext()
        else:
            current.setNext(current.getNext())

    def isEmpty(self):
        return self.head == None

    def search(self,item):
        current = self.head
        find = False
        stop = False
        while current != None and not find and not stop:
            if current == item:
                find = True
            else:
                if item < current.getDate():
                    stop = False
                else:
                    current = current.getNext()
        return find
    def add(self,item):
        current = self.head
        pronode = None
        find = False
        while current != None and find == False:
            if current == item:
                find = True
            else:
                pronode = current
                current = current.getNext()
        temp = Node(item)
        if pronode == None:
            temp.setNext(self.head)
            self.head = temp
        else:
            temp.setNext(self.head)
            pronode.setNext(temp)