Skip to content

Commit dc2d395

Browse files
committed
队列
1 parent 4c9cc17 commit dc2d395

File tree

4 files changed

+286
-0
lines changed

4 files changed

+286
-0
lines changed

4_队列/circular_queue.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# -*- coding: utf-8 -*-
2+
3+
4+
# NOTE: 从 array_and_list 第一章拷贝的代码
5+
class Array(object):
6+
7+
def __init__(self, size=32):
8+
self._size = size
9+
self._items = [None] * size
10+
11+
def __getitem__(self, index):
12+
return self._items[index]
13+
14+
def __setitem__(self, index, value):
15+
self._items[index] = value
16+
17+
def __len__(self):
18+
return self._size
19+
20+
def clear(self, value=None):
21+
for i in range(self._items):
22+
self._items[i] = value
23+
24+
def __iter__(self):
25+
for item in self._items:
26+
yield item
27+
28+
29+
class ArrayQueue(object):
30+
def __init__(self, maxsize):
31+
self.maxsize = maxsize
32+
self.array = Array(maxsize)
33+
self.head = 0
34+
self.tail = 0
35+
36+
def push(self, value):
37+
self.array[self.head % self.maxsize] = value
38+
self.head += 1
39+
40+
def pop(self):
41+
value = self.array[self.tail % self.maxsize]
42+
self.tail += 1
43+
return value
44+
45+
46+
def test_queue():
47+
size = 5
48+
q = ArrayQueue(size)
49+
for i in range(size):
50+
q.push(i)
51+
52+
assert q.pop() == 0
53+
assert q.pop() == 1
54+
assert q.pop() == 2
55+
assert q.pop() == 3
56+
assert q.pop() == 4

4_队列/deque.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# 留给读者练习

4_队列/queue.py

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
# -*- coding: utf-8 -*-
2+
3+
4+
# NOTE:注意这里是第三章 linked_list.py 里的内容,为了使文件自包含,我直接拷贝过来的
5+
class Node(object):
6+
def __init__(self, value=None, next=None): # 这里我们 root 节点默认都是 None,所以都给了默认值
7+
self.value = value
8+
self.next = next
9+
10+
def __str__(self):
11+
"""方便你打出来调试,复杂的代码可能需要断点调试"""
12+
return '<Node: value: {}, next={}>'.format(self.value, self.next)
13+
14+
__repr__ = __str__
15+
16+
17+
class LinkedList(object):
18+
""" 链接表 ADT
19+
[root] -> [node0] -> [node1] -> [node2]
20+
"""
21+
22+
def __init__(self, maxsize=None):
23+
"""
24+
:param maxsize: int or None, 如果是 None,无限扩充
25+
"""
26+
self.maxsize = maxsize
27+
self.root = Node() # 默认 root 节点指向 None
28+
self.tailnode = None
29+
self.length = 0
30+
31+
def __len__(self):
32+
return self.length
33+
34+
def append(self, value): # O(1)
35+
if self.maxsize is not None and len(self) > self.maxsize:
36+
raise Exception('LinkedList is Full')
37+
node = Node(value) # 构造节点
38+
tailnode = self.tailnode
39+
if tailnode is None: # 还没有 append 过,length = 0, 追加到 root 后
40+
self.root.next = node
41+
else: # 否则追加到最后一个节点的后边,并更新最后一个节点是 append 的节点
42+
tailnode.next = node
43+
self.tailnode = node
44+
self.length += 1
45+
46+
def appendleft(self, value):
47+
headnode = self.root.next
48+
node = Node(value)
49+
self.root.next = node
50+
node.next = headnode
51+
self.length += 1
52+
53+
def __iter__(self):
54+
for node in self.iter_node():
55+
yield node.value
56+
57+
def iter_node(self):
58+
"""遍历 从 head 节点到 tail 节点"""
59+
curnode = self.root.next
60+
while curnode is not self.tailnode: # 从第一个节点开始遍历
61+
yield curnode
62+
curnode = curnode.next # 移动到下一个节点
63+
yield curnode
64+
65+
def remove(self, value): # O(n)
66+
""" 删除包含值的一个节点,将其前一个节点的 next 指向被查询节点的下一个即可
67+
68+
:param value:
69+
"""
70+
prevnode = self.root #
71+
curnode = self.root.next
72+
while curnode.next is not None:
73+
if curnode.value == value:
74+
prevnode.next = curnode.next
75+
del curnode
76+
self.length -= 1
77+
return
78+
79+
def find(self, value): # O(n)
80+
""" 查找一个节点,返回序号,从 0 开始
81+
82+
:param value:
83+
"""
84+
index = 0
85+
for node in self.iter_node(): # 我们定义了 __iter__,这里就可以用 for 遍历它了
86+
if node.value == value:
87+
return index
88+
index += 1
89+
return -1 # 没找到
90+
91+
def popleft(self): # O(1)
92+
""" 删除第一个链表节点
93+
"""
94+
if self.root.next is None:
95+
raise Exception('pop from empty LinkedList')
96+
headnode = self.root.next
97+
self.root.next = headnode.next
98+
self.length -= 1
99+
value = headnode.value
100+
del headnode
101+
return value
102+
103+
def clear(self):
104+
for node in self.iter_node():
105+
del node
106+
self.root.next = None
107+
self.length = 0
108+
109+
######################################################
110+
# 下边是 Queue 实现
111+
######################################################
112+
113+
114+
class EmptyError(Exception):
115+
"""自定义异常"""
116+
pass
117+
118+
119+
class Queue(object):
120+
def __init__(self, maxsize=None):
121+
self.maxsize = maxsize
122+
self._item_link_list = LinkedList()
123+
124+
def __len__(self):
125+
return len(self._item_link_list)
126+
127+
def push(self, value): # O(1)
128+
""" 队尾添加元素 """
129+
return self._item_link_list.append(value)
130+
131+
def pop(self):
132+
"""队列头部删除元素"""
133+
if len(self) <= 0:
134+
raise EmptyError('empty queue')
135+
return self._item_link_list.popleft()
136+
137+
138+
def test_queue():
139+
q = Queue()
140+
q.push(0)
141+
q.push(1)
142+
q.push(2)
143+
144+
assert len(q) == 3
145+
146+
assert q.pop() == 0
147+
assert q.pop() == 1
148+
assert q.pop() == 2
149+
150+
import pytest # pip install pytest
151+
with pytest.raises(EmptyError) as excinfo: # 我们来测试是否真的抛出了异常
152+
q.pop() # 继续调用会抛出异常
153+
assert 'empty queue' == excinfo.value

