大O表示法是一种特殊的表示法,指出了算法的速度有多快。
一些常见的大 O 运行时间:
内存模型:堆和栈的区别
from array import array
# typecode (must be b, B, u, h, H, i, I, l, L, q, Q, f or d) # 类型取得这个里面的一个
array('l')
array('l', [1, 2, 3, 4, 5])
print(array('l', [1, 2, 3, 4, 5]))
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 selectionSort(arr):
newArr = []
for i in range(len(arr)):
smallest = findSmallest(arr)
newArr.append(arr.pop(smallest))
return newArr
print selectionSort([5, 3, 6, 2, 10])
递归和迭代的区别:
需要了解的算法实现:
递归需要掌握的两点内容:将问题分成基线条件和递归条件这样就容易写出递归函数。
注意点:使用循环,程序的性能可能更高;如果使用递归,程序可能更容易理解。
每个递归函数都有两部分:基线条件( base case)和递归条件( recursive case) 。递归条件指的是函数调用自己,而基线条件则指的是函数不再调用自己,从而避免形成无限循环。
def countdown(i):
print i
if i <= 0: # 基线条件
return
else: # 基线条件
countdown(i-1)
递归函数调用栈举例:
def fact(x):
if x == 1:
return 1
else:
return x * fact(x-1)
fact(4)
要知道经典排序算法:原理复杂度实现代码:
十大经典排序算法(动图演示) ——– 简洁易懂还有图例
(1) 找出基线条件,这种条件必须尽可能简单。
(2) 不断将问题分解(或者说缩小规模),直到符合基线条件。
对排序算法来说,最简单的数组就是根本不需要排序的数组。可以通过这个确定基线条件:
因此, 基线条件为数组为空或只包含一个元素。
代码表示如下:
def quicksort(array):
if len(array) < 2:
return array
然后确定快速排序的递归条件就是不断的划分数据进行二分法分类:
def quicksort(array):
if len(array) < 2:
return array
else: #
pivot = array[0] # 递归条件,这个是每次选取数组的第一个值进行分组
less = [i for i in array[1:] if i <= pivot] # 通过函数式编程划分出比第一个小的
greater = [i for i in array[1:] if i > pivot] # 通过函数式编程划分出比第一个大的
return quicksort(less) + [pivot] + quicksort(greater) # 递归调用分组
print(quicksort([10, 5, 2, 3]))
算法 | 算法复杂度(平均) | 举例数组长度(10,100,1000) |
---|---|---|
二分查找 | O(log(N)) | 对应时间:(0.3s,0.6s,1s) |
简单查找(基于线性表的查找) | O(N) | 1s,10s,100s |
快速排序 | O(N*log(N)) | 3.3s, 66.4s, 996s |
选择排序 | O(N*N) | 10s,16.6min,27.7h |
旅行商问题算法 | O(N!) | 4.2d,2.9*10**149year,xxx年 |
了解散列表的两点内容:
散列函数“将输入映射到数字”。其查找复杂度为O(1)
散列函数准确地指出了价格的存储位置,遵循以下原则:
hash表实现原理介绍参考:
散列表的基本原理与实现 —对hash实现的全实现展示
从头到尾解析Hash表算法 —包含部分代码实现
深入理解数据结构之散列表、散列、散列函数 —hash实现在Java中的概览
应用:
给两个不同的键分配的位置相同。
最简单的办法如下:如果两个键映射到了同一个位置,就在这个位置存储一个链表。
问题:如果都是以A开头那散列表就会变成了一个链表了,只在第一个存储值:
除第一个位置外,整个散列表都是空的,而第一个位置包含一个很长的列表!
所以:散列函数很重要,好的散列函数很少导致冲突。
操作 | 散列表(平均) | 散列表(最差) | 数组 | 链表 |
---|---|---|---|---|
查找 | O(1) | O(n) | O(1) | O(n) |
插入 | O(1) | O(n) | O(n) | O(1) |
删除 | O(1) | O(n) | O(n) | O(1) |
1.在平均情况下,散列表的查找(获取给定索引处的值)速度与数组一样快,而插入和删除速度与链表一样快,因此它兼具两者的优点!但在最糟情况下,散列表的各种操作的速度都很慢。
2.在使用散列表时,避开最糟情况至关重要。
所以,散列表的良好状况有下面连点要求:SHA函数
较低的填装因子;
良好的散列函数。
一个不错的经验规则是:一旦填装因子大于0.7,就调整散列表的长度。
图算法——广度优先搜索(breadth-first search, BFS)
广度优先搜索应用:
图由节点和边组成,图模拟一组连接。例如,假设你与朋友玩牌,并要模拟谁欠谁钱,可像下面这样指出小明欠老王钱。
关系查找问题:只有按添加顺序查找时,才能实现这样的目的。有一个可实现这种目的的数据结构,那就是队列(queue)
队列的工作原理:队列是一种先进先出(First In First Out, FIFO)的数据结构,而栈是一种后进先出(Last In First Out, LIFO)的数据结构。
如果你将两个元素加入队列,先加入的元素将在后加入的元素之前出队。因此,你可使用队列来表示查找名单!
#通过散列和列表来实现有向图结构
graph = {}
graph["you"] = ["alice", "bob", "claire"] # 相当于 you-->alice,bob,claire这三个路径
graph["bob"] = ["anuj", "peggy"]
graph["alice"] = ["peggy"]
graph["claire"] = ["thom", "jonny"]
graph["anuj"] = []
graph["peggy"] = []
graph["thom"] = []
graph["jonny"] = []
Anuj、 Peggy、 Thom和Jonny都没有邻居,这是因为虽然有指向他们的箭头,但没有从他们出发指向其他人的箭头。这被称为有向图(directed graph) ,其中的关系是单向的。因此, Anuj是Bob的邻居,但Bob不是Anuj的邻居。 无向图(undirected graph)没有箭头,直接相连的节点互为邻居。
小提示:
python队列Queue —简单易懂的介绍队列
python3 deque(双向队列) —-简单介绍双向队列操作
队列(queue)原理 —简单队列原理
from collections import deque # 使用函数deque来创建一个双端队列。
search_queue = deque() # 创建一个队列
search_queue += graph["you"] # 将你的邻居都加入到这个搜索队列中
# 判断一个人是不是芒果销售商
def person_is_seller(name):
return name[-1] == 'm'
# 实现有向图查找算法
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
这个算法将不断执行,直到满足以下条件之一:
问题: Peggy既是Alice的朋友又是Bob的朋友,因此她将被加入队列两次:一次是在添加Alice的朋友时,另一次是在添加Bob的朋友时;这也可能 导致图的无限循环进行;
因此需要对已经查询过的图对象点进行标记;检查完一个人后,应将其标记为已检查,且不再检查他。
# 优化后的代码
#!usr/bin/env python
# -*- coding:utf-8 -*-
"""
@note: # 广度优先算法
"""
from collections import deque # 使用函数deque来创建一个双端队列。
# 1。通过散列和列表来实现有向图结构
graph = {}
graph["you"] = ["alice", "bob", "claire"] # 相当于 you-->alice,bob,claire这三个路径
graph["bob"] = ["anuj", "peggy"] # 相当于 bob-->anuj,peggy这三个路径的有向图
graph["alice"] = ["peggy"]
graph["claire"] = ["thom", "jonny"]
graph["anuj"] = []
graph["peggy"] = []
graph["thom"] = []
graph["jonny"] = []
# 判断一个人是不是芒果销售商
def person_is_seller(name):
return name[-1] == 'm'
# 实现有向图查找算法
def search(name):
search_queue = deque() # 创建一个队列
search_queue += graph[name] # 通过给定name开始进行有向图深度搜索
searched = [] # 这个列表用于记录检查过的人
while search_queue: # 只要队列不为空
person = search_queue.popleft() # 就取出其中的第一个人
if not person in searched: # 仅当这个人没检查过 才进行检查
if person_is_seller(person): # 检查这个人是否是芒果销售商
print(person + " is a mango seller!")
return True
else:
search_queue += graph[person] # 不是芒果销售商。将这个人的朋友都加入搜索队列
searched.append(person) # 将这个人标记为检查过
return False # 如果到达了这里,就说明队列中没人是芒果销售商
search("you")
将一个人添加到队列需要的时间是固定的,即为O(1),因此对每个人都这样做需要的总时间为O(人数)。所以,广度优先搜索的运行时间为O(人数 + 边数),这通常写作O(V + E),其中V为顶点(vertice)数, E为边数。
注意点:
如果任务A依赖于任务B,在列表中任务A就必须在任务B后面。这被称为拓扑排序,使用它可根据图创建一个有序列表。
你需要按加入顺序检查搜索列表中的人,否则找到的就不是最短路径,因此搜索列表必须是队列。
对于检查过的人,务必不要再去检查,否则可能导致无限循环。
Dijkstra’s algorithm
找出加权图中前往X的最短路径。只能求得从一个单个起始点,到单个结束点的最短路径;
参考链接:Dijkstra’s Algorithm - Computerphile – B站上讲解清晰的视频,一共两个视频,各有优点
(1) 找出“最便宜”的节点,即可在最短时间内到达的节点。
(2) 更新该节点的邻居的开销,其含义将稍后介绍。
(3) 重复这个过程,直到对图中的每个节点都这样做了。
(4) 计算最终路径。
注意点: 不能将狄克斯特拉算法用于包含负权边的图。
对于负权边使用贝尔曼·福德算法( Bellman-Fordalgorithm)
权重距离:达到这个节点的权重值
父节点:通过哪个上一节点达到这个节点的
1.第一次从S(start,起点)开始
路径点 | S(start) | A | B | E(end) |
---|---|---|---|---|
权重距离 | 0 | 6 | 2 | ∞ |
父节点 | s | s | s | - |
1.第二次从B节点开始:因为从第一次选择可以看到,B权重距离最小。所以选择B作为第二次起始点。对于已经完成的不进行从新划分。
路径点 | S(start) | A | B | E(end) |
---|---|---|---|---|
权重距离 | 0 | 5 | 2 | 5 |
父节点 | s | B | s | B |
就这样依次查找更新。最终实现完成;
#! user/bin/env python
# -*- encoding:utf-8 -*-
"""
@time: 2018/07/03 23:01
@note:狄克斯特拉算法实现
"""
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 # 终点没有任何邻居
# 用一个散列表来存储每个节点的开销
infinity = float("inf")
costs = {}
costs["a"] = 6
costs["b"] = 2
costs["fin"] = infinity
# 一个存储父节点的散列表
parents = {}
parents["a"] = "start"
parents["b"] = "start"
parents["fin"] = None
processed = [] # 用于记录处理过的节点,因为对于同一个节点,不能处理多次
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
# 在未处理的节点中找出开销最小的节点
node = find_lowest_cost_node(costs)
while node is not None: # 这个while循环在所有节点都被处理过后结束
cost = costs[node]
neighbors = graph[node] # 这个地方需要判断如node为 fin 时结束
for n in neighbors.keys(): # 遍历当前节点的所有邻居
new_cost = cost + neighbors[n]
if costs[n] > new_cost:
# 如果经当前节点前往该邻居更近,就更新该邻居的开销
costs[n] = new_cost
# 将该邻居的父节点设置为当前节点
parents[n] = node
processed.append(node) # # 将当前节点标记为处理过
node = find_lowest_cost_node(costs) # 找出接下来要处理的节点,并循环
小总结:
广度优先搜索用于在非加权图中查找最短路径。
狄克斯特拉算法用于在加权图中查找最短路径。
仅当权重为正时狄克斯特拉算法才管用。
如果图中包含负权边,请使用贝尔曼·福德算法
最短路径指的并不一定是物理距离,也可能是让某种度量指标最小。
扩展知识:距离的定义建议看:距离定理:距离满足三角不等式
可以参考:《Generalized inverses: theory and computations》Guorong Wang, Yimin Wei.的27页找到
知乎:为什么距离(度量)要满足三角不等式?
根据欧式空间的性质以及(Cauchy—Schwarz不等式)证明
例子:假设你办了个广播节目,要让全美50个州的听众都收听得到。为此,你需要决定在哪些广播台播出。在每个广播台播出都需要支付费用,因此你力图在尽可能少的广播台播出。现有广播台名单如下:
广播合 | 覆盖州 |
---|---|
KONE | ID,NV,UT |
KTWO | WA,ID,MT |
KTHREE | PR.MV.CA |
KFOUR | NV,UT |
KFIVE | CA,AZ |
最好使用如下算法:
近似算法
贪婪算法可化解危机!使用下面的贪婪算法可得到非常接近的解。
(1) 选出这样一个广播台,即它覆盖了最多的未覆盖州。即便这个广播台覆盖了一些已覆盖
的州,也没有关系。
(2) 重复第一步,直到覆盖了所有的州。
这是一种近似算法(approximation algorithm) 。在获得精确解需要的时间太长时,可使用近
似算法。判断近似算法优劣的标准如下:
#!usr/bin/env python
# -*- coding:utf-8 -*-
"""
@time: 2018/07/04 12:35
@software:diagramalgorithm
@note: 贪婪算法实现
"""
states_needed = set(["mt", "wa", "or", "id", "nv", "ut","ca", "az"]) # 你传入一个数组,它被转换为集合
# 可供选择的广播台清单,使用散列表来表示
stations = {}
stations["kone"] = set(["id", "nv", "ut"])
stations["ktwo"] = set(["wa", "id", "mt"])
stations["kthree"] = set(["or", "nv", "ca"])
stations["kfour"] = set(["nv", "ut"])
stations["kfive"] = set(["ca", "az"])
# 存储最终选择的广播台
final_stations = set()
best_station = None
states_covered = set()
while states_needed:
for station, states_for_station in stations.items():
covered = states_needed & states_for_station
if len(covered) > len(states_covered): # 计算交集
best_station = station
states_covered = covered
final_stations.add(best_station)
states_needed -= states_covered
print(final_stations)
动态规划,这是一种解决棘手问题的方法,它将问题分成小问题,并先着手解决这些小问题。
对于背包问题,你先解决小背包(子背包)问题,再逐步解决原来的问题。
其实将背包的单位更小化,并把物品计量单位化;这样转化为一个计数问题。
实现上述算法的原理:使用这个公式来计算每个单元格的价值,最终的网格将与前一个网格相同。
假设你在杂货店行窃,可偷成袋的扁豆和大米,但如果整袋装不下,可打开包装,再将背包倒满。在这种情况下,不再是要么偷要么不偷,而是可偷商品的一部分。如何使用动态规划来处理这种情形呢?
&&&答案是没法处理。
小提示:动态规划功能强大,它能够解决子问题并使用这些答案来解决大问题。 但仅当每个子问题都是离散的,即不依赖于其他子问题时,动态规划才管用。
**每个单元格都是一个子问题,因此你应考虑如何将问题分成子问题,这有助于你找出网
格的坐标轴。**
1.第10章 KNN算法单独写
2.11章内容关注点在这小碎片一下
secure hash algorithm, SHA