Skip to content

Commit c4fd6d2

Browse files
committed
完成 队列和栈章节
1 parent dc2d395 commit c4fd6d2

File tree

7 files changed

+285
-15
lines changed

7 files changed

+285
-15
lines changed

4_队列/circular_queue.py renamed to 4_队列/array_queue.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ def __iter__(self):
2626
yield item
2727

2828

29+
class FullError(Exception):
30+
pass
31+
32+
2933
class ArrayQueue(object):
3034
def __init__(self, maxsize):
3135
self.maxsize = maxsize
@@ -34,23 +38,40 @@ def __init__(self, maxsize):
3438
self.tail = 0
3539

3640
def push(self, value):
37-
self.array[self.head % self.maxsize] = value
41+
if len(self) >= self.maxsize:
42+
raise FullError('queue full')
43+
self.array[self.head] = value
3844
self.head += 1
3945

4046
def pop(self):
4147
value = self.array[self.tail % self.maxsize]
4248
self.tail += 1
4349
return value
4450

51+
def __len__(self):
52+
return self.head-self.tail
53+
4554

4655
def test_queue():
56+
import pytest # pip install pytest
4757
size = 5
4858
q = ArrayQueue(size)
4959
for i in range(size):
5060
q.push(i)
5161

62+
with pytest.raises(FullError) as excinfo: # 我们来测试是否真的抛出了异常
63+
q.push(size)
64+
assert 'full' in str(excinfo.value)
65+
66+
assert len(q) == 5
67+
5268
assert q.pop() == 0
5369
assert q.pop() == 1
70+
71+
assert len(q) == 3
72+
5473
assert q.pop() == 2
5574
assert q.pop() == 3
5675
assert q.pop() == 4
76+
77+
assert len(q) == 0

4_队列/deque.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
# 留给读者练习
1+
# 留给读者练习,如果实在想不出,在第 5 章栈里 stack.py 会有实现

4_队列/queue_and_stack.md renamed to 4_队列/queue.md

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
生活中的数据结构:
88

99
- 队列。没错就是咱平常排队,第一个来的第一个走
10-
- 栈。好比在桶里头放盘子,先放的盘子放在了底下,后来的盘子放在上边。你要拿的时候,也是先拿最上边的。
1110

1211
本章我们详细讲讲常用的队列
1312

@@ -43,7 +42,21 @@
4342

4443
想象一下,队列就俩操作,进进出出,一进一出,pop 和 push 操作。
4544
似乎只要两个下标 head, tail 就可以了。 当我们 push 的时候赋值并且前移 head,pop 的时候前移 tail 就可以了。你可以在纸上
46-
模拟下试试。
45+
模拟下试试。列队的长度就是 head-pop,这个长度必须不能大于初始化的最大程度。
46+
47+
如果 head 先到了数组末尾咋办?重头来呗,只要我们保证 tail 不会超过 head 就行。
48+
49+
head = 0,1,2,3,4 ... 0,1,2,3,4 ...
50+
51+
重头再来,循环往复,仿佛一个轮回。。。。
52+
怎么重头来呢?看上边数组的规律你如果还想不起来用取模,估计小学数学是体育老师教的。
53+
54+
```py
55+
maxsize = 5
56+
for i in range(100):
57+
print(i % maxsize)
58+
```
59+
4760

4861
我们来实现一个空间有限的循环队列。ArrayQueue,它的实现很简单,但是缺点是需要预先知道队列的长度来分配内存。
4962

@@ -68,9 +81,6 @@
6881
交给你一个艰巨的任务,实现双端队列 Deque() ADT。你可以参考前几章的任何代码,挑战一下这个任务,别忘记写单元测试呦
6982

7083

71-
7284
# 思考题
73-
74-
75-
76-
85+
- 你能用 python 的 deque 来实现 queue ADT 吗?
86+
- 哪些经典算法里用到了队列呢?

