一些基础的算法知识——《图解算法》

1.可使用图算法编写跟踪用户的AI系统;使用K最近邻算法编写推荐系统;

2.二分查找

1)一般而言,对于包含n个元素的列表,用二分查找最多需要\[{\log _{\rm{2}}}{\rm{n}}\]步,而简单查找最多需要n步(一般的log指的是\[{\log _{\rm{2}}}{\rm{n}}\]);

2)仅当列表是有序的时候,二分查找才管用

def binary_search(my_list,item)
    low=0
    hign=len(my_list)-1
    while(low<=high)
       mid=(low+high)/2 #位置
       guss=my_list[mid] #位置上的数
       if(guss>item)
            high=mid-1
       else
             low=mid+1
    return  None

list=[1,2,3,4,5]
print binary_search(list,4)

3)

算法的速度指的并非时间,而是操作数的增速。谈论算法的速度时,我们说的是随着输入的增加,其运行时间将以什么样的速度增加
算法的运行时间用大O表示法表示。大O表示法指的并非以秒为单位的速度,大O表示法说的是最糟的情形。 大O表示法让你能够比较操作数(就是实际操作的次数),它指出了算法运行时间的增速

4)常见的大O时间

 O(log n),也叫对数时间,这样的算法包括二分查找。
 O(n),也叫线性时间,这样的算法包括简单查找。
 O(n * log n),这样的算法包括第4章将介绍的快速排序——一种速度较快的排序算法。
 O(n2),这样的算法包括第2章将介绍的选择排序——一种速度较慢的排序算法。
 O(n!),这样的算法包括接下来将介绍的旅行商问题的解决方案——一种非常慢的算法。

eg:假设你要绘制一个包含16格的网格,且有5种不同的算法可供选择,这些算法的运行时间如上所示。如果你选择第一种算法,绘制该网格所需的操作数将为4(log 16 = 4)。假设你每秒可执行10次操作(倒过来,得到0.1s/操作),那么绘制该网格需要0.4秒。

5.数组和链表的优缺点

1)链表中的元素可存储在内存的任何地方,链表的优势在插入元素方面
2)使用数组意味着所有待办事项在内存中都是相连的(紧靠在一起的);

      只要有足够的内存空间,就能为链表分配内存(内存的位置不用紧密相连)。
3)需要随机地读取元素时,数组的效率很高,因为可迅速找到数组的任何元素。在链表中,元素并非靠在一起的,你无法迅速计算出第五个元素的内存地址,而必须先访问第一个元素以获取第二个元素的地址,再访问第二个元素以获取第三个元素的地址,以此类推,直到访问第五个元素。

4)如果你要删除元素呢?链表也是更好的选择

常见的数组和链表操作的运行时间,如下:

读取,即查找。

注:仅当能够立即访问要删除的元素时,删除操作的运行时间才为O(1)。通常我们都记录了链表的第一个元素和最后一个元素,因此删除这些元素时运行时间为O(1)。

5)两种访问方式: 随机访问和顺序访问;

顺序访问意味着从第一个元素开始逐个地读取元素;随机访问意味着可直接跳到第十个元素
数组用得很多,因为它支持随机访问,链表只能顺序访问

6.元素的位置称为索引,eg:元素20位于索引1处
7.选择排序

1)时间复杂度

2)why算法这么写?

选择排序就是选择什么进去排序,下面的是选择索引进行排序

核心的思想是:对于长度是len的数组,判断len次数组中最小元素的索引

eg1:编写一个用于查找数组中最小元素的函数

##找出数组中最小元素的index
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 selection(arr):
    new_arr=[]
    for i in range(len(arr))
        smallesti=findsmallest(arr)
        new_arr.append(arr.pop(smallesti))
return new_arr

print selection([2,3,4,5])
        

8.递归

递归和循环的区别:如果使用循环,程序的性能可能更高;如果使用递归,程序可能更容易理解

递归函数都有两部分:基线条件(base case)和递归条件(recursive case)

eg:编写一个倒计时函数:3...2...1

这个函数将像预期的那样运行,如下所示。

9.调用栈

每当你调用函数时,计算机都像这样将函数调用涉及的所有变量的值存储到内存中;

