堆栈与队列算法
1. 堆栈(Stack)
2. 队列(Queue)
- 单向队列
- 双向队列
- 优先队列
1. 堆栈(Stack)
介绍
堆栈(stack)也叫“栈”,是一种后进先出(LIFO, Last In First Out)的线性表,它的特点在于只允许在堆栈的一端(top,栈顶)进行插入数据(push)和弹出数据(pop)的运算。
栈没有了位置概念,保证任何时候可以访问、删除的元素都是此前最后存入的那个元素,确定了一种默认的访问顺序。
栈与线性表的区别
- 栈(和队列):描述的是数据的操作方式。
- 线性表:描述的是数据的存储方式。
栈结构的实现
栈可以用顺序表实现,也可以用链表实现。
- 以顺序表来实现栈的好处是设计的算法相当简单,但是,如果栈本身的大小是变动的,而顺序表大小只能事先规划和声明好,那么顺序表规划太大了会浪费空间,规划太小了则又不够用。
- 以链表来实现栈的好处是随时可以动态改变链表长度,能有效利用内存资源,不过缺点是设计的算法较为复杂。
代码实现 1:用列表实现栈
- Stack():创建一个新的空栈。
- push(item):添加一个新的元素 item 到栈顶。
- pop():弹出栈顶元素。
- peek():返回栈顶元素。
- is_empty():判断栈是否为空。
- size():返回栈的元素个数。
1 class Stack: 2 "栈" 3 4 def __init__(self): 5 self.__list = [] 6 7 def push(self, item): 8 "添加一个新的元素item到栈顶" 9 self.__list.append(item) # 时间复杂度为O(1) 10 # self.__list.insert(0, item) # 时间复杂度为O(n) 11 12 def pop(self): 13 "弹出栈顶元素" 14 return self.__list.pop() # 接收列表pop方法的返回值 15 # self.__list.pop(0) # 时间复杂度为O(n) 16 17 def peek(self): 18 "返回栈顶元素" 19 if self.__list: 20 return self.__list[-1] 21 else: 22 return 23 24 def is_empty(self): 25 "判断栈是否为空" 26 return self.__list == [] 27 # return not self.__head 28 29 def size(self): 30 "返回栈的元素个数" 31 return len(self.__list) 32 33 34 if __name__ == "__main__": 35 s = Stack() 36 print(s.is_empty()) # True 37 s.push(1) 38 print(s.is_empty()) # False 39 s.push(2) 40 s.push(3) 41 print(s.peek()) # 3 42 s.push(4) 43 s.push("hello") 44 print(s.pop()) # hello 45 print(s.pop()) # 4 46 print(s.size()) # 3
代码实现 2:用链表实现栈
栈的操作
- Stack():创建一个新的空栈。
- push(item):添加一个新的元素 item 到栈顶。
- pop():弹出栈顶元素。
- is_empty():判断栈是否为空。
1 class Node: 2 "堆栈的链表节点类" 3 4 def __init__(self, data): 5 self.data = data # 本节点存储的数据 6 self.next = None # 指向下一个节点 7 8 class Stack: 9 "堆栈类" 10 11 # 初始化栈顶节点变量 12 def __init__(self): 13 self.top = None 14 15 # 判断堆栈是否为空 16 def is_empty(self): 17 if not self.top: 18 return True 19 else: 20 return False 21 22 # 将指定数据压栈 23 def push(self, data): 24 new_add_node = Node(data) 25 new_add_node.next = self.top 26 self.top = new_add_node 27 28 # 弹栈 29 def pop(self): 30 if self.is_empty(): 31 print("===当前堆栈已为空===") 32 return 33 else: 34 cur = self.top.data # 记录栈顶数据 35 self.top = self.top.next # 将栈顶节点指向后一个节点 36 return cur 37 38 # 主程序 39 s = Stack() 40 while 1: 41 i = int(input("压栈请输入【1】,弹栈请输入【0】,停止操作则输入【-1】:")) 42 if i == -1: 43 break 44 elif i == 1: 45 value = input("请输入要压栈的元素值:") 46 s.push(value) 47 elif i == 0: 48 print("弹栈元素为 %s" % s.pop()) 49 50 print("="*40) 51 while not s.is_empty(): # 陆续打印弹栈元素 52 print("弹栈顺序为:%s" % s.pop()) 53 print("="*40)
执行结果:
压栈请输入【1】,弹栈请输入【0】,停止操作则输入【-1】:1 请输入要压栈的元素值:7 压栈请输入【1】,弹栈请输入【0】,停止操作则输入【-1】:1 请输入要压栈的元素值:8 压栈请输入【1】,弹栈请输入【0】,停止操作则输入【-1】:1 请输入要压栈的元素值:9 压栈请输入【1】,弹栈请输入【0】,停止操作则输入【-1】:1 请输入要压栈的元素值:10 压栈请输入【1】,弹栈请输入【0】,停止操作则输入【-1】:0 弹栈元素为 10 压栈请输入【1】,弹栈请输入【0】,停止操作则输入【-1】:0 弹栈元素为 9 压栈请输入【1】,弹栈请输入【0】,停止操作则输入【-1】:-1 ======================================== 弹栈顺序为:8 弹栈顺序为:7 ========================================
2. 队列(Queue)
介绍
队列是一种先进先出(FIFO, First In First Out)的线性表,只允许在一端进行插入数据(enqueue),在另一端进行删除数据(dequeue)。
在队列中,允许插入的一端为队尾,允许删除的一端为队头,队列不允许在中间部位进行操作。
假设队列是 q =(a1,a2,……,an),那么 a1 就是队头元素,而 an 是队尾元素。这样我们就可以删除时,总是从 a1 开始,而插入时,总是在队列最后。这也比较符合我们通常生活中的习惯,排在第一个的优先出列,最后来的当然排在队伍最后。
队列结构的实现
同栈一样,队列也可以用顺序表或者链表实现。
1)单向队列
代码实现 1:用列表实现队列
- Queue():创建一个空的队列。
- enqueue(item):往队列中添加一个 item 元素。
- dequeue():从队列头部删除一个元素。
- is_empty():判断一个队列是否为空。
- size():返回队列的大小。
1 class Queue: 2 "队列" 3 4 def __init__(self): 5 self.__list = [] 6 7 def enqueue(self, item): 8 "往队列中添加一个item元素" 9 # 根据具体的使用频率,决定添加/删除元素的方式 10 self.__list.append(item) # O(1) 11 # self.__list.insert(0, item) # O(n) 12 13 def dequeue(self): 14 "从队列头部删除一个元素" 15 return self.__list.pop(0) # O(n) 16 # return self.__list.pop() # O(1) 17 18 def is_empty(self): 19 "判断一个队列是否为空" 20 return self.__list == [] 21 # return not self.__list 22 23 def size(self): 24 "返回队列的大小" 25 return len(self.__list) 26 27 28 if __name__ == "__main__": 29 q = Queue() 30 print(q.is_empty()) # True 31 q.enqueue("hello") 32 print(q.is_empty()) # False 33 q.enqueue(1) 34 q.enqueue(2) 35 q.enqueue(3) 36 print(q.dequeue()) # hello 37 print(q.dequeue()) # 1 38 print(q.size()) # 2
代码实现 2:用链表实现队列
- Queue():创建一个空的队列。
- enqueue(item):往队列中添加一个 item 元素。
- dequeue():从队列头部删除一个元素。
- is_empty():判断一个队列是否为空。
1 class Node: 2 "队列的链表节点类" 3 4 def __init__(self, data): 5 self.data = data # 本节点存储的数据 6 self.next = None # 指向下一个节点 7 8 class Queue: 9 "队列类" 10 11 # 初始化队列头部和尾部节点变量 12 def __init__(self): 13 self.head = None 14 self.end = None 15 16 # 判断队列是否为空 17 def is_empty(self): 18 if not self.head: 19 return True 20 else: 21 return False 22 23 # 往队列尾部添加元素 24 def enqueue(self, data): 25 new_node = Node(data) 26 # 表明添加的是第一个元素 27 if not self.end: 28 self.head = new_node 29 # 否则将新节点连接到队列末尾 30 else: 31 self.end.next = new_node 32 # 将新的队列末尾变量指向新节点 33 self.end = new_node 34 35 # 删除队列头部元素 36 def dequeue(self): 37 if self.is_empty(): 38 print("===当前队列已为空===") 39 return 40 else: 41 cur = self.head.data # 记录队列头部数据 42 self.head = self.head.next # 将头部节点指向后一个节点 43 return cur 44 45 # 主程序 46 q = Queue() 47 while 1: 48 i = int(input("添加元素请输入【1】,删除元素请输入【0】,停止操作则输入【-1】:")) 49 if i == -1: 50 break 51 elif i == 1: 52 value = input("请输入要添加的元素值:") 53 q.enqueue(value) 54 elif i == 0: 55 print("删除元素:%s" % q.dequeue()) 56 57 print("="*40) 58 while not q.is_empty(): 59 print("元素删除顺序为:%s" % q.dequeue()) 60 print("="*40)
执行结果:
添加元素请输入【1】,删除元素请输入【0】,停止操作则输入【-1】:1 请输入要添加的元素值:5 添加元素请输入【1】,删除元素请输入【0】,停止操作则输入【-1】:1 请输入要添加的元素值:6 添加元素请输入【1】,删除元素请输入【0】,停止操作则输入【-1】:1 请输入要添加的元素值:7 添加元素请输入【1】,删除元素请输入【0】,停止操作则输入【-1】:1 请输入要添加的元素值:8 添加元素请输入【1】,删除元素请输入【0】,停止操作则输入【-1】:0 删除元素:5 添加元素请输入【1】,删除元素请输入【0】,停止操作则输入【-1】:0 删除元素:6 添加元素请输入【1】,删除元素请输入【0】,停止操作则输入【-1】:-1 ======================================== 元素删除顺序为:7 元素删除顺序为:8 ========================================
2)双向列表
双向队列(deque,全名 double-ended queue)又称双端链表,是一种具有队列和栈的性质的数据结构。
双向队列中的元素可以从两端弹出,其限定插入和删除操作在表的两端进行。双向队列可以在队列任意一端入队和出队。
通常在一般的应用上,双向队列可以区分为两种:
- 数据只能从一端加入,但可从两端取出。
- 数据可从两端加入,但只能从一端取出。
代码实现 1:用列表实现双端列表
- Deque():创建一个空的双向队列。
- add_front(item):从队头加入一个 item 元素。
- add_rear(item):从队尾加入一个 item 元素。
- remove_front():从队头删除一个 item 元素。
- remove_rear():从队尾删除一个 item 元素。
- is_empty():判断双向队列是否为空。
- size():返回队列的大小。
1 class Deque: 2 "双端队列" 3 4 def __init__(self): 5 self.__list = [] 6 7 def add_front(self, item): 8 "往队列头部添加一个item元素" 9 self.__list.insert(0, item) 10 11 def add_rear(self, item): 12 "往队列尾部添加一个item元素" 13 self.__list.append(item) 14 15 def remove_front(self): 16 "从队列头部删除一个元素" 17 return self.__list.pop(0) 18 19 def remove_rear(self): 20 "从队列尾部删除一个元素" 21 return self.__list.pop() 22 23 def is_empty(self): 24 "判断一个队列是否为空" 25 return self.__list == [] 26 # return not self.__list 27 28 def size(self): 29 "返回队列的大小" 30 return len(self.__list) 31 32 33 if __name__ == "__main__": 34 d = Deque() 35 print(d.is_empty()) # True 36 d.add_front(1) 37 d.add_front(2) 38 d.add_rear(3) 39 d.add_rear(4) 40 print(d.is_empty()) # False 41 print(d.size()) # 4 42 print(d.remove_front()) # 2 43 print(d.remove_rear()) # 4 44 print(d.size()) # 2
代码实现 2:用链表实现双向队列
- Deque():创建一个空的双向队列。
- enqueue(item):从队头加入一个 item 元素。
- dequeue(action):根据 action 从队头或队尾删除一个元素。
- is_empty():判断双向队列是否为空。
1 class Node: 2 "队列的链表节点类" 3 4 def __init__(self, data): 5 self.data = data # 本节点存储的数据 6 self.next = None # 指向下一个节点 7 8 class Deque: 9 "双向队列类" 10 11 # 初始化队列头部和尾部节点变量 12 def __init__(self): 13 self.head = None 14 self.end = None 15 16 # 判断队列是否为空 17 def is_empty(self): 18 if not self.head: 19 return True 20 else: 21 return False 22 23 # 往队列尾部添加元素 24 def enqueue(self, data): 25 new_node = Node(data) 26 # 表明添加的是第一个元素 27 if not self.end: 28 self.head = new_node 29 # 否则将新节点连接到队列末尾 30 else: 31 self.end.next = new_node 32 # 将新的队列末尾变量指向新节点 33 self.end = new_node 34 35 # 可从队列两端删除元素 36 def dequeue(self, action): 37 if self.is_empty(): 38 print("===当前队列已为空===") 39 return 40 # 从队列头部删除元素 41 if action == 1: 42 value = self.head.data # 记录队列头部数据 43 self.head = self.head.next # 将头部节点指向后一个节点 44 return value 45 # 从队列尾部删除元素 46 elif action == 2: 47 value = self.end.data # 记录队末数据 48 # 若队列只有一个元素 49 if not self.head.next: 50 self.head = None 51 self.end = None 52 # 否则遍历找到倒数第二个节点 53 else: 54 cur = self.head 55 while cur.next.next: 56 cur = cur.next 57 self.end = cur 58 self.end.next = None 59 return value 60 61 # 主程序 62 q = Deque() 63 print("用链表来实现双向队列") 64 print("="*40) 65 while 1: 66 i = int(input("添加元素请输入【1】,删除元素请输入【0】,停止操作则输入【-1】:")) 67 if i == -1: 68 break 69 elif i == 1: 70 value = input("请输入要添加的元素值:") 71 q.enqueue(value) 72 elif i == 0: 73 print("从队头删除元素:%s" % q.dequeue(1)) 74 print("从队尾删除元素:%s" % q.dequeue(2))
3)优先队列
优先队列(Priority Queue)是一种不必遵守队列特性(先进先出)的有序线性表,其中的每一个元素都被赋予了一个优先级(Priority),加入元素时可任意加入,但有最高优先级者(Highest Priority Out First,HPOF)则最先输出。
像一般医院中的急诊室,当然以最严重的病患优先诊治,跟进入医院挂号的顺序无关。又例如在计算机中 CPU 的作业调度,优先级调度(Priority Scheduling,PS)就是一种按进程优先级”调度算法“(Scheduling Algorithm)进行的调度,这种调度就会使用到优先队列,好比优先级高的用户,就比一般用户拥有较高的权力。
注意,当各个元素按输入先后次序为优先级时,就是一般的队列;若是以输入先后次序的倒序作为优先级,此优先队列即为一个堆栈。