Skip to content

Commit d0ef5df

Browse files
author
wangningning
committed
Merge branch 'master' of github.com:PegasusWang/python_data_structures_and_algorithms
2 parents 4272e98 + 72f6cb3 commit d0ef5df

14 files changed

+390
-17
lines changed

README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
## 链接
99

1010
[网上阅读《Python 算法与数据结构教程 》](http://ningning.today/python_data_structures_and_algorithms/)
11+
1112
[github 链接](https://github.com/PegasusWang/python_data_structures_and_algorithms)
13+
1214
[readthedoc 电子书下载](http://python-data-structures-and-algorithms.readthedocs.io/zh/latest/)
1315

1416
## 痛点
@@ -124,7 +126,7 @@ Python 抽象程度比较高, 我们能用更少的代码来实现功能,同
124126
## 资料
125127

126128
- 视频。包含所有讲解视频
127-
- 代码示例。所有代码我会放到 github 上
129+
- 代码示例。所有代码我会放到 github 上。注意每一章目录里都有 py 文件,在电子书里看不到。clone 下代码找到对应目录里的 python 文件即是每章涉及到的代码。
128130
- markdown 讲义,包含视频内容的提要等内容
129131
- 延伸阅读。我会附上一些阅读资料方便想深入学习的同学
130132

@@ -171,9 +173,12 @@ Python 抽象程度比较高, 我们能用更少的代码来实现功能,同
171173

172174
安装依赖:
173175
```sh
174-
pip install mkdocs # 制作电子书
176+
pip install mkdocs # 制作电子书, http://markdown-docs-zh.readthedocs.io/zh_CN/latest/
175177
# https://stackoverflow.com/questions/27882261/mkdocs-and-mathjax/31874157
176178
pip install https://github.com/mitya57/python-markdown-math/archive/master.zip
179+
180+
# 或者直接
181+
pip install -r requirements.txt
177182
```
178183

179184
编写并查看:
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# 基本排序算法
2+
从本章开始讲常见的基于比较的排序算法,先讲三个简单的但是时间复杂度却不太理想的排序算法,包括冒泡排序、选择排序和插入排序。
3+
4+
5+
# 冒泡排序
6+
bubble sort 可以说是最简单的一种排序算法了,它的思想如下。对一个数组进行 n 轮迭代,每次比较相邻两个元素,
7+
如果相邻的元素前者大于后者,就交换它们。因为直接在元素上操作而不是返回新的数组,所以是一个 inplace 的操作。
8+
这里冒泡的意思其实就是每一轮冒泡一个最大的元素就会通过不断比较和交换相邻元素使它转移到最右边。
9+
10+
你可以想象假如有 10 个小盆友从左到右站成一排,个头不等。老师想让他们按照个头从低到高站好,于是他开始喊口号。
11+
每喊一次,从第一个小盆友开始,相邻的小朋友如果身高不是正序就会两两调换,就这样第一轮个头最高的排到了最右边。(冒泡到最右边)
12+
第二轮依次这么来,从第一个小朋友开始两两交换,这样次高的小盆友又排到了倒数第二个位置。依次类推。
13+
14+
15+
我们在视频里手动模拟下它的过程。
16+
17+
18+
```py
19+
import random
20+
21+
22+
def bubble_sort(seq): # O(n^2), n(n-1)/2 = 1/2(n^2 + n)
23+
n = len(seq)
24+
for i in range(n-1):
25+
print(seq) # 我打印出来让你看清楚每一轮最高、次高、次次高...的小朋友会冒泡到右边
26+
for j in range(n-1-i): # 这里之所以 n-1 还需要 减去 i 是因为每一轮冒泡最大的元素都会冒泡到最后,无需再比较
27+
if seq[j] > seq[j+1]:
28+
seq[j], seq[j+1] = seq[j+1], seq[j]
29+
print(seq)
30+
31+
32+
def test_bubble_sort():
33+
seq = list(range(10)) # 注意 python3 返回迭代器,所以我都用 list 强转了,python2 range 返回的就是 list
34+
random.shuffle(seq) # shuffle inplace 操作,打乱数组
35+
bubble_sort(seq)
36+
assert seq == sorted(seq) # 注意呦,内置的 sorted 就不是 inplace 的,它返回一个新的数组,不影响传入的参数
37+
38+
""" 我打印出来让你看到每次从最高到次高的小盆友就这么排好序了,因为是随机数,你第一个没有排序的数组应该和我的不一样
39+
[3, 4, 5, 0, 9, 1, 7, 8, 6, 2]
40+
[3, 4, 0, 5, 1, 7, 8, 6, 2, 9]
41+
[3, 0, 4, 1, 5, 7, 6, 2, 8, 9]
42+
[0, 3, 1, 4, 5, 6, 2, 7, 8, 9]
43+
[0, 1, 3, 4, 5, 2, 6, 7, 8, 9]
44+
[0, 1, 3, 4, 2, 5, 6, 7, 8, 9]
45+
[0, 1, 3, 2, 4, 5, 6, 7, 8, 9]
46+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
47+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
48+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
49+
"""
50+
```
51+
52+
53+
54+
# 选择排序
55+
刚才看到冒泡是每轮迭代中,如果相邻的两个元素前者大于后者了就交换两个相邻元素(假设正序排序)。其实还有一种思路就是,
56+
每次我们找到最小的元素插入迭代的起始位置,这样每个位置从它自己的位置开始它就是最小的了,一圈下来数组就有序了。
57+
选择可以理解为 一个 0 到 n-1 的迭代,每次向后查找选择一个最小的元素。
58+
59+
同样小盆友又来啦,这次我们从第一个开始,从头到尾找一个个头最小的小盆友,然后把它和第一个小盆友交换。
60+
然后从第二个小盆友开始采取同样的策略,这样一圈下来小盆友就有序了。
61+
62+
```py
63+
def select_sort(seq):
64+
n = len(seq)
65+
for i in range(n-1):
66+
min_idx = i # 我们假设当前下标的元素是最小的
67+
for j in range(i+1, n): # 从 i 的后边开始找到最小的元素,得到它的下标
68+
if seq[j] < seq[min_idx]:
69+
min_idx = j # 一个 j 循环下来之后就找到了最小的元素它的下标
70+
if min_idx != i: # swap
71+
seq[i], seq[min_idx] = seq[min_idx], seq[i]
72+
73+
74+
def test_select_sort():
75+
seq = list(range(10))
76+
random.shuffle(seq)
77+
select_sort(seq)
78+
assert seq == sorted(seq)
79+
80+
"""
81+
[4, 7, 5, 3, 6, 0, 2, 9, 8, 1]
82+
[0, 7, 5, 3, 6, 4, 2, 9, 8, 1]
83+
[0, 1, 5, 3, 6, 4, 2, 9, 8, 7]
84+
[0, 1, 2, 3, 6, 4, 5, 9, 8, 7]
85+
[0, 1, 2, 3, 6, 4, 5, 9, 8, 7]
86+
[0, 1, 2, 3, 4, 6, 5, 9, 8, 7]
87+
[0, 1, 2, 3, 4, 5, 6, 9, 8, 7]
88+
[0, 1, 2, 3, 4, 5, 6, 9, 8, 7]
89+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
90+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
91+
92+
"""
93+
```
94+
95+
96+
# 插入排序
97+
插入排序很多教科书都是用扑克牌的例子讲的,想象你手里有一些扑克牌,它们顺序是散乱的,现在需要你把它们整理成有序的,你会怎么做呢?
98+
首先拿最顶上的一张,然后拿第二张,第二张点数大,你就把第二张放在第一张的下边,否则放在第一张上边。
99+
当你拿第三张的时候,你同样会找到适合它大小的位置插入进去。
100+
101+
换成小朋友一样,第一个小盆友只有一个人我们假设是有序的,然后第二个小盆友会跟第一个比,如果第一个高就交换位置。
102+
接下来第三个小盆友从第二个位置开始比较,如果没第二个高就交换位置,然后没第一个高也交换位置,保持前边三个小盆友身高有序就好。
103+
依次类推,等到最后一个小盆友也转移到合适的位置,整个队列就是有序的了。
104+
105+
插入排序就是这个道理, 每次挑选下一个元素插入已经排序的数组中,初始时已排序数组只有一个元素。我们就直接上代码吧。
106+
107+
108+
```py
109+
def insertion_sort(seq):
110+
""" 每次挑选下一个元素插入已经排序的数组中,初始时已排序数组只有一个元素"""
111+
n = len(seq)
112+
print(seq)
113+
for i in range(1, n):
114+
value = seq[i] # 保存当前位置的值,因为转移的过程中它的位置可能被覆盖
115+
# 找到这个值的合适位置,使得前边的数组有序 [0,i] 有序
116+
pos = i
117+
while pos > 0 and value < seq[pos-1]:
118+
seq[pos] = seq[pos-1] # 如果前边的元素比它大,就让它一直前移
119+
pos -= 1
120+
seq[pos] = value # 找到了合适的位置赋值就好
121+
print(seq)
122+
123+
124+
""" 不断把新元素放到已经有序的数组中
125+
[1, 7, 3, 0, 9, 4, 8, 2, 6, 5]
126+
[1, 7, 3, 0, 9, 4, 8, 2, 6, 5]
127+
[1, 3, 7, 0, 9, 4, 8, 2, 6, 5]
128+
[0, 1, 3, 7, 9, 4, 8, 2, 6, 5]
129+
[0, 1, 3, 7, 9, 4, 8, 2, 6, 5]
130+
[0, 1, 3, 4, 7, 9, 8, 2, 6, 5]
131+
[0, 1, 3, 4, 7, 8, 9, 2, 6, 5]
132+
[0, 1, 2, 3, 4, 7, 8, 9, 6, 5]
133+
[0, 1, 2, 3, 4, 6, 7, 8, 9, 5]
134+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
135+
"""
136+
```
137+
138+
139+
# 思考题
140+
- 本章介绍的几个排序算法平均时间复杂度是多少?
141+
- 请你补充插入排序的单元测试代码
142+
143+
144+
# 延伸阅读
145+
- 《Data Structures and Algorithms in Python》第5章
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# -*- coding: utf-8 -*-
2+
3+
4+
import random
5+
6+
7+
def bubble_sort(seq): # O(n^2), n(n-1)/2 = 1/2(n^2 + n)
8+
n = len(seq)
9+
for i in range(n-1):
10+
print(seq) # 我打印出来让你看清楚每一轮最高、次高、次次高...的小朋友会冒泡到右边
11+
for j in range(n-1-i): # 这里之所以 n-1 还需要 减去 i 是因为每一轮冒泡最大的元素都会冒泡到最后,无需再比较
12+
if seq[j] > seq[j+1]:
13+
seq[j], seq[j+1] = seq[j+1], seq[j]
14+
print(seq)
15+
16+
17+
def test_bubble_sort():
18+
seq = list(range(10)) # 注意 python3 返回迭代器,所以我都用 list 强转了,python2 range 返回的就是 list
19+
random.shuffle(seq) # shuffle inplace 操作,打乱数组
20+
bubble_sort(seq)
21+
assert seq == sorted(seq) # 注意呦,内置的 sorted 就不是 inplace 的,它返回一个新的数组,不影响传入的参数
22+
23+
24+
def select_sort(seq):
25+
n = len(seq)
26+
for i in range(n-1):
27+
min_idx = i # 我们假设当前下标的元素是最小的
28+
for j in range(i+1, n): # 从 i 的后边开始找到最小的元素,得到它的下标
29+
if seq[j] < seq[min_idx]:
30+
min_idx = j # 一个 j 循环下来之后就找到了最小的元素它的下标
31+
if min_idx != i: # swap
32+
seq[i], seq[min_idx] = seq[min_idx], seq[i]
33+
34+
35+
def test_select_sort():
36+
seq = list(range(10))
37+
random.shuffle(seq)
38+
select_sort(seq)
39+
assert seq == sorted(seq)
40+
41+
42+
def insertion_sort(seq):
43+
""" 每次挑选下一个元素插入已经排序的数组中,初始时已排序数组只有一个元素"""
44+
n = len(seq)
45+
print(seq)
46+
for i in range(1, n):
47+
value = seq[i] # 保存当前位置的值,因为转移的过程中它的位置可能被覆盖
48+
# 找到这个值的合适位置,使得前边的数组有序 [0,i] 有序
49+
pos = i
50+
while pos > 0 and value < seq[pos-1]:
51+
seq[pos] = seq[pos-1] # 如果前边的元素比它大,就让它一直前移
52+
pos -= 1
53+
seq[pos] = value # 找到了合适的位置赋值就好
54+
print(seq)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# 高级排序算法
2+
3+
本章开始讲几个高级一些的排序算法,因为涉及到分治、递归和一些高级数据结构等,所以比前一章节的基本排序要稍微难理解一些。包括:
4+
5+
- [分治法与归并排序](./merge_sort.md)
6+
- [快速排序](./quick_sort.md)
7+
- [堆和堆排序](./heap_sort.md)

docs/13_高级排序算法/heap_sort.md

Whitespace-only changes.
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# 分治法 (Divide and Conquer)
2+
3+
很多有用的算法结构上是递归的,为了解决一个特定问题,算法一次或者多次递归调用其自身以解决若干子问题。
4+
这些算法典型地遵循分治法的思想:将原问题分解为几个规模较小但是类似于原问题的子问题,递归求解这些子问题,
5+
然后再合并这些问题的解来建立原问题的解。
6+
7+
分治法在每层递归时有三个步骤:
8+
9+
- **分解**原问题为若干子问题,这些子问题是原问题的规模最小的实例
10+
- **解决**这些子问题,递归地求解这些子问题。当子问题的规模足够小,就可以直接求解
11+
- **合并**这些子问题的解成原问题的解
12+
13+
14+
# 归并排序
15+
现在我们就来看下归并排序是是如何利用分治法解决问题的。
16+
17+
- **分解**:将待排序的 n 个元素分成各包含 n/2 个元素的子序列
18+
- **解决**:使用归并排序递归排序两个子序列
19+
- **合并**:合并两个已经排序的子序列以产生已排序的答案
20+
21+
考虑我们排序这个数组:[10,23,51,18,4,31,13,5] ,我们递归地将数组进行分解
22+
23+
![](./merge_sort_split.png)
24+
25+
当数组被完全分隔成只有单个元素的数组时,我们需要把它们合并回去,每次两两合并成一个有序的序列。
26+
27+
![](./merge_sort_merge.png)
28+
29+
用递归代码来描述这个问题:
30+
31+
```py
32+
def merge_sort(seq):
33+
if len(seq) <= 1: # 只有一个元素是递归出口
34+
return seq
35+
else:
36+
mid = int(len(seq)/2)
37+
left_half = merge_sort(seq[:mid])
38+
right_half = merge_sort(seq[mid:])
39+
40+
# 合并两个有序的数组
41+
new_seq = merge_sorted_list(left_half, right_half)
42+
return new_seq
43+
```
44+
45+
注意我们这里有一个函数没实现,就是如何合并两个有序数组 merge_sorted_list。其实你在纸上画一画,
46+
合并两个有序数组并不难实现。
47+
48+
![](./merge_sorted_array.png)
49+
50+
51+
```py
52+
def merge_sorted_list(sorted_a, sorted_b):
53+
""" 合并两个有序序列,返回一个新的有序序列
54+
55+
:param sorted_a:
56+
:param sorted_b:
57+
"""
58+
length_a, length_b = len(sorted_a), len(sorted_b)
59+
a = b = 0
60+
new_sorted_seq = list()
61+
62+
while a < length_a and b < length_b:
63+
if sorted_a[a] < sorted_b[b]:
64+
new_sorted_seq.append(sorted_a[a])
65+
a += 1
66+
else:
67+
new_sorted_seq.append(sorted_b[b])
68+
b += 1
69+
70+
# 最后别忘记把多余的都放到有序数组里
71+
while a < length_a:
72+
new_sorted_seq.append(sorted_a[a])
73+
a += 1
74+
75+
while b < length_b:
76+
new_sorted_seq.append(sorted_b[b])
77+
b += 1
78+
79+
return new_sorted_seq
80+
```
81+
82+
这样就实现了归并排序,并且你会发现它返回一个新的数组而不是修改原有数组。
83+
84+
85+
# 时间复杂度
86+
我们来简单看下它归并排序的时间复杂度,假设排序 n 个数字时间复杂度是 T(n),这里为了方便假设 n 是 2 的幂
87+
88+
\begin{align}
89+
T(n)= \begin{cases} c, & \text {if $n$ = 1} \\ 2T(n/2)+cn, & \text{if $n$ > 1} \end{cases}
90+
\end{align}
91+
92+
![](./merge_sort_recursion_tree.png)
93+
94+
总的代价是 $cnlg(n)+cn$ ,忽略常数项可以认为是 O(nlg(n))
95+
96+
# 思考题
97+
- 请你完成归并排序的单元测试
98+
- 这里实现的归并排序是 inplace 的吗?
99+
- 归并排序是稳定的吗?稳定指的是排序前后相同大小的数字依然保持相对顺序。
100+
101+
# 延伸阅读
102+
- 《算法导论》第 2 章和第 4 章,你需要了解下『主定理』,以及如何求解形如 $T(n)=aT(n/b) + f(n)$ 的递归式复杂度
103+
- 了解算法导论上递归式的三种求解方法:代入法,递归树法,主方法
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# -*- coding: utf-8 -*-
2+
3+
4+
def merge_sort(seq):
5+
if len(seq) <= 1: # 只有一个元素是递归出口
6+
return seq
7+
else:
8+
mid = int(len(seq)/2)
9+
left_half = merge_sort(seq[:mid])
10+
right_half = merge_sort(seq[mid:])
11+
12+
# 合并两个有序的数组
13+
new_seq = merge_sorted_list(left_half, right_half)
14+
return new_seq
15+
16+
17+
def merge_sorted_list(sorted_a, sorted_b):
18+
""" 合并两个有序序列,返回一个新的有序序列
19+
20+
:param sorted_a:
21+
:param sorted_b:
22+
"""
23+
length_a, length_b = len(sorted_a), len(sorted_b)
24+
a = b = 0
25+
new_sorted_seq = list()
26+
27+
while a < length_a and b < length_b:
28+
if sorted_a[a] < sorted_b[b]:
29+
new_sorted_seq.append(sorted_a[a])
30+
a += 1
31+
else:
32+
new_sorted_seq.append(sorted_b[b])
33+
b += 1
34+
35+
while a < length_a:
36+
new_sorted_seq.append(sorted_a[a])
37+
a += 1
38+
39+
while b < length_b:
40+
new_sorted_seq.append(sorted_b[b])
41+
b += 1
42+
43+
return new_sorted_seq
44+
45+
46+
def test_merge_sort():
47+
import random
48+
seq = list(range(10))
49+
random.shuffle(seq)
50+
assert merge_sort(seq) == sorted(seq)
51+
53.3 KB
Loading
Loading
Loading
Loading

docs/13_高级排序算法/quick_sort.md

Whitespace-only changes.

0 commit comments

Comments
 (0)