2021-02-27 15:01:45 +00:00
|
|
|
|
# 原创
|
|
|
|
|
: 算法图解笔记
|
|
|
|
|
|
|
|
|
|
# 算法图解笔记
|
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
#### 目录
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
### 算法简介
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
二分法查找,输入一个**有序列表**,返回元素位置或null。<br/> 一般而言,<strong>对于包含n个元素的列表,用二分法查找最多需要
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
l
|
|
|
|
|
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
o
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
g
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2
|
|
|
|
|
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
n
|
|
|
|
|
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
log_2 n
|
|
|
|
|
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
|
|
|
|
log2n</strong>
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
def binary_search(list, item):
|
|
|
|
|
low = 0
|
|
|
|
|
high = len(list) - 1
|
|
|
|
|
|
|
|
|
|
while low <= high:
|
|
|
|
|
mid = (low + high) // 2
|
|
|
|
|
if list[mid] == item:
|
|
|
|
|
return mid
|
|
|
|
|
elif list[mid] > item:
|
|
|
|
|
high = mid - 1
|
|
|
|
|
else:
|
|
|
|
|
low = mid + 1
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
[binary_search.py](binary_search.py)
|
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
### 选择排序
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
|
|
|
|
由于数组的结构在内存中是连续的,添加新元素十分麻烦,链表的优势在于插入新元素;<br/> 数组的优势在于元素的随机访问。也就是说**数组和链表在插入和读取元素的时间复杂度刚好互补**。<br/> [selectSort](selectSort.py)
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
def findSmallest(arr):
|
|
|
|
|
smallest = arr[0]
|
|
|
|
|
smallest_index = 0
|
|
|
|
|
for i in range(1,len(arr)):
|
|
|
|
|
if arr[i] < smallest:
|
|
|
|
|
smallest = arr[i]
|
|
|
|
|
smallest_index = i
|
|
|
|
|
return smallest_index
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def selectSort(arr):
|
|
|
|
|
newArr = []
|
|
|
|
|
for i in xrange(len(arr)):
|
|
|
|
|
smallest = findSmallest(arr)
|
|
|
|
|
newArr.append(arr.pop(smallest))
|
|
|
|
|
return newArr
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
我的写法
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
def selectSort2(arr):
|
|
|
|
|
n = len(arr)
|
|
|
|
|
for i in range(n):
|
|
|
|
|
for j in range(i, n):
|
|
|
|
|
if arr[j] < arr[i]:
|
|
|
|
|
arr[i], arr[j] = arr[j], arr[i]
|
|
|
|
|
return arr
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
时间复杂度为
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
O
|
|
|
|
|
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
(
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
n
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2
|
|
|
|
|
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
)
|
|
|
|
|
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
O(n^2)
|
|
|
|
|
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
|
|
|
|
O(n2)
|
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
### 递归
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
|
|
|
|
>
|
|
|
|
|
递归函数要有基线条件和递归条件,否则会无限循环.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
看一个阶乘函数的实现:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
def fact(x):
|
|
|
|
|
if x == 1:return 1
|
|
|
|
|
else: return x*fact(x-1)
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
### 快速排序
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
|
|
|
|
快速排序是分而治之的策略[quickSort](quickSort.py)
|
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
#### 分而治之
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
def quickSort(arr):
|
|
|
|
|
if len(arr)<2:
|
|
|
|
|
return arr
|
|
|
|
|
else:
|
|
|
|
|
pivot = arr[0]
|
|
|
|
|
less = [i for i in arr[1:] if i <= pivot]
|
|
|
|
|
greater = [i for i in arr[1:] if i > pivot]
|
|
|
|
|
return quickSort(less)+[pivot]+quickSort(greater)
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
这么看来比严版快排好理解多了,严版是用两个指针和一个基准值将数组划分成两部分,可以说时间复杂度确实小了不少。
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
def quickSort2(arr, left, right):
|
|
|
|
|
if left>=right:
|
|
|
|
|
return arr
|
|
|
|
|
|
|
|
|
|
low = left
|
|
|
|
|
high = right
|
|
|
|
|
key = arr[left]
|
|
|
|
|
|
|
|
|
|
while left < right:
|
|
|
|
|
while left < right and arr[right] >= key:
|
|
|
|
|
right -= 1
|
|
|
|
|
arr[left] = arr[right]
|
|
|
|
|
while left < right and arr[left] <= key:
|
|
|
|
|
left += 1
|
|
|
|
|
arr[right] = arr[left]
|
|
|
|
|
|
|
|
|
|
arr[left] = key
|
|
|
|
|
|
|
|
|
|
quickSort2(arr, low, left-1)
|
|
|
|
|
quickSort2(arr, left+1, high)
|
|
|
|
|
return arr
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
### 散列表
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
|
|
|
|
散列函数将输入映射到数字。它必须是一致的;它将不同的输入映射到不同的数字。Python实现了散列表的实现——字典。<br/> 散列表是提供DNS解析这种功能的方式之一。
|
|
|
|
|
|
|
|
|
|
一个简单的投票:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
voted = {}
|
|
|
|
|
def check_voter(name):
|
|
|
|
|
if voted.get(name):
|
|
|
|
|
print "kick them out!"
|
|
|
|
|
else:
|
|
|
|
|
voted[name] = True
|
|
|
|
|
print "let them vote!"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
将散列表用作缓存,缓存是一种常用的加速方式,缓存的数据则存储在散列表中。
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
cache = {}
|
|
|
|
|
def get_page(url):
|
|
|
|
|
if cache.get(url):
|
|
|
|
|
return cache[url]
|
|
|
|
|
else:
|
|
|
|
|
data = get_data_from_server(url)
|
|
|
|
|
cache[url] = data
|
|
|
|
|
return data
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
冲突的解决方法:将两个键映射到同一个位置,就在这个位置存储一个链表。为避免冲突,需要好的散列函数和较低的填充因子。
|
|
|
|
|
|
|
|
|
|
填装因子=散列表包含的元素数/位置总数,一旦填充因子大于0.7,就开始调整撒列表长度。
|
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
在平均条件下,散列表的时间复杂度为
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
O
|
|
|
|
|
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
(
|
|
|
|
|
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
1
|
|
|
|
|
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
)
|
|
|
|
|
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
O(1)
|
|
|
|
|
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
O(1),在最糟糕的情况下,时间复杂度为
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
O
|
|
|
|
|
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
(
|
|
|
|
|
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
n
|
|
|
|
|
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
)
|
|
|
|
|
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
O(n)
|
|
|
|
|
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
|
|
|
|
O(n)
|
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
### 广度优先搜索
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
|
|
|
|
图由节点——边组成。可以通过散列表将节点映射到其所有邻居,散列表是无序的。
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
graph = {}
|
|
|
|
|
graph["you"] = ["alice", "bob", "claire"]
|
|
|
|
|
graph["alice"] = ["peggy", "anuj"]
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
创建一个队列,用于存储要检查的人,从队列中弹出一个人,检查他是否是芒果销售商,否的坏将这个人所有的另据加入队列。
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
from collections import deque
|
|
|
|
|
search_deque = deque()
|
|
|
|
|
search_deque += graph["you"]
|
|
|
|
|
|
|
|
|
|
def person_is_seller(name):
|
|
|
|
|
return name[-1] == "m"
|
|
|
|
|
|
|
|
|
|
while search_deque:
|
|
|
|
|
person = search_deque.popleft()
|
|
|
|
|
if person_is_seller(persson):
|
|
|
|
|
return True
|
|
|
|
|
else:
|
|
|
|
|
search_deque += graph[person]
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
但是这种方法有缺陷,检查会有重复。应该检查完一个人,就将其标记。
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
def search(name):
|
|
|
|
|
search_queue = deque()
|
|
|
|
|
search_queue += graph[name]
|
|
|
|
|
searched = []
|
|
|
|
|
while search_queue:
|
|
|
|
|
person = search_queue.popleft()
|
|
|
|
|
if not person in searched:
|
|
|
|
|
if person_is_seller(persson):
|
|
|
|
|
return True
|
|
|
|
|
else:
|
|
|
|
|
search_queue += graph[person]
|
|
|
|
|
searched.append(person)
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
时间复杂度为
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
O
|
|
|
|
|
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
(
|
|
|
|
|
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
V
|
|
|
|
|
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
+
|
|
|
|
|
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
E
|
|
|
|
|
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
)
|
|
|
|
|
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
O(V+E)
|
|
|
|
|
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
|
|
|
|
O(V+E)边数和人数
|
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
### 狄克斯特拉算法
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
def find_lowest_cost_node(costs):
|
|
|
|
|
lowest_cost = float("inf")
|
|
|
|
|
lowest_cost_node = None
|
|
|
|
|
for node in costs:
|
|
|
|
|
cost = costs[node]
|
|
|
|
|
if cost < lowest_cost and node not in processed:
|
|
|
|
|
lowest_cost = cost
|
|
|
|
|
lowest_cost_node = node
|
|
|
|
|
return lowest_cost_node
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
### 贪婪算法
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
|
|
|
|
就是每步都选择最优解,最终得到全局的最优解。
|
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
#### NP完全问题
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
2024-07-03 09:49:47 +00:00
|
|
|
|
### 动态规划
|
2021-02-27 15:01:45 +00:00
|
|
|
|
|
|
|
|
|
与上一章对应,贪婪算法得到的可能不是最优解,而动态规划可以得到最优解。动态规划先解决子问题,再逐步解决大问题。
|