数据结构与算法--图 一步一步带你用Python实现图的深度遍历和广度优先遍历 Python实现图的深度遍历和广度优先遍历 Python详解DFS和BFS过程

图的基本介绍

  • 为什么要有图
    在这里插入图片描述
  • 图的举例说明
    在这里插入图片描述
  • 图的常用概念

在这里插入图片描述
在这里插入图片描述

图的表示方式

在这里插入图片描述

  • 邻接矩阵
    在这里插入图片描述
  • 邻接表
    在这里插入图片描述

图的入门 代码实现

  • 要求:用代码实现下面你的图结构
    在这里插入图片描述
  • 用邻接矩阵实现
class Graph(object):
    def __init__(self, n):  # 传入顶点个数
        self.vertex_list = []  # 存储顶点的集合
        self.edges = [[0 for i in range(n)] for j in range(n)]  # 存储图对应的邻接矩阵
        self.num_of_edges = 0  # 表示边的数目

    # 返回顶点的个数
    def get_num_of_vertex(self):
        return len(self.vertex_list)

    # 得到边的数目
    def get_num_of_edge(self):
        return self.num_of_edges

    # 返回结点i(下标)对应的数据:0->"A" 1->"B" 2->"C"
    def get_val_by_index(self, index):
        return self.vertex_list[index]

    # 返回v1和v2的权值
    def get_weight(self, v1, v2):
        return self.edges[v1][v2]

    # 显示图对应的矩阵
    def show_graph(self):
        for col in self.edges:
            print(col)

    # 插入顶点
    def insert_vertex(self, vertex):
        self.vertex_list.append(vertex)

    # 添加边
    def insert_edge(self, v1, v2, weight):
        '''
        :param v1: 表示顶点的下标,即是第几个顶点 “A”和“B”,“A”->0 "B"->1
        :param v2: 表示第二个顶点对应的下标
        :param weight: 表示权值(定义1 表示能直接连接 0 表示不能直接连接)
        :return:
        '''
        # 因为是无向的
        self.edges[v1][v2] = weight
        self.edges[v2][v1] = weight
        self.num_of_edges += 1


if __name__ == '__main__':
    n = 5  # 顶点个数
    vertex_val = ["A", "B", "C", "D", "E"]
    # 创建图对象
    g = Graph(n)
    # 循环的添加顶点
    for val in vertex_val:
        g.insert_vertex(val)
    # 添加边
    # A-B;A-C;B-C;B-D;B-E (无向的,五条边即可)
    g.insert_edge(0, 1, 1)  # A-B
    g.insert_edge(0, 2, 1)
    g.insert_edge(1, 2, 1)
    g.insert_edge(1, 3, 1)
    g.insert_edge(1, 4, 1)
    # 显示邻接矩阵
    g.show_graph()
'''输出结果
[0, 1, 1, 0, 0]
[1, 0, 1, 1, 1]
[1, 1, 0, 0, 0]
[0, 1, 0, 0, 0]
[0, 1, 0, 0, 0]
'''

图的深度优先遍历(DFS)

  • 图的深度优先遍历介绍
    在这里插入图片描述
    在这里插入图片描述
  • Python实现
class Graph(object):
    def __init__(self, n):  # 传入顶点个数
        self.vertex_list = []  # 存储顶点的集合
        self.edges = [[0 for i in range(n)] for j in range(n)]  # 存储图对应的邻接矩阵
        self.num_of_edges = 0  # 表示边的数目
        # 定义数组boolean_array,记录某个结点是否被访问过
        self.is_visited = [0] * n

    # 得到第一个邻接顶点的下标 w
    def get_first_neighbor(self, index):
        '''
        :param index: 传入第几个顶点
        :return: 如果存在就返回对应的下标,否则返回-1
        '''
        for j in range(len(self.vertex_list)):
            if self.edges[index][j] > 0:  # 说明下一个邻接结点存在
                return j
        return -1

    # 根据前一个邻接结点的下标来获取下一个邻接结点
    def get_next_neighbor(self, v1, v2):
        for j in range(v2 + 1, len(self.vertex_list)):
            if self.edges[v1][j] > 0:
                return j
        return -1

    # 深度优先遍历算法
    # i 第一次就是0
    def dfs(self, is_visited, i):
        # 首先访问该结点,输出
        print(self.get_val_by_index(i) + "->", end=" ")
        # 将整个结点设置为已经访问过
        self.is_visited[i] = True
        # 查找结点i的第一个结点是否存在w
        w = self.get_first_neighbor(i)
        while w != -1:  # 说明找到了下一个邻接结点
            if not self.is_visited[w]:  # 如果w没有被访问过
                self.dfs(is_visited, w)
            # 如果w结点被访问过
            w = self.get_next_neighbor(i, w)

    # 对dfs 遍历所有的结点,并进行dfs
    def dfs_override(self):
        # 遍历所有的结点,进行dfs【回溯】
        for i in range(self.get_num_of_vertex()):
            if not self.is_visited[i]:  # 如果当前结点没走过,提高效率
                self.dfs(self.is_visited, i)

    # 返回顶点的个数
    def get_num_of_vertex(self):
        return len(self.vertex_list)

    # 得到边的数目
    def get_num_of_edge(self):
        return self.num_of_edges

    # 返回结点i(下标)对应的数据:0->"A" 1->"B" 2->"C"
    def get_val_by_index(self, index):
        return self.vertex_list[index]

    # 返回v1和v2的权值
    def get_weight(self, v1, v2):
        return self.edges[v1][v2]

    # 显示图对应的矩阵
    def show_graph(self):
        for col in self.edges:
            print(col)

    # 插入顶点
    def insert_vertex(self, vertex):
        self.vertex_list.append(vertex)

    # 添加边
    def insert_edge(self, v1, v2, weight):
        '''
        :param v1: 表示顶点的下标,即是第几个顶点 “A”和“B”,“A”->0 "B"->1
        :param v2: 表示第二个顶点对应的下标
        :param weight: 表示权值(定义1 表示能直接连接 0 表示不能直接连接)
        :return:
        '''
        # 因为是无向的
        self.edges[v1][v2] = weight
        self.edges[v2][v1] = weight
        self.num_of_edges += 1


