《图解算法》摘要
平时知道优缺点,校招要懂算法实现;
算法简介
- 二分查找
def binary_search(list, item):
low = 0
high = len(list) - 1
while low < high:
mid = (low + high) / 2
guess = list[mid]
if guess == item:
return mid
elif guess < item:
low = mid + 1
else:
high = mid - 1
return None
- 大O表示法指出了算法的增速,即算法有多快,一些常见的大O运行时间:
- O(log(n)) ,也叫对数时间,包括二分查找;
- O(n) ,也叫线性时间,包括简单查找;
- O(n∗log(n)) ,包括快速排序;
- O(n2) ,包括选择排序
- O(n!) ,旅行商问题
选择排序
行为 | 数组 | 链表 |
---|---|---|
读取 | O(1) | O(n) |
插入 | O(n) | O(1) |
删除 | O(n) | O(1) |
* 数组用的很多,因为它支持随机访问;链表只能顺序访问
def selectionSort(alist):
for i in range(len(alist)-1, 0, -1):
maxone = 0
for j in range(1, i+1):
if alist[j] > alist[maxone]:
maxone = j
alist[maxone], alist[i] = alist[i], alist[maxone]
return alist
递归
- 每个递归函数都有两个部分:基线条件(base case)和递归条件(recursive case)。递归条件是函数调用自己,而基线条件势函数不再调用自己。
def countdown(i):
print i
if i <= 0:
return
else:
countdown(i-1)
- 栈:调用另一个函数时,当前函数暂停并处于未完成状态;
- 栈→递归 , 循环→队列 ;
快速排序
def quick_sort(a):
if len(a) < 2:
return a
target = a[0]
small = [i for i in a[1:] if i <= target]
large = [i for i in a[1:] if i > target]
return quick_sort(small) + [target] + quick_sort(large)
散列表
- 散列函数准确地指出了价格的存储位置,原因如下:
- 散列函数总是将同样的输入映射到相同的索引;
- 散列函数将不同的输入映射到不同的索引;
- 散列函数知道数据有多大,只返回有效的索引;
- 在你将学习的复杂数据结构中,散列表可能是最有用的,也被称为散列映射、映射、字典和关联数组;
- 散列表的实例:DNS解析(DNS resolution),缓存;
- 如果散列表存储的链表很长,散列表的速度将急剧下降。然而,如果使用的散列函数很好,这些链表就不会很长;
- 在平均情况下,散列表的查找(获取给定索引处的值)速度和数组一样快,而插入和删除速度和链表一样快,因此它兼具两者的优点;
- 填装因子=散列表包含的元素数位置总数 ,一旦填装因子大于0.7,就调整散列表的长度,将数据增长一倍;
图相关
广度优先搜索
- 广度优先搜索不仅查找从A到B的路径,而且找到的是最短路径;
- 图用于模拟不同东西是如何相连的;
from collections import deque
def search(name, graph):
# 创建一个队列
search_queue = deque()
search_queue += graph[name]
searched = []
while search_queue:
person = search_queue.popleft()
if person_is_seller(person):
print(person, 'is a mango seller!')
return True
else:
search_queue += graph[person]
return False
def person_is_seller(name):
return name[-1] == 'm'
def main():
# 关系图
graph = {}
graph['you'] = ['alice', 'bob', 'claire']
graph['bob'] = ['anuj', 'peggy']
graph['alice'] = ['peggy']
graph['claire'] = ['thom', 'jonny']
graph['anuj'] = []
graph['peggy'] = []
graph['thom'] = []
graph['jonny'] = []
search('you', graph)
if __name__ == '__main__':
main()
- 如果任务A依赖于任务B,在列表中任务A就必须在任务B后面,这被称为拓扑排序;
狄克斯特拉算法
- 要计算非加权图中的最短路径,可使用广度优先搜索。要计算加权图中的最短路径,可使用狄克斯特拉算法;
- 狄克斯特拉算法只适用于有向无环图;
def main():
# 有向无环图
graph = {}
graph['start'] = {}
graph['start']['a'] = 6
graph['start']['b'] = 2
graph['a'] = {}
graph['a']['fin'] = 1
graph['b'] = {}
graph['b']['a'] = 3
graph['b']['fin'] = 5
graph['fin'] = {}
# 开销表
infinity = float('inf')
costs = {'a': 6, 'b': 2, 'fin': infinity}
# 存储父节点
parents = {'a': 'start', 'b': 'start', 'fin': None}
# 记录处理过的节点
processed = []
node = find_lowest_cost_node(costs, processed)
while node is not None:
cost = costs[node]
neighbers = graph[node]
for n in neighbers.keys():
new_cost = cost + neighbers[n]
if costs[n] > new_cost:
costs[n] = new_cost
parents[n] = node
processed.append(node)
node = find_lowest_cost_node(costs, processed)
print(graph)
def find_lowest_cost_node(costs, processed):
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
if __name__ == '__main__':
main()
NP完全问题
贪婪算法(近似算法)
- 贪婪算法可能不会获得最优解,但非常接近。在有些情况下,完美是优秀的敌人,有时候,你只需找到一个能够大致解决问题的算法即可。判断贪婪算法优劣的标准如下:
- 速度有多快;
- 得到的近似解与最优解的接近程度;
- NP问题无处不在!如果能够判断出要解决的问题属于NP完全问题就好了,这样就不用去寻找完美的解决方案,而是使用近似算法即可
- NP问题的规律:
- 元素较少时算法的运行速度非常快,但随着元素数量的增加,速度变得非常慢;
- 涉及“所有组合”的问题通常是NP完全问题;
- 不能将问题分成小问题,必须考虑各种可能的情况。这可能是NP完全问题;
- 如果问题涉及序列(如旅行商问题中的城市序列)且难以解决,它可能就是NP完全问题;
- 如果问题涉及集合(如广播台集合)且难以解决,它可能就是NP完全问题;
- 如果问题可转换为集合覆盖问题或者旅行商问题,那它肯定是NP完全问题;
- 小结:
- 贪婪算法寻找局部最优解,企图以这种方式获得全局最优解;
- 对于NP完全问题,还没有找到快速解决方案;
- 面临NP完全问题时,最佳的做法是使用近似算法;
- 贪婪算法易于实现,运行速度快,是不错的近似算法;
动态规划
- 动态规划的行的排列顺序无关紧要;
- 小结:
- 需要在给定约束条件下优化某种指标时,动态规划很有用;
- 问题可分解为离散子问题时,可使用动态规划来解决;
- 每种动态规划解决方案都涉及网格;
- 单元格中的值通常就是你要优化的值;
- 每个单元格都是一个子问题,因此你需要考虑如何将问题分解为子问题;
- 没有放之四海皆准的动态规划解决方案公式;
- 示例公式: cell[i][j]=Max(cell[i−1][j],cell[i−1][j−当前商品重量])