计算机也为这个函数调用分配一块内存。这个栈用于存储多个函数的变量,被称为调用栈

10.递归调用栈

##阶乘的递归函数
def function(x):
    if x==1:
       return x
    else:
    x=x*function(x-1)

11.快速排序
解决递归式问题的方法和思路:分而治之(divide and conguer)

D&C的工作原理:

1)找出简单的基准条件,这种条件必须尽可能简单

2)确定如何缩小问题的规模,使其符合基准条件

对排序算法来说,最简单的数组什么样呢?就是根本不需要排序的数组。
因此,编写涉及数组的递归函数时,基线条件通常是数组为空或只包含一个元素。

注:C语言标准库中的函数qsort实现的就是快速排序,快排也使用了D&C

快排的基本步骤:

(1) 选择基准值,注:将任何元素用作基准值都可行

(2) 将数组分成两个子数组(也可称之为两个区paititioning):小于基准值的元素和大于基准值的元素

(3) 对这两个子数组进行快速排序,对划分得到的两个子数组递归地进行快速排序

证明算法行之有效的方式(高中数学知识的简单应用):

##快排的代码
def quick(arr):
        ##基准条件
    if len(arr)<=1:
       return arr
    else:
       ##递归
    base=arr[0]
    small=[i for i in arr[1:] if arr[i]<=base]
    big=[i for i in arr[1:] if arr[i]>base]
    return quicksort(small)+[base]+quicksmall(big)

12. 常见算法的运行时间

1.可使用图算法编写跟踪用户的AI系统;使用K最近邻算法编写推荐系统;

2.二分查找

1)一般而言,对于包含n个元素的列表,用二分查找最多需要\[{\log _{\rm{2}}}{\rm{n}}\]步,而简单查找最多需要n步(一般的log指的是\[{\log _{\rm{2}}}{\rm{n}}\]);

2)仅当列表是有序的时候,二分查找才管用

def binary_search(my_list,item)
    low=0
    hign=len(my_list)-1
    while(low<=high)
       mid=(low+high)/2 #位置
       guss=my_list[mid] #位置上的数
       if(guss>item)
            high=mid-1
       else
             low=mid+1
    return  None

list=[1,2,3,4,5]
print binary_search(list,4)

3)

算法的速度指的并非时间,而是操作数的增速。谈论算法的速度时,我们说的是随着输入的增加,其运行时间将以什么样的速度增加
算法的运行时间用大O表示法表示。大O表示法指的并非以秒为单位的速度,大O表示法说的是最糟的情形。 大O表示法让你能够比较操作数(就是实际操作的次数),它指出了算法运行时间的增速

4)常见的大O时间

 O(log n),也叫对数时间,这样的算法包括二分查找。
 O(n),也叫线性时间,这样的算法包括简单查找。
 O(n * log n),这样的算法包括第4章将介绍的快速排序——一种速度较快的排序算法。
 O(n2),这样的算法包括第2章将介绍的选择排序——一种速度较慢的排序算法。
 O(n!),这样的算法包括接下来将介绍的旅行商问题的解决方案——一种非常慢的算法。

eg:假设你要绘制一个包含16格的网格,且有5种不同的算法可供选择,这些算法的运行时间如上所示。如果你选择第一种算法,绘制该网格所需的操作数将为4(log 16 = 4)。假设你每秒可执行10次操作(倒过来,得到0.1s/操作),那么绘制该网格需要0.4秒。

5.数组和链表的优缺点

1)链表中的元素可存储在内存的任何地方,链表的优势在插入元素方面
2)使用数组意味着所有待办事项在内存中都是相连的(紧靠在一起的);

      只要有足够的内存空间,就能为链表分配内存(内存的位置不用紧密相连)。
3)需要随机地读取元素时,数组的效率很高,因为可迅速找到数组的任何元素。在链表中,元素并非靠在一起的,你无法迅速计算出第五个元素的内存地址,而必须先访问第一个元素以获取第二个元素的地址,再访问第二个元素以获取第三个元素的地址,以此类推,直到访问第五个元素。

4)如果你要删除元素呢?链表也是更好的选择

