vlambda博客
学习文章列表

你知道吗?广度优先与深度优先只有这一个区别!

你好呀,我是阿德。

我最近在复习二叉树相关内容,看到二叉树的遍历算法,电光火石之间,我突然发现了广度优先与深度优先的本质区别,可以让你一分钟记住这两个算法。

广度优先搜索和深度优先搜索一样,都是可以对二叉树或图进行搜索的算法,都是从起点开始沿着边搜索,一直搜索直到找到指定节点(即终点)。

在此过程中每走到一个节点,就会判断一次它是否为终点。

两者的区别就是,广度优先搜索会根据离起点的距离,按照从近到远的顺序对各节点进行搜索。而深度优先搜索会沿着一条路径不断往下搜索直到不能再继续为止,然后再折返,开始搜索下一条路径。


01
广度优先搜索
在广度优先搜索中,有一个保存候补节点的队列,队列的性质就是先进先出,即先进入该队列的候补节点就先进行搜索。
下图中红色表示当前所在的节点(节点A),终点设为节点G,将与节点A直连的三个节点B, C, D放入存放候补节点的队列中(与节点A直连的三个节点放入时可以没有顺序,这里的放入顺序为B, C, D),用绿色表示。
此时队列中有 [B, C, D] 三个节点,我们来看看广度优先搜索是如何对各节点进行搜索的。
你知道吗?广度优先与深度优先只有这一个区别!
1、上面左图:此时从队列中选出一个节点,优先选择最早放入队列的那个节点,这里选择最左边的节点B。将已经搜索过的节点变为橙色(节点A),搜索到节点B时,将与节点B直连的两个节点E和F放入队列中,此时队列为 [C, D, E, F]。
2、上面中图:对节点B搜索完毕,节点B不是要找的终点,再搜索节点C,将与节点C直连的节点H放入队列中,此时队列为 [D, E, F, H]。
3、然后对节点D进行同样的操作,此时队列为 [E, F, H, I, J]。
4、上面右图:对与节点A直连的节点搜索完毕,再对与节点B直连的节点进行搜索(因为剩下的点中它们最先放入队列),这里选择节点E,将与节点E直连的节点K放入队列中,此时队列为 [F, H, I, J, K]。
然后一直按照这个规则进行搜索,直到到达目标节点G为止,搜索结束。
你知道吗?广度优先与深度优先只有这一个区别!
广度优先搜索为从起点开始,由近及远进行广泛的搜索。因此,目标节点离起点越近,搜索结束得就越快。

02
深度优先搜索
在深度优先搜索中,保存候补节点是,栈的性质就是先进后出,即最先进入该栈的候补节点就最后进行搜索。
还是将起点设为节点A,终点设为节点G,还是先将与节点A直连的三个节点B, C, D放入存放候补节点的栈中(这里的放入顺序为D, C, B)。到这里和广度优先搜索似乎没什么区别。
因为节点B是最后放入,则先从节点B开始搜索,将与节点B直连的两个节点E和F放入栈中,此时栈为 [D, C, F, E]。
你知道吗?广度优先与深度优先只有这一个区别!
接下来就可以看出深度优先搜索和广度优先搜索存在的区别了。
你知道吗?广度优先与深度优先只有这一个区别!
1、上面左图:然后再对节点E进行搜索,将与节点E直连的节点K放入栈中,此时栈为 [D, C, F, K]。
2、此时节点K在栈的最后,所以先对节点K进行搜索,节点K不是终点,而且节点K没有其他直连的节点,所以此时栈为 [D, C, F]。
3、上面中图:然后再对节点F进行搜索,节点F也不是终点,而且节点F也没有其他直连的节点,所以此时栈为 [D, C]。
3、上面右图:接下来就对节点C进行搜索,将与节点C直连的节点H放入栈中,此时栈为 [D, H]。
然后一直按照这个规则进行搜索,直到到达目标节点G为止,搜索结束。
你知道吗?广度优先与深度优先只有这一个区别!
深度优先搜索会沿着一条路径不断往下,搜索直到不能再继续为止,到了路径的尽头,再折返,再对另一条路径进行搜索。

