算法快速入门-基于《算法图解》的算法入门教程(3)

简介: 笔记

一、问题引入


5.png

假如我们所在的结点为v1,而我们需要到达v4结点,我们应该用什么方法?

 通过分析,我们发现不能从v1直接到达v4结点,因为没有v1为起点,指向v4的路径。那么我们能选择的路径似乎有这么几条:v1→v6→v4,v1→v2→v4,当然也有v1→v6→v2→v4这种路径,但这显然不是最优解。

 这个算法发现,前往以v1为起点前往v4结点至少需要三步。这种问题被称为最短路径问题(shorterst-path problem)。

 我们经常要找出最短路径的问题,这种问题可能是前往朋友家的最短路径,也可能是国际象棋中把对方将死的最少步数。解决最短路径问题的算法被称为广度优先搜索。

 我们要确定如何从v1结点前往v4结点,需要两个步骤:

 ①使用图来建立问题模型。

 ②使用广度优先搜索解决问题。


二、图的介绍


 图模拟一组连接,由节点(node)和边(edge)组成。一个节点可能与众多节点直接相连,这些节点被称为邻居。

 图用于模拟不同的东西是如何相连的。下面我们继续来看看广度优先搜索。


三、广度优先搜索


 广度优先搜索是一种用于图的查找算法,可帮助我们回答两类问题:

 第一类问题:从节点A出发,有前往节点B的路径吗?

 第二类问题:从节点A出发,前往节点B的哪条路径最短?

 前面我们计算从v1结点前往v4结点的最短路径时,使用过广度优先搜索。这个问题属于第二 类问题,即哪条路径最短?下面再来详细地研究这个算法,我们将使用它来回答第一类问题:有路径吗?


6.png

还是这张图,我们想知道:v6到v3结点有路径吗?首先在我们看来,一度关系胜过二度关系(即邻居关系胜过邻居的邻居),我们首先要找的是v6有没有直接到v3结点的路径。

 很遗憾,我们没有找到这种路径。于是我们便自然地想到二度关系,即看邻居的邻居有没有通往v3结点的路径,这里的二度关系胜过三度关系,以此类推。我们发现v6→v2→v3这条路径和v6→v4→v3这条路径了,在边无加权的情况下,这两条路径显然就是我们要找的最佳路径了!

 它当然优于其余的路径,比如v6→v2→v4→v3这条路径。

 因此,面对路径问题,你应先在一度关系中搜索,确定其中没有直达路径后,才在二度关系中搜索。

 在广度优先搜索的执行过程中,搜索范围从起点开始逐渐向外延伸,即先检查一度关系,再检查二度关系。


四、实现图


 首先,需要使用代码来实现图。图由多个节点组成。

 每个节点都与邻近节点相连,如果表示类似于“v1→v2” 这样的关系呢?我们知道的一种结构让你能够表示这种关系,它就是散列表!散列表让你能够将键映射到值。在这里,我们要做的只是将节点映射到其所有邻居。

 表示这种映射关系的Python代码如下:

graph = {} 
graph["v1"] = ["v2", "v6"] 

我们需要注意:“v1”被映射到了一个数组,因此graph[“v1”]是一个数组,其中包含了“v1”的所有邻居。 图不过是一系列的节点和边,因此在Python中,只需使用上述代码就可表示一个图。

graph = {} 
graph["v1"] = ["v2", "v6"] 
graph["v2"] = ["v3", "v4"] 
graph["v3"] = [] 
graph["v4"] = ["v1", "v3","v5"] 
graph["v5"] = [] 
graph["v6"] = ["v2"]


7.png

如此,便能实现以上图。

#键—值对的添加顺序并不重要,换言之,如果你这样编写代码:
graph["v2"] = ["v4","v3"] 
graph["v1"] = ["v6","v2"] 
#而不是这样编写代码:
graph["v1"] = ["v2","v6"] 
graph["v2"] = ["v3","v4"] 
#其对结果无影响
#散列表是无序的,因此添加键—值对的顺序无关紧要。

五、实现算法


 这种算法的工作原理如下:


1、创建一个队列,用于检查要检查的结点

2、从队列中弹出一个结点

