Skip to content

Commit 0355662

Browse files
author
wangningning
committed
Merge branch 'master' of github.com:PegasusWang/python_data_structures_and_algorithms
2 parents d0ef5df + d0a97e9 commit 0355662

File tree

10 files changed

+189
-7
lines changed

10 files changed

+189
-7
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,9 @@
5959
- 递归
6060
- 查找:线性查找和二分查找
6161
- 基本排序算法
62-
- 高级排序算法: 归并排序、堆排序、快排
62+
- 高级排序算法: 归并排序、快排
6363
- 树,二叉树
64+
- 堆与堆排序
6465
- 图,dfs 和 bfs
6566
- python 内置常用数据结构和算法的使用。list, dict, set, collections 模块,heapq 模块
6667
- 面试笔试常考算法

docs/12_基本排序算法/basic_sort.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ def bubble_sort(seq): # O(n^2), n(n-1)/2 = 1/2(n^2 + n)
1717
def test_bubble_sort():
1818
seq = list(range(10)) # 注意 python3 返回迭代器,所以我都用 list 强转了,python2 range 返回的就是 list
1919
random.shuffle(seq) # shuffle inplace 操作,打乱数组
20+
sorted_seq = sorted(seq) # 注意呦,内置的 sorted 就不是 inplace 的,它返回一个新的数组,不影响传入的参数
2021
bubble_sort(seq)
21-
assert seq == sorted(seq) # 注意呦,内置的 sorted 就不是 inplace 的,它返回一个新的数组,不影响传入的参数
22+
assert seq == sorted_seq
2223

2324

2425
def select_sort(seq):
@@ -35,8 +36,9 @@ def select_sort(seq):
3536
def test_select_sort():
3637
seq = list(range(10))
3738
random.shuffle(seq)
39+
sorted_seq = sorted(seq)
3840
select_sort(seq)
39-
assert seq == sorted(seq)
41+
assert seq == sorted_seq
4042

4143

4244
def insertion_sort(seq):

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,7 @@
44

55
- [分治法与归并排序](./merge_sort.md)
66
- [快速排序](./quick_sort.md)
7+
8+
在讲完二叉树之后,我们会看下它的应用:
9+
710
- [堆和堆排序](./heap_sort.md)

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

Whitespace-only changes.

