《算法图解》_Aditya Bhargava
这本书应该是我目前完完整整自己看完的第一本算法书,虽然里面的知识说的比较浅,但是对于我这种萌新来说可读性强,理解度高。所以,十分不错啦!我从淘宝上买的电子书,在ipad上阅读,十分方便~
今天想把我在里面学习到的知识点梳理一下,存有哪些疑惑写下来,以后可以再看看,再思考一下~
如果你大致浏览了一下觉得很easy~说明你很棒棒!至少比我6,哈哈!祝你进一步学习愉快。
第一章 算法介绍
主要学习内容:
1、二分查找法-----“折半”查找
def find_lowest(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:
high = mid - 1 # 加一减一是因为mid是已经检查过的
else:
low = mid + 1
return None
2、时间复杂度(大O表示法)
常见的几种时间复杂度
,对数时间(二分查找法)
,线性时间(简单查找)
,(快速排序)
,指数时间(选择排序)
,阶乘时间(旅行商问题解决方案)
困难点:
不太会自己得出算法的时间复杂度。
第二章 选择排序
主要学习内容:
1、两种基本的数据结构----数组与链表
数组:便于查找,不便于插入、删除
链表:不变于查找,便于插入,删除
数组 | 链表 | |
读取 | O(1) | O(n) |
插入 | O(n) | O(1) |
删除 | O(n) | O(1) |
2、选择排序
遍历一次数组,找到其中最大(最小)的值,将其放入数组的“开头”(头指针在变化)。
需要的时间复杂度为或
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 arrSort(arr):
newArr = []
for i in range(len(arr)):
smallest = findSmallest(arr)
newArr.append(arr.pop(smallest)) # 将该数组中下标为smallest的元素弹出,并添加到新的数组中
return newArr
第三章 递归
主要学习内容:
1、递归思想
使用递归,首先要考虑如何设定问题的基线条件与递归条件。基线条件是控制递归过程在何时结束,递归条件是控制递归 如何进行。
2、递归与for循环的选择
递归只能让解决方案更加清晰,便于理解,并没有性能上的优势。
3、调用栈
栈-----先进后出
每次递归过程中产生的变量,以栈的结构存储
栈虽方便,但是存储详尽的信息可能占用大量的内存。
困难点:
自己在编辑递归函数时,基线条件不会选择(在练习CART算法时,哭了(┬_┬))
第四章 快速排序
主要知识点:
主要学习内容:
1、“分而治之” 解决问题思想
对于一个大问题,分成子问题来一个一个解决!
2、快速排序算法
快速排序的特点是:数字的比较和交换次数少,在许多情况下可以高速的进行排序。
设定基线条件:如果数组为空或者值包含一个元素
设定递归条件:取数组中的一个元素(可以任意选取)
将数组中的元素以选取的元素作为分界线,大于的为一组,小于的为一组,每个组再不断递归以上做法。
快速排序的性能告诉依赖于你选择的基准值!平均复杂度为,最糟复杂度为
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) + quicksort(greater)
第五章 散列表
1、散列表的实现
python中散列表就是字典类别, 将输入映射到数值,输入为“键”,输出为“值”
2、散列表的应用
①查找
②防止重复(在python中使用dict.get()来检查字典 中是否存在某“键”,存在则返回其值,不存在返回None)
③用作缓存-----将数据记住,不再重复计算(比如服务器将主页存储起来,在需要是直接发送给用户;用户访问的主页不再用 于缓存的散列表中,则再交给服务器处理)
3、冲突
在对“键”存储时如果被分配到了同一个位置,可生成一个链表。但会有个问题要考虑,可能所有的数据都存储在散列表的第 一个位置,散列表的其他位置都是空的,而链表却很长。并不利于查找。
解决方案:需要一个较低的填装因子、优秀的散列函数!
-> 填装因子: 散列表包含的元素个数 / 位置总数。填装因子越小,说明散列表中空的位置越多
-> 散列函数: SHA函数(待研究)
4、性能
散列表(平均情况) | 散列表(最糟情况) | 数组 | 链表 | |
查找 | O(1) | O(n) | O(1) | O(n) |
插入 | O(1) | O(n) | O(n) | O(1) |
查询 | O(1) | O(n) | O(n) | O(n) |
第六章 广度优先搜索(Breadth-first search,BFS)
主要知识点:
1、有向图与无向图
边(edge)有单指向的数据结构图,为有向图。A指向B,B是A的邻居,但是A不是B的邻居!
无向图的边没有方向,也可以这样想,两个节点之间有两条方向相反的边。
2、拓扑排序
任务A依赖于任务B,在列表中任务A就必须排在任务B的后面。
3、队列
两种操作:入队、出队
特点:先进先出(First In First Out, FIFO)
4、广度算法算法-----查找最短路径,其中数据的存储结构为队列,运行时间为O(V+E),V为节点的个数,E为边的个数
步骤:
①将当前节点的所有邻居放入队列
②若队列不为空,检查队列最左边的元素
③若元素为查询目标,返回该元素;否,将该元素的邻居节点添加到队列中,同时将该元素标记已检查
④重复这个过程,知道队列为空
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'] = [] # ‘thom’、‘jonny’,‘peggy’没有邻居节点
def person_is_seller(name): # 检查人的姓名是否以m结尾,是就是芒果销售商
return name[-1] == 'm'
from collections import deque
def findSeller(name):
search_queue = deque()
search_queue += graph['you']
searched = [] # 用于标记已经检查过的人
while search_queue: # 只要队列不为空
person = search_queue.popleft() # 从队列中弹出最左边的一个元素
if person not in searched:
if person_is_seller(person):
print(person + 'is a seller!')
return True
else:
search_queue += graph[person] # 将当前节点的邻居放入队列中待检查
searched.append(person)
return False
findSeller('you')
第七章 狄科斯特拉算法
主要知识点:
1、加权图
给边添加了权重
2、狄克斯特拉算法-----寻找成本最低路径
步骤:
①找到“最便宜”的节点,即“到达成本”最低的节点
②更新该节点的邻居的开销
③重复,直到所有节点都更新完毕
④计算最终路径
狄科斯特拉算法需要三个散列表-----关系图、开销、父节点
graph = {} # 存储数据关系
costs = {} # 存储开销
partents = {} # 存储父节点
graph['start'] = {}
graph['a'] = {}
graph['b'] = {}
graph['inf'] = {} # 每个节点再设定为字典结构
graph['start']['a'] = 6
graph['start']['b'] = 2
graph['a']['inf'] = 1
graph['b']['a'] = 3
graph['b']['inf'] = 5
costs['a'] = 6
costs['b'] = 2
costs['inf'] = float('inf') # 不知道的节点设为无穷大
parents['a'] = 'start'
parents['b'] = 'start'
parents['inf'] = None
processed = [] # 用于存储已经处理过的几点
# 找到成本最低节点
def find_lowest_cost_node(costs):
lowest_node = None
lowest_cost = float('inf')
for node in costs.keys():
cost = costs[node]
if cost < lowest_cost and node not in processed:
lowest_cost = cost
lowest_node = node
return node
node = find_lowest_cost_node(costs)
while node is not None: # 如果到达终节点返回了空,结束
cost = costs[node] # 得到到达当前节点的开销
neighbors = graph[node] # 找出当前节点的所有邻居
for n in neighbors.keys(): # 对邻居遍历
new_cost = cost + neighbors[n] # 获得到达当前节点的开销+到达其每个邻居的成本的新的开销
if new_cost < costs[n]: # 如果新的开销小于之前该到达该邻居的开销
costs[n] = new_cost # 更新该邻居的开销为最小开销
parents[n] = node # 更新该邻居的父节点
processed.append(node)
node = find_lowest_cost_node(costs)
狄科斯特拉算法不适用具有负权重的数据结构图(使用贝尔曼-福德算法)
不足处:
自己写算法的流程还不是很熟练,整体思路只是大致了解。可能逻辑思维比较差。
第八章 贪婪算法
主要知识点:
1、贪婪策略-----寻找局部最优解(近似算法)
states_needed = set(['mt','wa','or','id','nv','ut','ca','az']) # 所有要考虑的州
stations = {} # 覆盖的站台
stations['one'] = set(['id','nv','ut']) # 每个站台所播放的州
stations['two'] = set(['wa','id','mt'])
stations['three'] = set(['or','nv','ca'])
stations['four'] = set(['nv','ut'])
stations['five'] = set(['ca','az'])
final_stations = set() # 最终可以包含多少个站台
while states_needed_cover:
best_station = None
states_covered = set()
for station, states in stations.items():
covered = states_needed & states #已经被覆盖的州为需要被覆盖的州与当前站台覆盖的州的交集
if len(covered) > len(states_covered): #如果当前站台可以覆盖的州大于已经考虑可覆盖的州的个数
best_station = station # 当前站台为最好的站台
states_covered = covered # 当前可覆盖的州
states_needed_cover -= states_covered # 留下还没有被覆盖的州
final_stations.add(best_station) # 添加需要的站台
2 、NP完全问题
①涉及“所有组合”的问题通常是NP完全问题
②不可将问题分成小问题,必须考虑各种可能的情况是NP完全问题
③问题涉及序列且难以解决是NP完全问题
④问题涉及集合且难以解决是NP完全问题
困难点:
不太会区分问题是否为NP完全问题,会想当然。在书中的例题我都想错了。
贪婪算法告诉我:完美是优秀的敌人!哈哈,所以我不要完美了。
第九章 动态规划
我觉得动态规划的思想是不断改变问题,先抓一个问题,再添上另一个问题,不断叠加,而不是把大问题划分为小问题。
主要学习内容:
1、动态规划思想
① 我觉得动态规划的思想是不断改变问题,先抓一个问题,再添上另一个问题,不断叠加,而不是把大问题划分为小问题。
② 并且动态规划要求每个子问题都是离散的,就是不依赖其他子问题。
③ 动态规划基于网格,在表格中对比,叠加。
④动态规划可以在给定约束条件下找到最优解。
2、最长公共子串
比如:‘hish’与‘fish’的这两个单词,最长公共子串是‘ish’’
H | I | S | H | |
F | 0 | 0 | 0 | 0 |
I | 0 | 1 | 0 | 0 |
S | 0 | 0 | 2 | 0 |
H | 0 | 0 | 0 | 3 |
if word1[i] == word2[j]:
excel[i][j] = excel[i-1][j-1] + 1
else:
excel[i][j] = 0
3、最长公共序列
(A) | F | O | S | H |
F | 1 | 0 | 0 | 0 |
I | 0 | 0 | 0 | 0 |
S | 0 | 0 | 1 | 0 |
H | 0 | 0 | 0 | 2 |
(B) | F | O | S | H |
F | 1 | 1 | 1 | 1 |
I | 1 | 1 | 1 | 1 |
S | 1 | 1 | 2 | 2 |
H | 1 | 1 | 2 | 3 |
(C) | F | O | S | H |
F | 1 | 0 | 0 | 0 |
O | 0 | 2 | 0 | 0 |
R | 0 | 0 | 0 | 0 |
T | 0 | 0 | 0 | 0 |
(D) | F | O | S | H |
F | 1 | 1 | 1 | 1 |
O | 1 | 2 | 2 | 2 |
R | 1 | 2 | 2 | 2 |
T | 1 | 2 | 2 | 2 |
A,C为最长公共子串方法,B,D为最长公共序列方法。从B,D可知“fosh”与‘fish’更为相似
if word1[i] == word2[j]:
excel[i][j] = excel[i-1][j-1] + 1
else:
excel[i][j] = max(excel[i-1][j], excel[i][j-1]) # 对比当前单元格上方与左方的值的大小
不足之处:
书中的知识看是看懂了,但是在实际运用中还有很多要弥补的地方
第十章 k最近邻算法
主要学习内容:
大致介绍了一下K近邻的思想。K近邻可以用于回归与分类。关键在于对于对于数据的特征提取(具有代表性,可分辨性),并且用于回归分类的距离算法的使用也对结果产生影响。
第十一章 接下来如何做
介绍了这本书中还没有介绍的10中算法以及它们的用途。
1、树:代表“二叉树”,便于查找,输入和删除操作的速度也很快。
2、反向索引:主要用于网页搜索引擎,有一个单词映射到存在该单词的所有链接
3、傅里叶变化:绝妙且优雅。可以通过“表象”看“本质”(给一杯水,告诉你成分)
4、并行算法:多个内核并行处理海量数据
5、MapReduce:特殊的并行算法,分布式算法。基于两个简单的理念:映射(map)函数【将一个数组转换为另一个数组】和并归(reduce)函数【将一个数组转换为一个元素】
6、布隆过滤器和HyperLogLog:(这一块不太懂。。。。)
布隆过滤器是一种概率型数据结构,它提供的答案有可能不正确,在优点在于所占的存储空间很少。
HyperLogLog类似于布隆过滤器
7、SHA算法:安全散列算法。给定一个字符串,SHA返回其散列值。可用于比较两个文件是否相同、检查密码
8、局部敏感的散列算法:SHA算法给出的值,即使“键”有细微的差别,也千差万别,而Simhash算法只有两“键”不同之处有细微的不同。该方法可以用于比较两个字符串的相似程度~
9、Diffie-Hellman密钥交换:对信息进行交换,具有两个密钥--公钥和私钥。可以这样理解,公钥就是我正大光明的告诉你我有一个秘密文件要给某人并且给该文件添加了密码,而某人有这个密码,也就是私钥。
10、线性规划:给定约束条件下最大限度地改善指定的指标(不是很理解,目前还没有遇到涉及到这个知识的实战项目)。
O V E R!