线性数据结构包括栈、列队、双端列队和列表,其元素顺序取决于添加和删除顺序。
栈
栈分顶端和底端,添加和移除操作总是发生在顶端,后进先出(LIFO)。栈的操作就好像拿走一个顶端的托盘。
用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)。
匹配括号,匹配括号是左边的每个括号都有右边的括号对应,并且括号都有对应的匹配关系。
在书本给出的算法中,利用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
十进制转化
十进制数转化为二进制,在十进制向二进制转化的过程中,得数不为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
前序、中序、后续表达式
中序表达式,运算符合出现在操作数的中间,即我们常见的数学表达式。而通过对运算符的移动,可以得到前序表达式和后续表达式。
相比于中序表达式,前/后序表达式的运算顺序完全由符号位置决定。
中序表达式向后序表达式转换,先把中序表达式看成完全括号表达式,遇上左括号就去掉,运算符移到右括号的位置。
中序表达式向前序表达式转换,先把中序表达式看成完全括号表达式,遇上右括号就去掉,运算符移到左括号的位置。
向后续表达式转化:创建一个保存运算符的空栈和表达结果的空列表,用split将输入的字符串转化为列表,从左往右扫描列表。 如果是字母就输入结果列表,如果是左括号就压入栈,遇上右括号就回到栈删除左括号并向结果列表输出运算符,如果是运算符就输入到栈中,但此前要向结果列表输出同级及更高级的运算符。结束后输出栈中剩余的符号。
我们通过字典prec储存符号的大小,当遇上加减时,要先输出已经存储的乘除运算符,保障乘除在加减前执行,而左括号是一个特殊情况,只有遇上右括号才对其进行处理,因此要对左括号给予一个最小的权重,保证四则运算符号的处理过程不会影响括号。
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 "))
我们输入一个后序表达式,对其进行运算,根据后序表达式的运算规则,要对运算符号前的两位数字进行处理,而运算符号的顺序决定了计算的顺序。
我们创建一个自定义函数来定义四则运算的过程。
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
双端列队
双端列队是与列队相似的有序集合,有一前一后两端,对在哪端进出没有限制而是根据需求进行选择。
用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)