4_队列/queue.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,4 +150,4 @@ def test_queue():
150150
import pytest # pip install pytest
151151
with pytest.raises(EmptyError) as excinfo: # 我们来测试是否真的抛出了异常
152152
q.pop() # 继续调用会抛出异常
153-
assert 'empty queue' == excinfo.value
153+
assert 'empty queue' == str(excinfo.value)

5_栈/stack.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#
2+
3+
栈这个词实际上在计算机科学里使用很多,除了数据结构外,还有内存里的栈区 (和堆对应),熟悉 C 系语言的话应该不会陌生。
4+
上一章我们讲到了先进先出 queue,其实用 python 的内置类型 collections.deque 或者我们自己实现的 LinkedList 来实现它都很简单。
5+
本章我们讲讲 后进先出的栈。
6+
7+
生活中的数据结构:
8+
- 栈。好比在桶里头放盘子,先放的盘子放在了底下,后来的盘子放在上边。你要拿的时候,也是先拿最上边的。
9+
10+
栈其实也很简单,因为基础操作就俩,一个 push 和一个 pop,咦,咋和队列一样的?
11+
确实方法名字一样,但是得到的结果可是不同的。
12+
13+
14+
# 栈 ADT
15+
16+
上一章我介绍了我们怎样选取恰到的数据结构来实现新的 ADT?你能想到这里我们应该使用之前提到的哪个数据结构来实现吗?
17+
你的大脑可能开始高(gui)速(su)旋转了,上几章学过的 array, list, deque, LinkedList, CircularDoubleLinkedList, queue
18+
等在大脑里呼啸而过,这个时候可能已经一脸愁容了,到底该选啥?
19+
20+
还用问嘛,当然是时间复杂度最小的啦,大部分情况下空间都是够用的。
21+
其实你会发现栈比队列还简单,因为它只在顶上操作(想象装着盘子的桶),如果有一种数据结构能方便在尾部增减元素不就满足需求了吗。
22+
这个时候如果你忘记了,可以翻翻前几章,看看哪个数据结构符合要求。
23+
24+
想一下,似乎 CircularDoubleLinkedList 循环双端队列是满足的,因为增删最后一个元素都是 O(1)。
25+
不过看了下示例代码,似乎没有 pop() 方法,对,因为我已经把实现 deque 作为思考题了。😂
26+
如果之前你没写出来也没关系,这里我们会再实现它。
27+
28+
29+
视频里我们将借助 CircularDoubleLinkedList 实现 双端队列 Deque ,并且用 Deque 实现 Stack。
30+
31+
32+
# Stack over flow 什么鬼?
33+
嗯,stackoverflow 不是一个程序员问答网站吗?没错。
34+
函数的临时变量是存储在栈区的,如果你不幸写了一个没有出口的递归函数,就会这个错。不信你试试:
35+
36+
37+
```py
38+
def infinite_fib(n):
39+
return infinite_fib(n-1) + infinite_fib(n-2)
40+
infinite_fib(10)
41+
```
42+
43+
一大段输出之后就会出现异常: RecursionError: maximum recursion depth exceeded。
44+
后边会讲到递归,递归是初学者比较难理解的概念,在树的遍历等地方还会看到它。
45+
46+
47+
# 思考题
48+
- 上一章我们用数组实现了队列,其实也能用数组来实现 栈,你能自己用数组来实现一个栈的 ADT 吗?
49+
- 这里我们自己实现了 Deque,你能用 python 内置的 collections.deque 实现栈吗?有轮子能直接用的话看起来就简单多了
50+
- 哪些经典算法里使用到了栈呢?