if __name__ == '__main__':
    n = 5  # 顶点个数
    vertex_val = ["A", "B", "C", "D", "E"]
    # 创建图对象
    g = Graph(n)
    # 循环的添加顶点
    for val in vertex_val:
        g.insert_vertex(val)
    # 添加边
    # A-B;A-C;B-C;B-D;B-E (无向的,五条边即可)
    g.insert_edge(0, 1, 1)  # A-B
    g.insert_edge(0, 2, 1)
    g.insert_edge(1, 2, 1)
    g.insert_edge(1, 3, 1)
    g.insert_edge(1, 4, 1)
    # 显示邻接矩阵
    g.show_graph()
    print("深度优先遍历:")
    g.dfs_override()

'''输出结果
[0, 1, 1, 0, 0]
[1, 0, 1, 1, 1]
[1, 1, 0, 0, 0]
[0, 1, 0, 0, 0]
[0, 1, 0, 0, 0]
深度优先遍历:
A-> B-> C-> D-> E-> 
'''

图的广度优先遍历(BFS)

  • 广度优先遍历的思想
    在这里插入图片描述
  • 广度优先遍历算法步骤
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • Python实现
class Graph(object):
    def __init__(self, n):  # 传入顶点个数
        self.vertex_list = []  # 存储顶点的集合
        self.edges = [[0 for i in range(n)] for j in range(n)]  # 存储图对应的邻接矩阵
        self.num_of_edges = 0  # 表示边的数目
        # 定义数组boolean_array,记录某个结点是否被访问过
        self.is_visited = [0] * n

    # 得到第一个邻接顶点的下标 w
    def get_first_neighbor(self, index):
        '''
        :param index: 传入第几个顶点
        :return: 如果存在就返回对应的下标,否则返回-1
        '''
        for j in range(len(self.vertex_list)):
            if self.edges[index][j] > 0:  # 说明下一个邻接结点存在
                return j
        return -1

    # 根据前一个邻接结点的下标来获取下一个邻接结点
    def get_next_neighbor(self, v1, v2):
        for j in range(v2 + 1, len(self.vertex_list)):
            if self.edges[v1][j] > 0:
                return j
        return -1

    # 对一个结点进行广度优先遍历
    def bfs(self, is_visited, i):
        # u 表示队列的头结点对应的下标
        # w 表示邻接结点w
        # 新建一个队列,记录结点访问的顺序
        queue = []
        # 首先访问该结点,输出
        print(self.get_val_by_index(i) + "->", end=" ")
        # 将整个结点设置为已经访问过
        self.is_visited[i] = True
        # 将结点加入队列
        queue.append(i)
        while queue:
            # 取出队列的头结点下标
            u = queue.pop(0)
            # 得到第一个邻结点的下标w
            w = self.get_first_neighbor(u)
            while w != -1:
                # 再判断是否访问过
                if not is_visited[w]:
                    # 输出该当前结点
                    print(self.get_val_by_index(w) + "->", end=" ")
                    # 将该结点设为为已经访问
                    self.is_visited[w] = True
                    # 将结点加入队列
                    queue.append(w)
                # 如果结点被访问过
                # 以u为前驱出发点,找w后面的下一个邻结点
                w = self.get_next_neighbor(u, w)  # 体现出广度优先

    # 遍历所有的结点,都进行广度优先遍历
    def bfs_override(self):
        for i in range(self.get_num_of_vertex()):
            if not self.is_visited[i]:
                self.bfs(self.is_visited, i)

    # 返回顶点的个数
    def get_num_of_vertex(self):
        return len(self.vertex_list)

    # 得到边的数目
    def get_num_of_edge(self):
        return self.num_of_edges

    # 返回结点i(下标)对应的数据:0->"A" 1->"B" 2->"C"
    def get_val_by_index(self, index):
        return self.vertex_list[index]

    # 返回v1和v2的权值
    def get_weight(self, v1, v2):
        return self.edges[v1][v2]

    # 显示图对应的矩阵
    def show_graph(self):
        for col in self.edges:
            print(col)

    # 插入顶点
    def insert_vertex(self, vertex):
        self.vertex_list.append(vertex)

    # 添加边
    def insert_edge(self, v1, v2, weight):
        '''
        :param v1: 表示顶点的下标,即是第几个顶点 “A”和“B”,“A”->0 "B"->1
        :param v2: 表示第二个顶点对应的下标
        :param weight: 表示权值(定义1 表示能直接连接 0 表示不能直接连接)
        :return:
        '''
        # 因为是无向的
        self.edges[v1][v2] = weight
        self.edges[v2][v1] = weight
        self.num_of_edges += 1