常见的数组和链表操作的运行时间,如下:

读取,即查找。

注:仅当能够立即访问要删除的元素时,删除操作的运行时间才为O(1)。通常我们都记录了链表的第一个元素和最后一个元素,因此删除这些元素时运行时间为O(1)。

5)两种访问方式: 随机访问和顺序访问;

顺序访问意味着从第一个元素开始逐个地读取元素;随机访问意味着可直接跳到第十个元素
数组用得很多,因为它支持随机访问,链表只能顺序访问

6.元素的位置称为索引,eg:元素20位于索引1处
7.选择排序

1)时间复杂度

2)why算法这么写?

选择排序就是选择什么进去排序,下面的是选择索引进行排序

核心的思想是:对于长度是len的数组,判断len次数组中最小元素的索引

eg1:编写一个用于查找数组中最小元素的函数

##找出数组中最小元素的index
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 selection(arr):
    new_arr=[]
    for i in range(len(arr))
        smallesti=findsmallest(arr)
        new_arr.append(arr.pop(smallesti))
return new_arr

print selection([2,3,4,5])
        

8.递归

递归和循环的区别:如果使用循环,程序的性能可能更高;如果使用递归,程序可能更容易理解

递归函数都有两部分:基线条件(base case)和递归条件(recursive case)

eg:编写一个倒计时函数:3...2...1

这个函数将像预期的那样运行,如下所示。

9.调用栈

每当你调用函数时,计算机都像这样将函数调用涉及的所有变量的值存储到内存中;

计算机也为这个函数调用分配一块内存。这个栈用于存储多个函数的变量,被称为调用栈

10.递归调用栈

##阶乘的递归函数
def function(x):
    if x==1:
       return x
    else:
    x=x*function(x-1)

11.快速排序
解决递归式问题的方法和思路:分而治之(divide and conguer)

D&C的工作原理:

1)找出简单的基准条件,这种条件必须尽可能简单

2)确定如何缩小问题的规模,使其符合基准条件

对排序算法来说,最简单的数组什么样呢?就是根本不需要排序的数组。
因此,编写涉及数组的递归函数时,基线条件通常是数组为空或只包含一个元素。

注:C语言标准库中的函数qsort实现的就是快速排序,快排也使用了D&C

快排的基本步骤:

(1) 选择基准值,注:将任何元素用作基准值都可行

(2) 将数组分成两个子数组(也可称之为两个区paititioning):小于基准值的元素和大于基准值的元素

(3) 对这两个子数组进行快速排序,对划分得到的两个子数组递归地进行快速排序

证明算法行之有效的方式(高中数学知识的简单应用):

##快排的代码
def quick(arr):
        ##基准条件
    if len(arr)<=1:
       return arr
    else:
       ##递归
    base=arr[0]
    small=[i for i in arr[1:] if arr[i]<=base]
    big=[i for i in arr[1:] if arr[i]>base]
    return quicksort(small)+[base]+quicksmall(big)

12. 常见算法的运行时间

二分查找:O(log n);

简单查找:O(n);

快速排序:平均情况下是:O(nlog n),最坏情况下是:O(n^2)

选择排序:O(n^2);

旅行商问题算法O(n!);

合并排序:O(nlog n);

13.散列函数

散列函数(hash table)是这样的函数,即无论你给它什么数据,它都还你一个数字;
散列函数“将输入映射到数字”。

你结合使用散列函数数组创建了一种被称为散列表(hash table)的数据结构

散列函数的要求:

1) 散列函数总是将同样的输入映射到相同的索引。

2)散列函数将不同的输入映射到不同的索引

3)散列函数知道数组有多大,只返回有效的索引

eg:Python提供的散列表实现为字典,散列表由键和值组成。
用处:

1)查找

DNS解析

2)防止重复

vote={}

def check(name):
    if vote.get(name):
        print "你已经投过票了"
     else:
        print "请投票"


>>> check_voter("tom")
请投票
>>> check_voter("mike")
你已经投过票了
>>> check_voter("mike")
你已经投过票了

3)散列表用作缓存,以免服务器再通过处理来生成它们,eg假设你访问网站facebook.com
 