5_栈/stack.py

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
# -*- coding: utf-8 -*-
2+
3+
# NOTE: 这里拷贝的 double_link_list.py 里的代码
4+
5+
6+
class Node(object):
7+
8+
def __init__(self, value=None, prev=None, next=None):
9+
self.value, self.prev, self.next = value, prev, next
10+
11+
12+
class CircularDoubleLinkedList(object):
13+
"""循环双端链表 ADT
14+
多了个循环其实就是把 root 的 prev 指向 tail 节点,串起来
15+
"""
16+
17+
def __init__(self, maxsize=None):
18+
self.maxsize = maxsize
19+
node = Node()
20+
node.next, node.prev = node, node
21+
self.root = node
22+
self.length = 0
23+
24+
def __len__(self):
25+
return self.length
26+
27+
def headnode(self):
28+
return self.root.next
29+
30+
def tailnode(self):
31+
return self.root.prev
32+
33+
def append(self, value): # O(1), 你发现一般不用 for 循环的就是 O(1),有限个步骤
34+
if self.maxsize is not None and len(self) > self.maxsize:
35+
raise Exception('LinkedList is Full')
36+
node = Node(value=value)
37+
tailnode = self.tailnode() or self.root
38+
39+
tailnode.next = node
40+
node.prev = tailnode
41+
node.next = self.root
42+
self.root.prev = node
43+
self.length += 1
44+
45+
def appendleft(self, value):
46+
if self.maxsize is not None and len(self) > self.maxsize:
47+
raise Exception('LinkedList is Full')
48+
node = Node(value=value)
49+
if self.root.next is self.root: # empty
50+
node.next = self.root
51+
node.prev = self.root
52+
self.root.next = node
53+
self.root.prev = node
54+
else:
55+
node.prev = self.root
56+
headnode = self.root.next
57+
node.next = headnode
58+
headnode.prev = node
59+
self.root.next = node
60+
self.length += 1
61+
62+
def remove(self, node): # O(1),传入node 而不是 value 我们就能实现 O(1) 删除
63+
"""remove
64+
:param node # 在 lru_cache 里实际上根据key 保存了整个node:
65+
"""
66+
if node is self.root:
67+
return
68+
else: #
69+
node.prev.next = node.next
70+
node.next.prev = node.prev
71+
self.length -= 1
72+
return node
73+
74+
def iter_node(self):
75+
if self.root.next is self.root:
76+
return
77+
curnode = self.root.next
78+
while curnode.next is not self.root:
79+
yield curnode
80+
curnode = curnode.next
81+
yield curnode
82+
83+
def __iter__(self):
84+
for node in self.iter_node():
85+
yield node.value
86+
87+
def iter_node_reverse(self):
88+
"""相比单链表独有的反序遍历"""
89+
if self.root.prev is self.root:
90+
return
91+
curnode = self.root.prev
92+
while curnode.prev is not self.root:
93+
yield curnode
94+
curnode = curnode.prev
95+
yield curnode
96+
97+
98+
############################################################
99+
# 分割线,下边是本章 内容实现
100+
############################################################
101+
102+
103+
class Deque(CircularDoubleLinkedList): # 注意这里我们用到了继承,嗯,貌似我说过不会用啥 OOP 特性的,抱歉
104+
105+
def pop(self):
106+
"""删除尾节点"""
107+
if len(self) == 0:
108+
raise Exception('empty')
109+
tailnode = self.tailnode()
110+
value = tailnode.value
111+
self.remove(tailnode)
112+
return value
113+
114+
def popleft(self):
115+
if len(self) == 0:
116+
raise Exception('empty')
117+
headnode = self.headnode()
118+
value = headnode.value
119+
self.remove(headnode)
120+
return value
121+
122+
123+
def test_deque():
124+
dq = Deque()
125+
dq.append(1)
126+
127+
dq.append(2)
128+
assert list(dq) == [1, 2]
129+
130+
dq.appendleft(0)
131+
assert list(dq) == [0, 1, 2]
132+
133+
dq.pop()
134+
assert list(dq) == [0, 1]
135+
136+
dq.popleft()
137+
assert list(dq) == [1]
138+
139+
dq.pop()
140+
assert len(dq) == 0
141+
142+
143+
class Stack(object):
144+
def __init__(self):
145+
self.deque = Deque()
146+
147+
def push(self, value):
148+
self.deque.append(value)
149+
150+
def pop(self):
151+
return self.deque.pop()
152+
153+
154+
def test_stack():
155+
s = Stack()
156+
s.push(0)
157+
s.push(1)
158+
s.push(2)
159+
160+
assert s.pop() == 2
161+
assert s.pop() == 1
162+
assert s.pop() == 0
163+
164+
import pytest # pip install pytest
165+
with pytest.raises(Exception) as excinfo: # 我们来测试是否真的抛出了异常
166+
s.pop()
167+
assert 'empty' in str(excinfo.value)
168+
169+
170+
if __name__ == '__main__':
171+
test_stack()

