- 源码地址:https://github.com/egonSchiele/grokking_algorithms
- 参考书籍:https://book.douban.com/subject/26979890/
算法简介
二分法查找,输入一个有序列表,返回元素位置或null。
一般而言,对于包含n个元素的列表,用二分法查找最多需要
l
o
g
2
n
log_2 n
log2n
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
- 大O表示法是指最坏情况下的时间复杂度
- 简单查找: O ( l o g n ) O(logn) O(logn)
- 二分法查找: O ( l o g 2 n ) O(log_2 n) O(log2n)
- 快速排序: O ( n l o g n ) O(nlogn) O(nlogn)
- 选择排序: O ( n 2 ) O(n^2) O(n2)
选择排序
由于数组的结构在内存中是连续的,添加新元素十分麻烦,链表的优势在于插入新元素;
数组的优势在于元素的随机访问。也就是说数组和链表在插入和读取元素的时间复杂度刚好互补。
selectSort
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
时间复杂度为 O ( n 2 ) O(n^2) O(n2)
递归
递归函数要有基线条件和递归条件,否则会无限循环.
- 调用一个函数,计算机会首先为该函数调用分配一块内存,并且把该函数的变量存储在该内存中,所有函数调用都用到调用栈。
- 调用栈可能时间很长,这需要大量内存,递归函数都要用到调用栈。
看一个阶乘函数的实现:
def fact(x):
if x == 1:return 1
else: return x*fact(x-1)
快速排序
快速排序是分而治之的策略quickSort
分而治之
- 找出基线条件,这种条件尽可能简单;
- 不断将问题分解(或者说缩小规模),直到符合基线条件。
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
- 快速排序的时间复杂度在最坏的情况下为
O
(
n
2
)
O(n^2)
O(n2),平均情况下为
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn),最好情况也是
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn),
总体而言时间复杂度为 O ( n 2 ) O(n^2) O(n2).
散列表
散列函数将输入映射到数字。它必须是一致的;它将不同的输入映射到不同的数字。Python实现了散列表的实现——字典。
散列表是提供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,就开始调整撒列表长度。
在平均条件下,散列表的时间复杂度为 O ( 1 ) O(1) O(1),在最糟糕的情况下,时间复杂度为 O ( n ) O(n) O(n)
广度优先搜索
图由节点——边组成。可以通过散列表将节点映射到其所有邻居,散列表是无序的。
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
时间复杂度为 O ( V + E ) O(V+E) O(V+E)边数和人数
狄克斯特拉算法
- 加权图,边的数字称为权重。计算非加权图的最短路径,使用广度优先搜索;计算加权图的最短路径,使用狄克斯特拉算法。
狄克斯特拉算法只适用有向无环图。
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
贪婪算法
就是每步都选择最优解,最终得到全局的最优解。
NP完全问题
- 涉及n个城市的路线,一共有 n ! n! n!条可能的路线。
- 计算所有的解,并从中选取最小/最短的那个,这类问题就是NP完全问题。
- 涉及“所有组合”、序列问题难以解决、集合问题难以解决,它可能就是NP完全问题;
- 问题可以转化为集合覆盖问题或者旅行商问题,它就是NP完全问题。
动态规划
与上一章对应,贪婪算法得到的可能不是最优解,而动态规划可以得到最优解。动态规划先解决子问题,再逐步解决大问题。