cache={}

def get(url)
   if cache.get(url):
    return cache{url}#直接返回缓存中的数据
    else:
    data=get_data_from_server(url)
    cache{url}=data##将数据保存在缓存中
    return data

散列表的冲突:

假设:

不好,这个位置已经存储了苹果的价格!怎么办?这种情况被称为冲突(collision)

解决办法:

a)较低的填装因子

填装因子越低,发生冲突的可能性越小,
散列表的性能越高。一个不错的经验规则是:一旦填装因子大于0.7,就调整散列表的长度。
 

b)良好的散列函数

良好的散列函数让数组中的值呈均匀分布。

解决办法的eg:如果两个键映射到了同一个位置,就在这个位置存储一个链表
 

散列表的运行时间比较:

13.广度优先搜索(breadth-first search, BFS)

eg:一度关系指的是:圈以内的;圈以外的指的是二度关系;

一度和二度关系都需要查找,查找用的是队列这种数据结构

14。队列

队列是一种先进先出(First In First Out, FIFO)的数据结构,而栈是一种后进先出(Last InFirst Out, LIFO)的数据结构

注意:更新队列时,我使用术语“入队”和“出队”,但你也可能遇到术语“压入”和“弹出”。压入大致相当于入队,而弹出大致相当于出队

15.实现图

利用散列表,记住,散列表让你能够将键映射到值

eg:

代码是:

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"] = []

使用DFS来判断上述所构造的图中,是否有芒果商

注意:对于检查过的人,务必不要再去检查,否则可能导致无限循环
看上面的graph代码:Peggy既是Alice的朋友又是Bob的朋友,因此她将被加入队列两次:一次是在添加Alice的朋友时,另一次是在添加Bob的朋友时。因此,搜索队列将包含两个Peggy。如果你检查两次,就做了无用功
 

##首先,创建一个队列。在Python中,可使用函数deque来创建一个双端队列。
def search(name):
    search_q=deque()
    search_q+=graph[name]
    searched=[]##用于记录已经检查过的人
    while search_q:##只要队列不为空
          person=search_q.popleft()##取出其中的第一个人
          if not person in searched:##当此人未标记时才检查
               if person_is_seller(person): 
                    print "他是卖芒果的"
                    return True
               else
                    search_q+=graph[person]
                    searched.append[person]  ##将此人标记
                    return False

##判断一个人是不是芒果销售商
def person_is_seller(name):
    return name[-1] == 'm'    



seach ("you")          

其时间复杂度为:广度优先搜索的运行时间为O(人数 + 边数),这通常写作O(V + E),其中V为顶点(vertice)数, E为边数。

16.图的依赖关系以及拓扑排序,树

17.狄克斯特拉算法(Dijkstra’s algorithm)。

1)广度优先搜索BFS与它之间的区别

广度优先搜索,它找出的是段数最少的路径(第一个图);狄克斯特拉算法,找出最快的路径(第二个图)

2)如何使用狄克斯特拉算法

(1) 找出最便宜的节点,即可在最短时间内前往的节点。
(2) 对于该节点的邻居,检查是否有前往它们的更短路径,如果有,就更新其开销。
(3) 重复这个过程,直到对图中的每个节点都这样做了。
(4) 计算最终路径。
注:狄克斯特拉算法只适用于有向无环图(directed acyclicgraph, DAG)。

应用:短路径指的并不一定是物理距离,也可能是让某种度量指标最小

eg:
这个图中的节点是大家愿意拿出来交换的东西,边的权重是交换时需要额外加多少钱。拿海报换吉他需要额外加30美元,拿黑胶唱片换吉他需要额外加15美元。
3)负权边

如果有负权边,就不能使用狄克斯特拉算法;

在包含负权边的图中,要找出最短路径,可使用另一种算法——贝尔曼福德算法(Bellman-Fordalgorithm)
4)实现狄克斯特拉算法

要编写解决这个问题的代码,需要三个散列表

a)实现图

graph = {}
graph["start"] = ["a", "b"]
......

b)实现边的权重,graph["start"]是一个散列表

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"] = {}

>>> print graph["start"].keys()
["a", "b"]