4_队列/queue_and_stack.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# 队列和栈
2+
3+
前面讲了线性和链式结构,如果你顺利掌握了,下边的队列和栈就小菜一碟了。因为我们会用前两章讲到的东西来实现队列和栈。
4+
之所以放到一起讲是因为这两个东西很类似,队列是先进先出结构(FIFO, first in first out),
5+
栈是后进先出结构(LIFO, last in first out)。
6+
7+
生活中的数据结构:
8+
9+
- 队列。没错就是咱平常排队,第一个来的第一个走
10+
- 栈。好比在桶里头放盘子,先放的盘子放在了底下,后来的盘子放在上边。你要拿的时候,也是先拿最上边的。
11+
12+
本章我们详细讲讲常用的队列
13+
14+
# 队列 Queue
15+
16+
这里卖个关子,如果你熟悉了上两节讲的内容,这里你会选取哪个数据结构作为队列的底层存储?
17+
还记得第一章讲的如何实现 ADT 吗?我视频了说了三个注意事项:
18+
19+
- 1.如何选用恰当的数据结构作为存储?
20+
- 2.选取的数据结构能否满足 ADT 的功能需求
21+
- 3.实现效率如何?
22+
23+
我们先来看看 list 可以不?对照这个三个需求,看看能否满足:
24+
25+
- 1.我们选择了 list
26+
- 2.看起来队列需要从头删除,向尾部增加元素,也就是 list.insert(0, element) 和 list.append(element)
27+
- 3.嗯,貌似 list.insert(0, element) 会导致所有list元素后移,O(n)复杂度。append 平均倒是O(1),但是如果内存不够还要重新分配内存。
28+
29+
你看,使用了 list 的话频繁 insert(0, element) 和 append 都是非常低效的。
30+
31+
脑子再转转, 我们第二章实现了 链表 LinkedList,看看能否满足要求:
32+
- 1.这里选择 LinkedList
33+
- 2.删除头元素 LinkedList.popleft(),追加 append(element)。都可以满足
34+
- 3.哇欧,这两个操作都是 O(1) 的,完美。
35+
36+
好, 就用 LinkedList 了,我们开始实现,具体看视频。这次实现我们还将演示自定义异常和测试异常。
37+
38+
39+
# 用数组实现队列
40+
41+
难道用数组就不能实现队列了吗?其实还是可以的。只不过数组是预先分配固定内存的,所以如果你知道了队列的最大长度,也是
42+
可以用数组来实现的。
43+
44+
想象一下,队列就俩操作,进进出出,一进一出,pop 和 push 操作。
45+
似乎只要两个下标 head, tail 就可以了。 当我们 push 的时候赋值并且前移 head,pop 的时候前移 tail 就可以了。你可以在纸上
46+
模拟下试试。
47+
48+
我们来实现一个空间有限的循环队列。ArrayQueue,它的实现很简单,但是缺点是需要预先知道队列的长度来分配内存。
49+
50+
51+
# 双端队列 Double ended Queue
52+
看了视频相信你已经会实现队列了,你可能还听过双端队列。上边讲到的队列 队头出,尾尾进,我们如果想头部和尾巴都能进能出呢?
53+
这就是双端队列了,如果你用过 collections.deque 模块,就是这个东西。他能高效在两头操作。
54+
55+
假如让你实现你能想起来嘛?
56+
似乎我们需要一个能 append() appendleft() popleft() pop() 都是 O(1) 的数据结构。
57+
58+
上边我们实现 队列的 LinkedList 可以吗?貌似就差一个 pop() 最后边的元素无法实现了。
59+
对,我们还有双端链表。它有这几个方法:
60+
61+
- append
62+
- appendleft
63+
- headnode()
64+
- tailnode()
65+
- remove(node) # O(1)
66+
67+
啊哈,似乎删除头尾都可以啦,而且都是 O(1) 的,完美。
68+
交给你一个艰巨的任务,实现双端队列 Deque() ADT。你可以参考前几章的任何代码,挑战一下这个任务,别忘记写单元测试呦
69+
70+
71+
72+
# 思考题
73+
74+
75+
76+

0 commit comments

Comments
 (0)