docs/13_高级排序算法/merge_sort.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,3 @@ def test_merge_sort():
4848
seq = list(range(10))
4949
random.shuffle(seq)
5050
assert merge_sort(seq) == sorted(seq)
51-
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# 快速排序
2+
3+
快速排序名字可不是盖的,很多程序语言标准库实现的内置排序都有它的身影,我们就直奔主题吧。
4+
和归并排序一样,快排也是一种分而治之的策略。归并排序把数组递归成只有单个元素的数组,之后再不断两两
5+
合并,最后得到一个有序数组。这里的递归基本条件就是只包含一个元素的数组,当数组只包含一个元素的时候,我们可以认为它本来就是有序的(当然空数组也不用排序)。
6+
7+
快排的工作过程其实比较简单,三步走:
8+
9+
- 选择基准值 pivot
10+
11+
- 将数组分成两个子数组:小于基准值的元素和大于基准值的元素。这个过程称之为 partition
12+
13+
- 对这两个子数组进行快速排序。
14+
15+
根据这个想法我们可以快速写出快排的代码,简直就是在翻译上边的描述:
16+
17+
```py
18+
def quicksort(array):
19+
if len(array) < 2: # 递归出口,空数组或者只有一个元素的数组都是有序的
20+
return array
21+
else:
22+
pivot_index = 0 # 选择第一个元素作为主元 pivot
23+
pivot = array[pivot_index]
24+
less_part = [i for i in array[pivot_index+1:] if i <= pivot]
25+
great_part = [i for i in array[pivot_index+1:] if i > pivot]
26+
return quicksort(less_part) + [pivot] + quicksort(great_part)
27+
28+
29+
def test_quicksort():
30+
import random
31+
seq = list(range(10))
32+
random.shuffle(seq)
33+
assert quicksort(seq) == sorted(seq)
34+
```
35+
是不是很简单,下次面试官让你手写快排你再写不出来就有点过分啦。 当然这个实现有两个不好的地方:
36+
37+
- 第一是它需要额外的存储空间,我们想实现 inplace 原地排序。
38+
- 第二是它的 partion 操作每次都要两次遍历整个数组,我们想改善一下。
39+
40+
这里我们就来优化一下它,实现 inplace 排序并且改善下 partition 操作。新的代码大概如下:
41+
42+
```py
43+
def quicksort_inplace(array, beg, end): # 注意这里我们都用左闭右开区间,end 传入 len(array)
44+
if beg < end: # beg == end 的时候递归出口
45+
pivot = partition(array, beg, end)
46+
quicksort_inplace(array, beg, pivot)
47+
quicksort_inplace(array, pivot+1, end)
48+
```
49+
50+
能否实现只遍历一次数组就可以完成 partition 操作呢?实际上是可以的。我们设置首位俩个指针 left, right,两个指针不断向中间收拢。如果遇到 left 位置的元素大于 pivot 并且 right 指向的元素小于 pivot,我们就交换这俩元素,当 left > right 的时候推出就行了,这样实现了一次遍历就完成了 partition。如果你感觉懵逼,纸上画画就立马明白了。我们来撸代码实现:
51+
52+
```py
53+
def partition(array, beg, end):
54+
pivot_index = beg
55+
pivot = array[pivot_index]
56+
left = pivot_index + 1
57+
right = end - 1 # 开区间,最后一个元素位置是 end-1 [0, end-1] or [0: end),括号表示开区间
58+
59+
while True:
60+
# 从左边找到比 pivot 大的
61+
while left <= right and array[left] < pivot:
62+
left += 1
63+
64+
while right >= left and array[right] >= pivot:
65+
right -= 1
66+
67+
if left > right:
68+
break
69+
else:
70+
array[left], array[right] = array[right], array[left]
71+
72+
array[pivot_index], array[right] = array[right], array[pivot_index]
73+
return right # 新的 pivot 位置
74+
75+
76+
def test_partition():
77+
l = [4, 1, 2, 8]
78+
assert partition(l, 0, len(l)) == 2
79+
l = [1, 2, 3, 4]
80+
assert partition(l, 0, len(l)) == 0
81+
l = [4, 3, 2, 1]
82+
assert partition(l, 0, len(l))
83+
```
84+
85+
大功告成,新的快排就实现好了。
86+
87+
# 时间复杂度
88+
在比较理想的情况下,比如数组每次都被 pivot 均分,我们可以得到递归式:
89+
90+
T(n) = 2T(n/2) + n
91+
92+
上一节我们讲过通过递归树得到它的时间复杂度是 O(nlog(n))。即便是很坏的情况,比如 pivot 每次都把数组按照 1:9 划分,依然是 O(nlog(n)),感兴趣请阅读算法导论相关章节。
93+
94+
![](quicksort_worst.png)
95+
96+
97+
# 思考题
98+
- 请你补充 quicksort_inplace 的单元测试
99+
- 最坏的情况下快排的时间复杂度是多少?什么时候会发生这种情况?
100+
- 我们实现的快排是稳定的啵?
101+
- 选择基准值如果选不好就可能导致复杂度升高,算导中提到了一种『median of 3』策略,就是说选择 pivot 的时候
102+
从子数组中随机选三个元素,再取它的中位数,你能实现这个想法吗?这里我们的代码很简单地取了第一个元素作为 pivot
103+
- 利用快排中的 partition 操作,我们还能实现另一个算法,nth_element,快速查找一个无序数组中的第 k 大元素,请你尝试实现它并编写单测
104+
105+
106+
# 延伸阅读
107+
- 《算法导论》第 7 章
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# -*- coding: utf-8 -*-
2+
3+
4+
def quicksort(array):
5+
if len(array) < 2: # 递归出口,空数组或者只有一个元素的数组都是有序的
6+
return array
7+
else:
8+
pivot_index = 0 # 选择第一个元素作为主元 pivot
9+
pivot = array[pivot_index]
10+
less_part = [i for i in array[pivot_index+1:] if i <= pivot]
11+
great_part = [i for i in array[pivot_index+1:] if i > pivot]
12+
return quicksort(less_part) + [pivot] + quicksort(great_part)
13+
14+
15+
def test_quicksort():
16+
import random
17+
seq = list(range(10))
18+
random.shuffle(seq)
19+
assert quicksort(seq) == sorted(seq) # 用内置的sorted 『对拍』
20+
21+
22+
def quicksort_inplace(array, beg, end): # 注意这里我们都用左闭右开区间
23+
if beg < end: # beg == end 的时候递归出口
24+
pivot = partition(array, beg, end)
25+
quicksort_inplace(array, beg, pivot)
26+
quicksort_inplace(array, pivot+1, end)
27+
28+
29+
def partition(array, beg, end):
30+
pivot_index = beg
31+
pivot = array[pivot_index]
32+
left = pivot_index + 1
33+
right = end - 1 # 开区间,最后一个元素位置是 end-1 [0, end-1] or [0: end),括号表示开区间
34+
35+
while True:
36+
# 从左边找到比 pivot 大的
37+
while left <= right and array[left] < pivot:
38+
left += 1
39+
40+
while right >= left and array[right] >= pivot:
41+
right -= 1
42+
43+
if left > right:
44+
break
45+
else:
46+
array[left], array[right] = array[right], array[left]
47+
48+
array[pivot_index], array[right] = array[right], array[pivot_index]
49+
return right # 新的 pivot 位置
50+
51+
52+
def test_partition():
53+
l = [4, 1, 2, 8]
54+
assert partition(l, 0, len(l)) == 2
55+
l = [1, 2, 3, 4]
56+
assert partition(l, 0, len(l)) == 0
57+
l = [4, 3, 2, 1]
58+
assert partition(l, 0, len(l))
59+
60+
61+
def test_quicksort_inplace():
62+
import random
63+
seq = list(range(10))
64+
random.shuffle(seq)
65+
sorted_seq = sorted(seq)
66+
quicksort_inplace(seq, 0, len(seq))
67+
assert seq == sorted_seq
65.5 KB
Loading