c)节点的开销指的是从起点出发前往该节点需要多长时间

infinity = float("inf")
#创建开销表的代码如下:
infinity = float("inf")
costs = {}
costs["a"] = 6
costs["b"] = 2
costs["fin"] = infinity


#还需要一个存储父节点的散列表
parents = {}
parents["a"] = "start"
parents["b"] = "start"
parents["fin"] = None

d)你需要一个数组,用于记录处理过的节点,因为对于同一个节点,你不用处理多次

processed = []

e)算法的流程图

##算法代码
#在未处理的节点中找出开销最小的节点
node=find_lowest_cost_node(costs)
while node is not None:#这个while循环在所有节点都被处理过后结束
    cost = costs[node]
    neighbors = graph[node]
    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)找出接下来要处理的节点,并循环





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
        
        

18

1)贪婪算法

贪婪策略——一种非常简单的问题解决策略

用专业术语说,就是你每步都选择局部最优解
2)背包问题

eg:

若采取贪心:

(1) 盗窃可装入背包的最贵商品。
(2) 再盗窃还可装入背包的最贵商品,以此类推。

若采用背包的解法:

3)集合覆盖

eg:假设你办了个广播节目,要让全美50个州的听众都收听得到。为此,你需要决定在哪些广播台播出。在每个广播台播出都需要支付费用,因此你力图在尽可能少的广播台播出。

使用近似算法:贪婪算法

步骤:(1) 选出这样一个广播台,即它覆盖了最多的未覆盖州(2) 重复第一步,直到覆盖了所有的州

判断近似算法优劣的标准如下:
 速度有多快;
 得到的近似解与最优解的接近程度。

#首先,创建一个列表,其中包含要覆盖的州。
states_needed = set(["mt", "wa", "or", "id", "nv", "ut",

#还需要有可供选择的广播台清单,我选择使用散列表来表示它。
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"])



while states_needed:
    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 -= states_covered##你还需更新states_needed。###由于该广播台覆盖了一些州,  因此不用再覆盖这些州。
    final_stations.add(best_station)



###最后,你打印final_stations,结果类似于下面这样。
>>> print final_stations
set(['ktwo', 'kthree', 'kone', 'kfive'])

19.NP完全问题

旅行商问题的可能路线的条数:假设有10个城市,可能的路线有多少条呢? 10! = 3 628 800

这被称为阶乘函数(factorial function)
常见的NP完全问题

20动态规划

动态规划先解决子问题,再逐步解决大问题

要求:但仅当每个子问题都是离散的,即不依赖于其他子问题时,动态规划才管用
 

 动态规划可帮助你在给定约束条件下找到最优解。在背包问题中,你必
须在背包容量给定的情况下,偷到价值最高的商品。
 在问题可分解为彼此独立且离散的子问题时,就可使用动态规划来解决。
要设计出动态规划解决方案可能很难,这正是本节要介绍的。下面是一些通用的小贴士。
 每种动态规划解决方案都涉及网格

 单元格中的值通常就是你要优化的值。在前面的背包问题中,单元格的值为商品的价值。
 每个单元格都是一个子问题,因此你应考虑如何将问题分成子问题,这有助于你找出网
格的坐标轴
 

动态规划的步骤:可以参考:https://www.cnblogs.com/NEWzyz/p/8929844.html

背包问题,最终的答案总是在最后的单元格中,但对于最长公共子串问题,答案为网格中最大的数字——它可
能并不位于最后的单元格中

用法:(a)比较最长公共子串

1)绘制表格

2)填充网络

填充的方法(规定的计算最长公共子串的方法):

##两个字母相同
if word_a[i] == word_b[j]:
    cell[i][j] = cell[i-1][j-1] + 1

##两个字母不同
else:
    cell[i][j] = 0

(b)最长公共子序列

比较方法:

#两个字母相同
if word_a[i] == word_b[j]:
    cell[i][j] = cell[i-1][j-1] + 1
#两个字母不同
else:
    cell[i][j] = max(cell[i-1][j], cell[i][j-1])

猜你喜欢

转载自blog.csdn.net/u011436427/article/details/81431645
今日推荐