数据结构与算法(三)队列与堆(Queue and Heap)
队列(queue)
基本FIFO队列是先进先出( First-In-First-Out)的线性表。在具体应用中通常用链表或者数组来实现。队列只允许在后端(称为rear)进行插入操作,在前端(称为front)进行删除操作。
除基本FIFO队列外, 还有LIFO队列( 后进先出 Last-In-First-Out, 也就是我们平时说的堆栈的栈)和优先级队列( Priority Queue)
队列的类型:链式队列:即用链表实现的队列。静态队列:即用数组实现的队列。
#十分钟python理解三种队列:
- Python Threading Beginners Tutorial- Queue (part#1 ) https://www.youtube.com/watch?v=bnm5_GH04fM 内容为FIFO
- Python Threading Beginners Tutorial- Queue -(part#2) https://www.youtube.com/watch?v=wkPMom77Hqk 内容为LIFO和Priority
我们通过内置queue模块来对比一下三个队列:
https://docs.python.org/3/library/queue.html #Python3官方文档-queue
***
import queue
#首先是FIFO
q = queue.Queue()
for i in range(5):
q.put(i)
while not q.empty():
print (q.get(),end = ' ') #结果:0 1 2 3 4
#然后是LIFO
q = queue.LifoQueue()
for i in range(5):
q.put(i)
while not q.empty():
print (q.get(),end = ' ') #结果:4 3 2 1 0
可以看到LIFO的结果和FIFO完全相反
#最后是Priority
import queue
q = queue.PriorityQueue()
q.put((1, 'Priority 1'))
q.put((3, 'Prioirty 3'))
q.put((4 ,'Priority 4'))
q.put((2 ,'Priority 2'))
for i in range(q.qsize()):
print(q.get()[1])
#结果 Priority 1
# Priority 2
# Priority 3
# Priority 4
优先级队列把后面的元素按照priority_number的顺序输出出来了
拓展阅读: 双端队列和多线程队列
详解Python中的四种队列 https://www.jb51.net/article/140534.htm
堆(Heap)
堆是一种经过排序的树形数据结构,每个结点都有一个值。通常我们所说的堆的数据结构,是指二叉堆。
堆的特点是根结点的值最小(或最大),且根结点的两个子树也是一个堆。由于堆的这个特性,常用来实现优先队列。
堆分为最大堆和最小堆,其实就是完全二叉树。 如果要升序排序就建大顶堆,降序排序建小顶堆
堆和栈的区别:
堆的存取是随意,这就如同我们在图书馆的书架上取书,虽然书的摆放是有顺序的,但是我们想取任意一本时不必像栈一样,先取出前面所有的书,书架这种机制不同于箱子,我们可以直接取出我们想要的书。
堆排序(Heapsort)
堆排序就是把堆顶的最大数取出,
将剩余的堆继续调整为最大堆,具体过程在第二块有介绍,以递归实现
剩余部分调整为最大堆后,再次将堆顶的最大数取出,再将剩余部分调整为最大堆,这个过程持续到剩余数只有一个时结束
代码取自维基百科 https://zh.wikipedia.org/wiki/堆排序
#!/usr/bin/env python
#-*-coding:utf-8-*-
def heap_sort(lst):
def sift_down(start, end):
"""最大堆调整"""
root = start
# 从root开始对最大堆调整
while True:
child = 2 * root + 1 #left child 因为我们是从0开始, 所以要+1
if child > end:
break
# 找出两个child中交大的一个
if child + 1 <= end and lst[child] < lst[child + 1]: #如果左边小于右边
child += 1 #设置右边为大
if lst[root] < lst[child]:
# 最大堆小于较大的child, 交换顺序
lst[root], lst[child] = lst[child], lst[root]
# 正在调整的节点设置为root
root = child
else:
# 无需调整的时候, 退出
break
# 创建最大堆
# 从最后一个有子节点的位置开始调整最大堆
for start in range((len(lst) - 2) // 2, -1, -1):
sift_down(start, len(lst) - 1)
# 堆排序
# 将最大的放到堆的最后一个, 堆-1,
# 这是第一个数就是一个很小的数了,继续调整排序, 知道再次成为最大堆
for end in range(len(lst) - 1, 0, -1):
lst[0], lst[end] = lst[end], lst[0]
sift_down(0, end - 1)
print(lst) #验证一下结果
return lst
def main():
l = [9, 2, 1, 7, 6, 8, 5, 3, 4]
heap_sort(l)
if __name__ == "__main__":
main()
#结果 [1,2,3,4,5,6,7,8,9]
看了半天才把这个代码完全弄明白, 好在用纸把图画下来, 走一走就明白了, 很开心
LeetCode思想实践:
#239 Sliding Window Maximum
相邻k个数就取一个最大值, 输出列表
Input: nums = [1,3,-1,-3,5,3,6,7], and k = 3
Output: [3,3,5,5,6,7]
Explanation:
Window position Max
[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7
LeetCode根本就不用考虑暴力双循环了, 肯定超时
用长度为k的双端队列存储,最大值总是存在队列头,较小值存在队列后,队列持续更新,每次输出队列头即可。想不明白可以加上print运行一边代码看每次循环后的队列更新情况
class Solution:
def maxSlidingWindow(self, nums, k):
res = []
index = []
for i in range(len(nums)):
# index[0]已经走出k长度的判断条,淘汰
while index and index[0] <= i - k:
index.pop(0)
# index[-1]没有下一个值大,淘汰
while index and nums[index[-1]] <= nums[i]:
index.pop(-1)
index.append(i)
if i < k-1: # 凑齐k个数才输出
continue
res.append(nums[index[0]])
return res
结果:
今天的学习有用到前两天的知识, 要记得复习
哈希表 主要是一种以空间换时间的数据结构,追求效率,查找和插入的时间复杂度都为O(1)。
链表 主要是在插入的时候可以达到O(1)的复杂度,但是在查找特定编号节点需要O(n)的时间。
结尾不能忘了感谢体统资料的大神们:
- 详解Python中的四种队列 https://www.jb51.net/article/140534.htm
- 维基百科-堆排序 https://zh.wikipedia.org/wiki/堆排序
- 图解排序算法(三)之堆排序 http://www.cnblogs.com/chengxiao/p/6129630.html
- Python3-queue https://docs.python.org/3/library/queue.html
- 堆和栈的区别 之 数据结构和内存 https://blog.csdn.net/langb2014/article/details/79376349
- 用python实现堆排序 https://blog.csdn.net/dujiahaogod/article/details/79688331
下次见