if __name__ == '__main__':
    n = 5  # 顶点个数
    vertex_val = ["A", "B", "C", "D", "E"]
    # 创建图对象
    g = Graph(n)
    # 循环的添加顶点
    for val in vertex_val:
        g.insert_vertex(val)
    # 添加边
    # A-B;A-C;B-C;B-D;B-E (无向的,五条边即可)
    g.insert_edge(0, 1, 1)  # A-B
    g.insert_edge(0, 2, 1)
    g.insert_edge(1, 2, 1)
    g.insert_edge(1, 3, 1)
    g.insert_edge(1, 4, 1)
    # 显示邻接矩阵
    g.show_graph()
    print("广度优先遍历:")
    g.bfs_override()
    
'''输出结果
[0, 1, 1, 0, 0]
[1, 0, 1, 1, 1]
[1, 1, 0, 0, 0]
[0, 1, 0, 0, 0]
[0, 1, 0, 0, 0]
广度优先遍历:
A-> B-> C-> D-> E-> 
'''

深度优先遍历和广度优先遍历的区别

  • 上面代码实现的例子,因为图非常简单,所有看不太出两种遍历方式的区别,于是看一个新的例子
    在这里插入图片描述
class Graph(object):
    def __init__(self, n):  # 传入顶点个数
        self.vertex_list = []  # 存储顶点的集合
        self.edges = [[0 for i in range(n)] for j in range(n)]  # 存储图对应的邻接矩阵
        self.num_of_edges = 0  # 表示边的数目
        # 定义数组boolean_array,记录某个结点是否被访问过
        self.is_visited = [0] * n

    # 得到第一个邻接顶点的下标 w
    def get_first_neighbor(self, index):
        '''
        :param index: 传入第几个顶点
        :return: 如果存在就返回对应的下标,否则返回-1
        '''
        for j in range(len(self.vertex_list)):
            if self.edges[index][j] > 0:  # 说明下一个邻接结点存在
                return j
        return -1

    # 根据前一个邻接结点的下标来获取下一个邻接结点
    def get_next_neighbor(self, v1, v2):
        for j in range(v2 + 1, len(self.vertex_list)):
            if self.edges[v1][j] > 0:
                return j
        return -1

    # 深度优先遍历算法
    # i 第一次就是0
    def dfs(self, is_visited, i):
        # 首先访问该结点,输出
        print(self.get_val_by_index(i) + "->", end=" ")
        # 将整个结点设置为已经访问过
        self.is_visited[i] = True
        # 查找结点i的第一个结点是否存在w
        w = self.get_first_neighbor(i)
        while w != -1:  # 说明找到了下一个邻接结点
            if not self.is_visited[w]:  # 如果w没有被访问过
                self.dfs(is_visited, w)
            # 如果w结点被访问过
            w = self.get_next_neighbor(i, w)

    # 对dfs 遍历所有的结点,并进行多少次 dfs
    def dfs_override(self):
        # 遍历所有的结点,进行dfs【回溯】
        for i in range(self.get_num_of_vertex()):
            if not self.is_visited[i]:  # 如果当前结点没走过,提高效率
                self.dfs(self.is_visited, i)

    # 对一个结点进行广度优先遍历
    def bfs(self, is_visited, i):
        # u 表示队列的头结点对应的下标
        # w 表示邻接结点w
        # 新建一个队列,记录结点访问的顺序
        queue = []
        # 首先访问该结点,输出
        print(self.get_val_by_index(i) + "->", end=" ")
        # 将整个结点设置为已经访问过
        self.is_visited[i] = True
        # 将结点加入队列
        queue.append(i)
        while queue:
            # 取出队列的头结点下标
            u = queue.pop(0)
            # 得到第一个邻结点的下标w
            w = self.get_first_neighbor(u)
            while w != -1:
                # 再判断是否访问过
                if not is_visited[w]:
                    # 输出该当前结点
                    print(self.get_val_by_index(w) + "->", end=" ")
                    # 将该结点设为为已经访问
                    self.is_visited[w] = True
                    # 将结点加入队列
                    queue.append(w)
                # 如果结点被访问过
                # 以u为前驱出发点,找w后面的下一个邻结点
                w = self.get_next_neighbor(u, w)  # 体现出广度优先

    # 遍历所有的结点,都进行广度优先搜索
    def bfs_override(self):
    	self.is_visited=[0] * n # 为了测试广度优先遍历,先把数组置空
        for i in range(self.get_num_of_vertex()):
            if not self.is_visited[i]:
                self.bfs(self.is_visited, i)

    # 返回顶点的个数
    def get_num_of_vertex(self):
        return len(self.vertex_list)

    # 得到边的数目
    def get_num_of_edge(self):
        return self.num_of_edges

    # 返回结点i(下标)对应的数据:0->"A" 1->"B" 2->"C"
    def get_val_by_index(self, index):
        return self.vertex_list[index]

    # 返回v1和v2的权值
    def get_weight(self, v1, v2):
        return self.edges[v1][v2]

    # 显示图对应的矩阵
    def show_graph(self):
        for col in self.edges:
            print(col)

    # 插入顶点
    def insert_vertex(self, vertex):
        self.vertex_list.append(vertex)

    # 添加边
    def insert_edge(self, v1, v2, weight):
        '''
        :param v1: 表示顶点的下标,即是第几个顶点 “A”和“B”,“A”->0 "B"->1
        :param v2: 表示第二个顶点对应的下标
        :param weight: 表示权值(定义1 表示能直接连接 0 表示不能直接连接)
        :return:
        '''
        # 因为是无向的
        self.edges[v1][v2] = weight
        self.edges[v2][v1] = weight
        self.num_of_edges += 1


