# 原创 : 算法图解笔记 # 算法图解笔记 ### 目录 ## 算法简介 二分法查找,输入一个**有序列表**,返回元素位置或null。
一般而言,对于包含n个元素的列表,用二分法查找最多需要 l o g 2 n log_2 n log2​n ``` 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) ## 选择排序 由于数组的结构在内存中是连续的,添加新元素十分麻烦,链表的优势在于插入新元素;
数组的优势在于元素的随机访问。也就是说**数组和链表在插入和读取元素的时间复杂度刚好互补**。
[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 ``` 时间复杂度为 O ( n 2 ) O(n^2) O(n2) ## 递归 > 递归函数要有基线条件和递归条件,否则会无限循环. 看一个阶乘函数的实现: ``` def fact(x): if x == 1:return 1 else: return x*fact(x-1) ``` ## 快速排序 快速排序是分而治之的策略[quickSort](quickSort.py) ### 分而治之 ``` 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 ``` ## 散列表 散列函数将输入映射到数字。它必须是一致的;它将不同的输入映射到不同的数字。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完全问题 ## 动态规划 与上一章对应,贪婪算法得到的可能不是最优解,而动态规划可以得到最优解。动态规划先解决子问题,再逐步解决大问题。