README.md

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@
55
笔者尝试录制视频教程帮助 Python 初学者掌握常用算法和数据结构,提升开发技能。
66
本教程是付费教程,因为笔者录制的过程中除了购买软件、手写板等硬件之外,业余需要花费很多时间和精力来录制视频、查资料、编写课件和代码,希望大家体谅。
77

8+
## 痛点
9+
- 讲 Python 数据结构和算法的资料很少
10+
- 很多自学 Python 的工程师对基础不够重视,面试也发现很多数据结构和算法不过关
11+
- 缺少工程应用场景下的讲解,很多讲算法的资料太『教科书化』
12+
813
## 作者简介
914
目前就职于[知乎](https://www.zhihu.com/people/pegasus-wang/activities),从实习期间接触 Python 起一直从事 Python 网站后端开发,有一定 Python 的使用和实践经验。
1015

@@ -31,9 +36,9 @@
3136
- 课程简介之笨方法学算法
3237
- 抽象数据类型 ADT,面向对象编程
3338
- 数组和列表
34-
- 链表
35-
- 高级链表。双链表,循环双端链表
36-
- 队列和栈,双端队列
39+
- 链表,高级链表。双链表,循环双端链表
40+
- 队列,双端队列,循环双端队列
41+
-
3742
- 算法分析,时间复杂度 大O 表示法
3843
- 哈希表,散列冲突
3944
- 字典和集合
@@ -88,8 +93,16 @@ Python 抽象程度比较高, 我们能用更少的代码来实现功能,同
8893

8994
[抱歉,我是开发,你居然让我写单测[视频]](https://zhuanlan.zhihu.com/p/35352024)
9095

91-
- 视频演示更加直观
92-
- 演示代码实现思路
96+
97+
## 课程特点
98+
99+
- 讲义循序渐进,结合自己的学习和使用经验讲解。github 上实时更新
100+
- 视频演示更加直观易懂
101+
- 演示代码实现思路,现场编写
102+
- 偏向工程应用和代码实现。代码直接可以用。
103+
- 良好的工程实践:[编码之前碎碎念(工程实践)](http://python-web-guide.readthedocs.io/zh/latest/codingstyle/codingstyle.html)。这是很多看了几本书没有太多实践经验就敢讲课的培训班老师教不了的。
104+
- 每个实现都会有单测来验证,培养良好的工程习惯
105+
- 结合 cpython 底层实现讲解(比如list 内存分配策略等)
93106

94107
## 资料
95108

@@ -109,6 +122,7 @@ Python 抽象程度比较高, 我们能用更少的代码来实现功能,同
109122
- 使用场景,什么时候用
110123
- 自己尝试实现,如果抛开视频自己写起来有困难可以多看几次视频,一定要自己手动实现。很多面试可能会让手写
111124
- 每章讲义后边都会有我设计的几个小问题,最好能够回答上来
125+
- 最好按照顺序循序渐进,每章都会有铺垫和联系
112126

113127
## 课程目标
114128
掌握基本的算法和数据结构原理,能独立使用 Python 语言实现,能在日常开发中灵活选用数据结构。
@@ -122,3 +136,7 @@ Python 抽象程度比较高, 我们能用更少的代码来实现功能,同
122136
- Vscode
123137
- Vim
124138

139+
140+
## 勘误
141+
142+
如果你觉得哪里讲错了,可以在github 上提 issue 讨论,我会修改内容并重新录制视频。

0 commit comments

Comments
 (0)