if __name__ == '__main__':
    # n = 5  # 顶点个数
    n = 8  # 测试更复杂的顶点个数
    # vertex_val = ["A", "B", "C", "D", "E"]
    vertex_val = ['1', '2', '3', '4', '5', '6', '7', '8']
    # 创建图对象
    g = Graph(n)
    # 循环的添加顶点
    for val in vertex_val:
        g.insert_vertex(val)
    # 添加边
    # A-B;A-C;B-C;B-D;B-E (无向的,五条边即可)
    # g.insert_edge(0, 1, 1)  # A-B
    # g.insert_edge(0, 2, 1)
    # g.insert_edge(1, 2, 1)
    # g.insert_edge(1, 3, 1)
    # g.insert_edge(1, 4, 1)
    # -----更新更复杂的边----
    g.insert_edge(0, 1, 1)
    g.insert_edge(0, 2, 1)
    g.insert_edge(1, 3, 1)
    g.insert_edge(1, 4, 1)
    g.insert_edge(3, 7, 1)
    g.insert_edge(4, 7, 1)
    g.insert_edge(2, 5, 1)
    g.insert_edge(2, 6, 1)
    g.insert_edge(5, 6, 1)

    # 显示邻接矩阵
    g.show_graph()
    print("深度优先遍历:")
    g.dfs_override()
    print()
    print("广度优先遍历:")
    g.bfs_override()
# 结果分别为:
'''输出结果
[0, 1, 1, 0, 0, 0, 0, 0]
[1, 0, 0, 1, 1, 0, 0, 0]
[1, 0, 0, 0, 0, 1, 1, 0]
[0, 1, 0, 0, 0, 0, 0, 1]
[0, 1, 0, 0, 0, 0, 0, 1]
[0, 0, 1, 0, 0, 0, 1, 0]
[0, 0, 1, 0, 0, 1, 0, 0]
[0, 0, 0, 1, 1, 0, 0, 0]
深度优先遍历:
1-> 2-> 4-> 8-> 5-> 3-> 6-> 7-> 
广度优先遍历:
1-> 2-> 3-> 4-> 5-> 6-> 7-> 8-> 
'''
  • 对比两者之间的路径
    (1)深度优先遍历
    在这里插入图片描述
    (2)广度优先遍历
    在这里插入图片描述
发布了146 篇原创文章 · 获赞 37 · 访问量 7852

猜你喜欢

转载自blog.csdn.net/storyfull/article/details/103858521