Skip to content

Commit 5e02a9c

Browse files
committed
线性查找和二分查找
1 parent 307fd4c commit 5e02a9c

File tree

4 files changed

+174
-0
lines changed

4 files changed

+174
-0
lines changed

docs/10_递归/recursion.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ def print_num_use_stack(n):
5050

5151
while not s.is_empty(): # 参数弹出
5252
print(s.pop())
53+
54+
5355
def hanoi_move(n, source, dest, intermediate):
5456
if n >= 1: # 递归出口,只剩一个盘子
5557
hanoi_move(n-1, source, intermediate, dest)
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# 查找
2+
3+
查找可以说是我们业务代码里用得最多的操作,比如我们经常需要在一个列表里找到我们需要的一个元素,然后返回它的位置。
4+
其实之前我们介绍的哈希表就是非常高效率的查找数据结构,很明显地它是用空间换时间。这一节介绍两个基本的基于线性结构的查找。
5+
6+
# 线性查找
7+
线性查找就是从头找到尾,直到符合条件了就返回。比如在一个 list 中找到一个等于 5 的元素并返回下标:
8+
9+
```py
10+
number_list = [0, 1, 2, 3, 4, 5, 6, 7]
11+
12+
13+
def linear_search(value, iterable):
14+
for index, val in enumerate(iterable):
15+
if val == value:
16+
return index
17+
return -1
18+
19+
20+
assert linear_search(5, number_list) == 5
21+
22+
```
23+
是不是 so easy。当然我们需要来一点花样,比如传一个谓词进去,你要知道,在 python 里一切皆对象,所以我们可以把函数当成一个参数传给另一个函数。
24+
25+
```py
26+
def linear_search_v2(predicate, iterable):
27+
for index, val in enumerate(iterable):
28+
if predicate(val):
29+
return index
30+
return -1
31+
32+
33+
assert linear_search_v2(lambda x: x == 5, number_list) == 5
34+
```
35+
36+
效果是一样的,但是传入一个谓词函数进去更灵活一些,比如我们可以找到第一个大于或者小于 5 的,从而控制函数的行为。
37+
还能玩出什么花样呢?前面我们刚学习了递归,能不能发挥自虐精神没事找事用递归来实现呢?
38+
39+
```py
40+
def linear_search_recusive(array, value):
41+
if len(array) == 0:
42+
return -1
43+
index = len(array)-1
44+
if array[index] == value:
45+
return index
46+
return linear_search_recusive(array[0:index], value)
47+
48+
49+
assert linear_search_recusive(number_list, 5) == 5
50+
assert linear_search_recusive(number_list, 8) == -1
51+
assert linear_search_recusive(number_list, 7) == 7
52+
assert linear_search_recusive(number_list, 0) == 0
53+
```
54+
这里的 assert 我多写了几个,包括正常情况、异常情况和边界值等,因为递归比较容易出错。注意这里的两个递归出口。
55+
当然业务代码里如果碰到这种问题我们肯定是选上边最直白的方式来实现,要不你的同事肯定想打你。
56+
57+
# 二分查找
58+
上一小节说的线性查找针对的是无序序列,假如一个序列已经有序了呢,我们还需要从头找到尾吗?当然不用,折半(二分)是一种经典思想。日常生活中还有哪些经典的二分思想呢?
59+
60+
- 猜数字游戏
61+
- 一尺之棰,日取其半,万世不竭
62+
- 有些民间股神,告诉一堆人某个股票会涨,告诉另一半人会跌。后来真涨了,慢慢又告诉信了他的一半人另一个股票会涨,另一半说会跌。就这样韭菜多了总有一些人信奉他为股神。。。
63+
64+
其实之前写过博客[《抱歉,我是开发,你居然让我写单测[视频]](https://zhuanlan.zhihu.com/p/35352024)讲过二分查找,当时主要是为了引入单元测试这个概念的,因为很多不正规的项目代码很糙,更别说写单测了。这里我就直接贴代码啦
65+
66+
```py
67+
def binary_search(sorted_array, val):
68+
if not sorted_array:
69+
return -1
70+
71+
beg = 0
72+
end = len(sorted_array) - 1
73+
74+
while beg <= end:
75+
mid = int((beg + end) / 2) # beg + (end-beg)/2, 为了屏蔽 python 2/3 差异我用了强转
76+
if sorted_array[mid] == val:
77+
return mid
78+
elif sorted_array[mid] > val:
79+
end = mid - 1
80+
else:
81+
beg = mid + 1
82+
return -1
83+
84+
85+
def test_binary_search():
86+
a = list(range(10))
87+
88+
# 正常值
89+
assert binary_search(a, 1) == 1
90+
assert binary_search(a, -1) == -1
91+
92+
# 异常值
93+
assert binary_search(None, 1) == -1
94+
95+
# 边界值
96+
assert binary_search(a, 0) == 0
97+
```
98+
99+
100+
# 思考题
101+
- 给你个挑战,用递归来实现本章的二分查找。你要十分注意边界条件,注意用单测测试呦,在你写代码的时候,可能会碰到边界问题或者无穷递归等。 如果你想不起来,可以看看本章的代码示例
102+
- 二分查找有一个变形,比如我们想在一个有序数组中插入一个值之后,数组仍保持有序,请你找出这个位置。(bisect 模块)
103+
104+
105+
# 延伸阅读
106+
这里没给链接,请善用 google 等搜索引擎和 Dash(mac) 等文档查询工具,在你学习代码的过程中你会非常频繁地使用它们。
107+
或者如果你有时间也可以跳转到这些模块的源码,看看它们的实现方式。标准库都是些高手写的,肯定能学到一些姿势。
108+
109+
- 阅读 python 文档关于二分的 bisect 模块。
110+
- 阅读 python 文档 itertools 相关模块和常见的几个函数 takewhile, dropwhile, from_iterable, count, tee 等用法
111+
- [每个程序员都应该会点形式化证明](https://zhuanlan.zhihu.com/p/35364999?group_id=967109293607129088)
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# -*- coding: utf-8 -*-
2+
3+
number_list = [0, 1, 2, 3, 4, 5, 6, 7]
4+
5+
6+
def linear_search(value, iterable):
7+
for index, val in enumerate(iterable):
8+
if val == value:
9+
return index
10+
return -1
11+
12+
13+
assert linear_search(5, number_list) == 5
14+
15+
16+
def linear_search_v2(predicate, iterable):
17+
for index, val in enumerate(iterable):
18+
if predicate(val):
19+
return index
20+
return -1
21+
22+
23+
assert linear_search_v2(lambda x: x == 5, number_list) == 5
24+
25+
26+
def linear_search_recusive(array, value):
27+
if len(array) == 0:
28+
return -1
29+
index = len(array)-1
30+
if array[index] == value:
31+
return index
32+
return linear_search_recusive(array[0:index], value)
33+
34+
35+
assert linear_search_recusive(number_list, 5) == 5
36+
assert linear_search_recusive(number_list, 8) == -1
37+
assert linear_search_recusive(number_list, 7) == 7
38+
assert linear_search_recusive(number_list, 0) == 0
39+
40+
41+
def binary_search_recursive(sorted_array, beg, end, val):
42+
if beg >= end:
43+
return -1
44+
mid = int((beg + end) / 2) # beg + (end-beg)/2
45+
if sorted_array[mid] == val:
46+
return mid
47+
elif sorted_array[mid] > val:
48+
return binary_search_recursive(sorted_array, beg, mid, val) # 注意我依然假设 beg, end 区间是左闭右开的
49+
else:
50+
return binary_search_recursive(sorted_array, mid+1, end, val)
51+
52+
53+
def test_binary_search_recursive():
54+
# 我们测试所有值和边界条件
55+
a = list(range(10))
56+
for i in a:
57+
assert binary_search_recursive(a, 0, len(a), i) == i
58+
59+
assert binary_search_recursive(a, 0, len(a), -1) == -1
60+
assert binary_search_recursive(a, 0, len(a), 10) == -1

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ pages:
2020
- 字典: '08_字典/dict.md'
2121
- 集合: '09_集合/set.md'
2222
- 递归: '10_递归/recursion.md'
23+
- 线性查找与二分查找: '11_线性查找与二分查找/search.md'

0 commit comments

Comments
 (0)