python 下的数据结构与算法---4:线形数据结构,栈,队列,双端队列,列表
目录:
前言
1:栈
1.1:栈的实现
1.2:栈的应用:
1.2.1:检验数学表达式的括号匹配
1.2.2:将十进制数转化为任意进制
1.2.3:后置表达式的生成及其计算
2:队列
2.1:队列的实现
2.2:队列的应用之囚徒问题
3:双端队列
3.1:双端队列的实现
3.2:双端队列的应用之回文检测
4:列表
3.1:链表的实现
前言
线性数据结构有四种:栈(stack),队列(queue),双端队列(deque),列表(list)
线性数据结构就是一群数据的集合,数据的位置和其加入的先后顺序有关,并且由此将其看为有两端,头(top)与尾(rear)【其中认为头是最先加入的数据,尾是最后加入的数据】
一:栈(stack)
1:栈的构造
栈可以看成是堆盘子,只能从上端加入以及从上端取出,也就是只能从一端对其进行操作。对应到线形结构就是只能在尾部对其进行加入与删除的数据结构。简称LIFO(last in first out),即是后进先出。
习惯上我们把栈尾部叫做栈顶(TOP),加入数据叫做压栈(push),删除叫做出栈(pop)
要注意一点的是,此处的栈顶其实是list的队尾,理论上将两个top对应起来也是简单的,但是前面讲过list的insert(0,i)与pop(0)都是O(n)的操作,而末尾的append(i)与pop(i)都是O(1),故依旧选择后者。
1 class Stack: 2 def __init__(self,*args): 3 self.cstack = [] #c:construct 4 for i in args: 5 self.cstack.append(i) 6 def push(self,x): #压栈 7 self.cstack.append(x) 8 def size(self): #查看元素个数 9 return len(self.cstack) 10 def pop(self): #出栈 11 if self.size()>0: 12 return self.cstack.pop() 13 else: 14 print('Sorry, this is an empty stack') 15 def peek(self): #检查栈顶元素是什么 16 if self.size()>0: 17 return self.cstack[len(self.cstack)-1] 18 else: 19 return 'empty stack'
由上我们可以看出,其操作都是基于list的,结合昨天的表可以知道:
Stack = Stack(1,2,3,4,5…,n) O(n)
Push,pop,size,peek O(1)
2:栈的应用:
1]: 检测数学表达式的括号是否配对
遍历输入的表达式,遇到(后压入栈,遇到)后将栈顶(出栈,若最后为空栈则是匹配成功的,其他 [ ]与 { }同理
1 from stackType_01 import Stack 2 3 while True: 4 stack = Stack() 5 str = input('input the arithmetic expression:') 6 if len(str) == 0: 7 print('input something') 8 else: 9 for i in str: 10 if i in '({[': #!!! 11 stack.push(i) 12 elif i in ')}]': 13 stack.pop() 14 if stack.size()==0: 15 print('It is a balanced arithmetic expression') 16 else: 17 print('not a balanced arithmatic expression')
2]:将一个十进制的数转化为任意进制的数
原理:首先,n进制就是从0开始到n-1,若n>9则从A开始分别表示10……
其次,假设输入的十进制是n,想要转化为x进制,此时首先对n除以x取余,将余数放入栈,而后将n整除x后的数作为下一个n重复上面两个步骤直到n整除值为零,最后栈里面的数的逆序就是结果了:
e.g:十进制转二进制
1 from stackType_01 import Stack 2 deci = int(input('input the number:')) 3 base = int(input('input the system you want to transfer to:')) 4 5 def trans(deci,base): 6 flag = '0123456789ABCDEF' 7 stack = Stack() 8 while deci//base > 0: 9 stack.push(flag[deci%base]) 10 deci //= base 11 stack.push(flag[deci]) 12 print(stack.cstack) 13 14 trans(deci,base)
3]:后置,前置,中置表达式
即是把运算符放到最近的括号外(前置放括号前面,后置放后面)。
将普通表达式转化为后置表达式的原理:创建一个list以及一个stack,遇到’(,+-*/’ 都压入栈,遇到数字放入list,遇到‘)‘就把栈里面直到最近的一个’(‘中的运算符pop入list,如下图所示:
遇到后置表达式的运算原理:创建一个栈,开始遍历后置表达式,如果是数字就压入栈,如果是运算符就出栈两个数进行对应的运算后得到一个数并将其压入栈。
#python提供的eval函数能够对string型运算表达式进行运算并返回值
1 # -*- coding:utf-8 -*- 2 # from stackType_01 import Stack 3 import stackType_01 4 def ToPostfix(expression): 5 L = [] 6 stack = stackType_01.Stack() 7 for i in expression: 8 if i in '(+-*/': 9 stack.push(i) 10 elif i == ')': 11 while stack.peek() != '(': 12 L.append(stack.pop()) 13 stack.pop() 14 else: 15 L.append(i) 16 print('postfix:',''.join(L)) 17 return L 18 19 def calpostfix(stt): 20 stack = stackType_01.Stack() 21 for i in stt: 22 if i in '+-*/': 23 pop1 = stack.pop() 24 pop2 = stack.pop() 25 temp = pop2+i+pop1 26 stack.push(str(eval(temp))) 27 else: 28 stack.push(i) 29 print('calresult:',stack.peek()) 30 31 expression = list(input('plz input the expression with balanced parentheses:')) 32 calpostfix(ToPostfix(expression))
二:队列(queue)
1:队列的实现
区别于栈的只能够从一边进行出,队列是一端进,一段出,就像有一截水管,从一端先进的从另一端先出,这叫FIFO(first in first out),即是先进先出。
习惯上我们把加入数据叫做入队(enqueue),删除数据叫离队(dequeue)。
由于还是用list来实现queue,故说明下队首与list位置的关系,为了模拟左进右出的管道,我们把list尾部实现为queue的对首,即是即将删除的数据。
1 class Queue: # right is the front 2 def __init__(self,*args): 3 self.item = [] 4 for i in args: 5 self.item.insert(0,i) 6 7 def size(self): 8 return len(self.item) 9 10 def is_empty(self): 11 if self.size() == 0: 12 return True 13 else: 14 return False 15 def enqueue(self,*arg): 16 for i in arg: 17 self.item.insert(0,i) 18 19 def dequeue(self): 20 if self.size() > 0: 21 return self.item.pop() 22 else: 23 print('empty queue, cannot dequeue anymore') 24 25 # que = Queue(1,2,3) 26 # que.enqueue(4,5) 27 # print(que.item) 28 # print(que.dequeue()) 29 # print(que.dequeue()) 30 # print(que.size())
显然,dequeue方法为O(n),enqueue方法为O(1)
2:队列的应用
囚徒问题:n个犯人围城圈,从头开始数一个确定的数,到达这个数后此人出圈,他下个人继续从1开始数,直到最后只剩下一个人,用程序模拟此过程:
实现原理:用队列实现一个圆圈的模拟,从队首开始,没经过了它就把计数器flag加一而后把此元素删除并且放到队尾,直到遇到flag为判别数的人,将此人删掉,flag置一后又开始直到只剩下一个人
1 from Queue_05 import Queue 2 names = input('plz input the names(with blank betwwwn them):') 3 juge = int(input('plz input the judge number:')) 4 flag = 1 5 queue = Queue() 6 for i in names.split(" "): 7 queue.enqueue(i) 8 9 while queue.size() != 1: 10 if flag != juge: 11 queue.enqueue(queue.dequeue()) 12 flag+=1 13 else: 14 queue.dequeue() 15 flag=1 16 17 print(queue.item)
三:双端队列(deque)
1:双端队列的实现
区别于队列的一端进一端出,双端队列是在两端都可以进出的数据结构。
我们把list右端作为其前端,且分别把队头,队尾的增删操作方法为add_front / rear,remove_front / rear
1 class Deque: # right is the front 2 def __init__(self,*args): 3 self.item = [] 4 for i in args: 5 self.item.insert(0,i) 6 7 def __len__(self): 8 return len(self.item) 9 def size(self): 10 return len(self.item) 11 12 def is_empty(self): 13 if self.size() == 0: 14 return True 15 else: 16 return False 17 def add_front(self,*arg): 18 for i in arg: 19 self.item.append(i) 20 21 def add_rear(self,*arg): 22 for i in arg: 23 self.item.insert(0,i) 24 25 def remove_front(self): 26 if self.size() > 0: 27 return self.item.pop() 28 else: 29 print('empty deque, cannot dequeue anymore') 30 31 def remove_rear(self): 32 if self.size() > 0: 33 return self.item.pop(0) 34 else: 35 print('empty deque, cannot dequeue anymore')
注意,此地增删可同时n个数,front端的增删为O(n),rear端的为O(n^2)
2:应用
回文检验:原理,从队列两端分别删除元素来进行比较,直到剩下不多于一个数且都全部匹配则为回文
1 from Deque_07 import Deque 2 flag = 1 3 expression = input('plz input the string:') 4 deque = Deque() 5 for i in expression: 6 deque.add_rear(i) 7 8 while deque.size() != 1 or 0: 9 a = deque.remove_front() 10 b = deque.remove_rear() 11 if a!=b: 12 flag = 0 13 break 14 15 if flag: 16 print('yes, it is huiwen') 17 else: 18 print('nout a huiwen string')
四:列表(list)
原本的list,python是有的,不过在这里我们要实现一个基于相对位置有序的列表,也就是C语言中我们所说的链表(linked list)
设计思路:拆分出来看,每个节点(node)都是一个值和一个指向下个节点的变量构成的,总体上来看,有first与last两个变量分别指向第一个节点和最后一个节点。
1 class LinkedList: 2 3 class __Node: 4 def __init__(self, item, next = None): 5 self.item = item 6 self.next = next 7 8 def getItem(self): 9 return self.item 10 11 def setItem(self, item): 12 self.item = item 13 14 def setNext(self,next): 15 self.next = next 16 17 def getNext(self): 18 return self.next 19 20 def __init__(self,contents=[]): 21 self.first = LinkedList.__Node(None,None) 22 self.last = self.first 23 self.numItems = 0 24 for i,e in enumerate(contents): 25 if i == 1: 26 self.first = self.first.getNext() 27 self.append(e) 28 29 30 def append(self,item): 31 node = LinkedList.__Node(item) 32 self.last.setNext(node) 33 self.last = node 34 self.numItems += 1 35 36 def __getitem__(self,index): 37 if index >= 0 and index < self.numItems: 38 cursor = self.first.getNext() 39 for i in range(index): 40 cursor = cursor.getNext() 41 return cursor.getItem() 42 43 raise IndexError("LinkedList index out of range") 44 45 def __setitem__(self,index,val): 46 if index >= 0 and index < self.numItems: 47 cursor = self.first.getNext() 48 for i in range(index): 49 cursor = cursor.getNext() 50 cursor.setItem(val) 51 return 52 53 raise IndexError("LinkedList assignment index out of range") 54 55 def __add__(self,other): 56 if type(self) != type(other): 57 raise TypeError("Concatenate undefined for " + str(type(self)) + " + " + str(type(other))) 58 result = LinkedList() 59 cursor = self.first.getNext() 60 while cursor != None: 61 result.append(cursor.getItem()) 62 cursor = cursor.getNext() 63 64 cursor = other.first.getNext() 65 66 while cursor != None: 67 result.append(cursor.getItem()) 68 cursor = cursor.getNext() 69 return result 70 71 def insert(self,index,item): 72 cursor = self.first 73 if index < self.numItems: 74 for i in range(index): 75 cursor = cursor.getNext() 76 77 node = LinkedList.__Node(item, cursor.getNext()) 78 cursor.setNext(node) 79 self.numItems += 1 80 else: 81 self.append(item) 82 83 linkedlist = LinkedList([1,2,3,4,5]) 84 linkedlist2 = LinkedList([9,8,7,6]) 85 print(linkedlist.first.item) 86 87 print(linkedlist[2]) 88 linkedlist.append('append') 89 linkedlist.insert(2,'insert') 90 linkedlist += linkedlist2 91 for i in linkedlist: 92 print(i)
其实这个程序有点小问题不知你发现没有,我也是刚发现的,其实就是first指标其实一直指向了一个空节点。通过实例.getnext()才能够获得真正的第一个节点。
将(*)部分改成如下即可:
1 for i,e in enumerate(contents): 2 if i == 1: 3 self.first = self.first.getNext() 4 self.append(e)
需要注意的是linked list的性质就和list完全不一样了,我们回顾一下,list的访问元素时间都是O(1),中间插入元素为O(k)[k为插入位置后面有的元素数],而linked元素的位置由于是相对的,访问元素需要从first开始依次查找,故时间为O(k)[k为寻找元素前面的元素个数],插入元素则比较简单,改变下两个next指向就行了,所以为O(1),好吧,不开玩笑了,其实不是这样的,插入元素前需要先从头开始找到插入元素前的节点,而此为O(k),是更高阶,所有还是为O(k),好吧,其实上面两个都对,o(1)情况是若linked list无序时,直接加到最后,O(k)是若是有序的,则需要从头开始比较比它小的k个数后再放入。