03
两者区别
虽然广度优先搜索和深度优先搜索在搜索顺序上有很大的差异,但是在操作步骤上却只有一点不同,就是选择哪一个候补节点作为下一个节点的基准不同。
广度优先搜索选择的是最早成为候补的节点,因为节点离起点越近就越早成为候补,所以会从离起点近的地方开始按顺序搜索;而深度优先搜索选择的则是最新成为候补的节点,所以会一路往下,沿着新发现的路径不断深入搜索。
通过深度优先搜索可以判断图是否是连通图。具体实现为:在图中任意选择一个节点,从该节点开始进行深度优先搜索,如果在这个搜索过程中所有的节点都被搜索到,则该图为连通;反之,若存在没有被搜索到的节点,则说明该图是非连通的。
在广度优先搜索的执行过程中,搜索范围从起点开始逐渐向外延伸,所以不但可以判断两个节点之间是否有路径,还可以找出这两个节点的最短路径,即可以解决最短路径问题。
广度优先搜索可以用于找到两个节点的最短路径问题,这里的最短路径其实是针对非加权图,寻找段数最少的路径。但是对于加权图,段数最少的路径并不代表路径中的权重总和也最小。
对于加权图,计算最短路径可以如下三个算法:
  • 贝尔曼-福特算法((Bellman-Ford)
  • 狄克斯特拉算法(Dijkstra)
  • A* 算法(A-Star)

04
Python 代码实现

除了二叉树,有向图也可以使用广度优先搜索和深度优先搜索。
你知道吗?广度优先与深度优先只有这一个区别!
我用字典来表示整个图,图由多个节点组成。将节点与其所有邻近节点的关系用键值对来表示,代码如下:
graph = {}graph["A"] = ["B", "D", "F"]graph["B"] = ["C", "E"]graph["D"] = ["C"]graph["F"] = ["G", "H"]graph["C"] = []graph["E"] = []graph["G"] = []graph["H"] = []
键表示每个节点,值是一个数组,其中包含了键的所有邻近节点,这里的邻近节点是指从键所表示的节点出发,箭头所指向的节点。 没有邻近节点的键所对应的值为空列表。

04.1
广度优先搜索 Python 实现

这里使用函数  deque  来创建一个双端列表,可以实现在列表两端添加(append)和弹出(pop)元素。
from collections import dequesearch_queue = deque() # 创建一个节点列表search_queue += graph["A"] # 表示将"A"的相邻节点都添加到节点列表中
指定 "A" 为起点,"G" 为终点。
需要一个  searched  数组来保存搜索过的节点,防止同一个节点被多次搜索。
每次从节点列表的最左边取出节点。
完整代码如下:
from collections import deque
def search(start_node): search_queue = deque() search_queue += graph[start_node] searched = [] # 这个数组用于记录检查过的节点 while search_queue: # 只要节点列表不为空 node = search_queue.popleft() # 取出节点列表中最左边的节点 print(node, end=' ') # 打印出当前节点 if not node in searched: # 如果这个节点没检查过 if node == 'G': # 检查这个节点是否为终点"G" print("\nfind the destination!") return True else: search_queue += graph[node] # 将此节点的相邻节点都添加到节点列表中 searched.append(node) # 将这个节点标记为检查过 # 如果节点列表为空仍没找到终点,则返回False return False
print(search("A"))
运行上述代码,可以得到输出:
B D F C E C G find the destination!True
根据节点的输出顺序,就可以知道搜索顺序符合广度优先搜索的规则。

04.2
深度优先搜索 Python 实现

上面提到广度优先搜索和深度优先搜索的唯一区别就是选择哪一个候补节点作为下一个节点的基准不同
那么只需要将上面代码中的
node = search_queue.popleft() # 取出节点列表中最左边的节点
改为如下语句即可。
node = search_queue.pop() # 取出节点列表中最右边的节点
其他全都一模一样
我们指定 "A" 为起点,"B" 为终点,并运行上述代码,可以得到输出:
F H G D C B find the destination!True
根据节点的输出顺序,就可以知道搜索顺序符合深度度优先搜索的规则。
END

阿德最新文章不错过

程序员阿德
985非科班程序员,自学转行计算机,专注于分享计算机基础知识,从底层提升你的码力。
1篇原创内容
Official Account
创作不易,给阿德点个赞同、在看支持下哦。