docs/index.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,9 @@
5959
- 递归
6060
- 查找:线性查找和二分查找
6161
- 基本排序算法
62-
- 高级排序算法: 归并排序、堆排序、快排
62+
- 高级排序算法: 归并排序、快排
6363
- 树,二叉树
64+
- 堆与堆排序
6465
- 图,dfs 和 bfs
6566
- python 内置常用数据结构和算法的使用。list, dict, set, collections 模块,heapq 模块
6667
- 面试笔试常考算法
@@ -173,9 +174,12 @@ Python 抽象程度比较高, 我们能用更少的代码来实现功能,同
173174

174175
安装依赖:
175176
```sh
176-
pip install mkdocs # 制作电子书
177+
pip install mkdocs # 制作电子书, http://markdown-docs-zh.readthedocs.io/zh_CN/latest/
177178
# https://stackoverflow.com/questions/27882261/mkdocs-and-mathjax/31874157
178179
pip install https://github.com/mitya57/python-markdown-math/archive/master.zip
180+
181+
# 或者直接
182+
pip install -r requirements.txt
179183
```
180184

181185
编写并查看:

mkdocs.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,3 @@ pages:
2626
- 高级排序算法: '13_高级排序算法/advanced_sorting.md'
2727
- 分治法与归并排序: '13_高级排序算法/merge_sort.md'
2828
- 快速排序: '13_高级排序算法/quick_sort.md'
29-
- 堆与堆排序: '13_高级排序算法/heap_sort.md'

0 commit comments

Comments
 (0)