深度优先查找与广度优先查找

深度优先查找和广度优先查找的查找与遍历图的两种非常重要的算法,它们也可以看作是减治技术的应用。

为方便部分读者对本节内容的掌握,我们先和大家一起学习和熟悉一些图问题中的概念。

严格来说一个图G=<V,E>有两个集合来定义。一个集合V,它的元素称为顶点;另一个集合E,它的元素是一对顶点,称为边。

如果每对顶点之间都没有顺序,也就是顶点对(u,v)等价于(v,u),则称u和v相互邻接,他们通过无向边(u,v)相连接,我们称顶点u,v和该边相关联,如果G中所有图都是无向的,我们称之为无向图。

如果每对顶点有顺序,即(u,v)不同于(v,u)我们则称之为有向的,如果每一条边都是有向的,则图本身就是有向的。

图的两种表示方法

深度优先查找

深度优先查找是一个过程,以下是这个过程的步骤:①选定任意顶点作为图的访问顶点并标记已访问→②处理与当前顶点邻接的未访问顶点(任选一个)→③重复步骤②→④遇到死端→⑤后退一条边,重复步骤②-⑤→⑥直到遇到起始顶点,而起始顶点也是死端时,算法停止。

这里解释一下死端,死端就是一个无法通向未访问邻接顶点的顶点。

在上述步骤中,我们需要标记已访问的顶点,如何标记呢?用一个栈(先进后出)来跟踪深度优先查找是比较方便的。开始对顶点访问时,将该顶点入栈,当它成为一个死端时,即结束对该顶点的访问时,将它出栈。也就是说一个顶点入栈和出栈的顺序是不一样的。

深度优先查找中还有一个概念:深度优先查找森林。遍历的初始顶点作为第一棵树的根,每次遇到一个新的未访问顶点,就看它是从哪个顶点被访问到的,那么新顶点就作为该顶点的子女。连接这样两个顶点的边称为树向边。有时,往下访问时会遇到一个已访问过的顶点(非父母边),此时将它们连起来的边称为回边。

广度优先查找

步骤:①选定初始顶点→②首先访问所有和初始顶点邻接的顶点→③访问离初始顶点两条边的所有未访问顶点→④以此类推,往下访问,直到所有与初始顶点同在一个连通分量的所有顶点都被访问过→⑤判断是否还存在仍未被访问的顶点→⑥如有,则从图的其他连通分量中的任意顶点开始,重复以上操作。

和用栈跟踪DFS不同的是,使用队列(先进先出)跟踪广度优先查找比较方便。操作:队列先从遍历的初始顶点开始,将该顶点标记为已访问。每次迭代时,该算法找出所有和队头顶点邻接的未访问顶点,将它们标记为已访问,把它们入队,然后将队头顶点从队列中移去。由于我们使用的是队列跟踪BFS,所有顶点的入队顺序和出队顺序是一样的。

广度优先查找森林:遍历的初始顶点作为森林中的第一棵树的根,无论何时,只要第一次遇到一个新的未访问顶点,它是从哪个顶点被访问到的,就把它附加为哪个顶点的子女。连接这样两个顶点的边称为树向边。如果一条边指向的是一个曾经访问过的顶点,并且这个顶点不是它的父母,这种边被称为交叉边。(回想一下这里和DFS森林的区别)。交叉边和回边不同,交叉除了把顶点和那些已访问顶点相连,还连接BFS树中同层或者相邻层中的兄弟顶点。

DFS和BFS都可用来检查图的连通性和无环性,但是在一些比较复杂的应用上又有所不同,DFS可求关节点,而BFS可用来求两个给定顶点间边的数量最少的路径。此外,BFS和DFS的效率是相同的。

最后,留给读者们一个小小的思考题,大家可以通过表格的形式从数据结构、顶点顺序种类、边的类型(无向图)以及应用几个项目方面总结一下DFS和BFS的异同,这将有助于大家更深刻地理解BFS和DFS。


from collections import deque


class Graph(object):
    def __init__(self):
        self.order = []
        self.neighbor = {}

    def add_node(self, node):
        key, val = node
        if not isinstance(val, list):
            print('node value should be a list')

        self.neighbor[key] = val

    def bfs(self, root):
        if root != None:
            search_queue = deque()
            search_queue.append(root)
            visited = []
        else:
            print('root is None')
            return -1

        while search_queue:
            person = search_queue.popleft()
            self.order.append(person)

            if (not person in visited) and (person in self.neighbor.keys()):
                search_queue += self.neighbor[person]
                visited.append(person)

    def dfs(self, root):
        if root != None:
            search_queue = deque()
            search_queue.append(root)
            visited = []
        else:
            print('root is None')
            return -1

        while search_queue:
            person = search_queue.popleft()
            self.order.append(person)

            if (not person in visited) and (person in self.neighbor.keys()):
                tmp = self.neighbor[person]
                tmp.reverse()

                for index in tmp:
                    search_queue.appendleft(index)

                visited.append(person)

    def clear(self):
        self.order = []

    def node_print(self):
        for index in self.order:
            print(index, end='  ')


if __name__ == '__main__':
    g = Graph()
    g.add_node(('a',['b','c']))
    g.add_node(('b', ['d']))
    g.add_node(('c', ['e','f','g']))

    g.bfs('a')
    print('broadth search first:')
    print('  ', end='  ')
    g.node_print()
    g.clear()

    print("  ")

    print('depth search first:')
    print('  ', end='  ')
    g.dfs('a')
    g.node_print()
    g.clear()

欢迎关注公众号 : 数学算法实验室
专注于算法与人工智能知识

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值