3、检查这个结点是不是原结点的邻居

 a)是,则找到目标路径

 b)否,则将这个结点的所有邻居都假如队列

4、回到第2步

5、如果队列为空,就说明原结点的没有到目标结点的路径

from collections import deque 
search_queue = deque()      #创建一个队列
search_queue += graph["v1"] #将你的邻居都加入到这个搜索队列中
while search_queue:         #只要队列不为空,
  person = search_queue.popleft() #就取出其中的第一个结点
  if person_is_target(person):  #检查这个人是否是目标结点
    print person + " is the target!"  #是目标结点
    return True 
  else: 
    search_queue += graph[node]#不是目标节点,将它的邻居都加入到这个搜索队列中
return False #如果到达了这里,就说明队列中没有到目标节点的路径

最后,你还需编写函数person_is_target,判断一个结点是不是目标节点,如下所示:

def person_is_taregt(node): 
  return node

这个算法将不断执行,直到满足以下条件之一:

    找到目标节点;

    队列变成空的,这意味着原结点没有到目标结点的路径。

  具体实现算法如下:

def search(node): 
  search_queue = deque() 
  search_queue += graph[node] 
  searched = []               #这个数组用于记录检查过的结点
  while search_queue: 
    person = search_queue.popleft() 
    if not person in searched:        #仅当这个结点没检查过时才检查
      if person_is_target(person): 
        print person + " is the target!" 
        return True 
      else: 
        search_queue += graph[[person] 
        searched.append(person)   #将这个结点标记为检查过
  return False 
search("v1") 


六、运行时间


 如果你在你的整个图网中搜索目标节点,就意味着你将沿每条边前行(边是从一个结点到另一个结点的箭头或连接),因此运行时间至少为O(边数)。

 我们还使用了一个队列,其中包含要检查的每个结点。将一个结点添加到队列需要的时间是固定的,即为O(1),因此对每个人都这样做需要的总时间为O(结点数)。所以,广度优先搜索的运行时间为O(人数 + 边数),这通常写作O(V + E),其中V为顶点(vertice)数,E为边数。


未完待续


相关文章
|
2月前
|
Dart 算法 JavaScript
C#数据结构与算法入门教程,值得收藏学习!
C#数据结构与算法入门教程,值得收藏学习!
|
4月前
|
机器学习/深度学习 存储 人工智能
【利用AI让知识体系化】3万多字让你&我快速入门数据结构与算法(三)
【利用AI让知识体系化】3万多字让你&我快速入门数据结构与算法
|
4月前
|
存储 人工智能 搜索推荐
【利用AI让知识体系化】3万多字让你&我快速入门数据结构与算法(二)
【利用AI让知识体系化】3万多字让你&我快速入门数据结构与算法
|
4月前
|
存储 人工智能 算法
【利用AI让知识体系化】3万多字让你&我快速入门数据结构与算法(一)
【利用AI让知识体系化】3万多字让你&我快速入门数据结构与算法
|
9月前
|
Rust Dart 算法
支持C#的开源免费、新手友好的数据结构与算法入门教程 - Hello算法
支持C#的开源免费、新手友好的数据结构与算法入门教程 - Hello算法
|
存储 算法 芯片
快速入门数字芯片设计,UCSD ECE111(七)enum枚举类型&优化SHA256哈希算法(二)
快速入门数字芯片设计,UCSD ECE111(七)enum枚举类型&优化SHA256哈希算法(二)
67 0
|
算法 安全 芯片
快速入门数字芯片设计,UCSD ECE111(七)enum枚举类型&优化SHA256哈希算法(一)
快速入门数字芯片设计,UCSD ECE111(七)enum枚举类型&优化SHA256哈希算法
82 0
|
存储 算法 数据处理
快速入门数字芯片设计,UCSD ECE111(六)SHA256哈希算法的状态机实现(二)
快速入门数字芯片设计,UCSD ECE111(六)SHA256哈希算法的状态机实现(二)
88 0
|
算法 安全 区块链
快速入门数字芯片设计,UCSD ECE111(六)SHA256哈希算法的状态机实现(一)
快速入门数字芯片设计,UCSD ECE111(六)SHA256哈希算法的状态机实现
131 0