图是一种数据结构,其中节点可以具有零个或者多个相邻的元素,两个节点之间的连接成为边。节点也可以成为顶点。

  • 邻接表: 邻接表一般采用数组+链表的形式,数组表示各个顶点,链表中的元素表示该顶点与链表中的元素相连,与链表本身的指针没有关系。如上图 数组0 对应的链表1->3->4 表示0这个顶点与1 3 4这个顶点连接 数组1 表示1这个顶点与 0 2 4顶点相连以此类推
  • 邻接矩阵和邻接表的区别 邻接矩阵会为每个顶点都分配N个边的空间,其实很多表都不存在,存在了空间的损失,邻接表值关心存在边,因此没有空间的损失。

常用概念

  • 顶点 vertex 图中的每一个节点都是一个顶点 通常记做V
  • 边 edge 两个顶点之间的连接 通常记做E
  • 路径 从一个顶点到另一个顶点经过的边的总和,如果带权则是所有边的权重和
  • 无向图 顶点与顶点之间没有方向,即可以从顶点A到顶点B,也可以动B到A
  • 有向图 与无向图相反顶点与顶点之间存在明显的方向
  • 带权图 边上面带有权值的图称为带权图
  • 弧头 弧尾 在有向图中,无箭头端的顶点称为起始点或者弧尾,有箭头的顶点称为终端点或者弧头
  • (V1,V2)和<V1,V2>的区别 ,(V1,V2)表示无向图中2个顶点V1和V2,<V1,V2>表示有向图V1到V2的单向关系

深度优先

图的深度优先算法 depth first search

    1. 访问初始节点V,将V节点置为已访问状态
    2. 查询V节点的第一个邻接节点W
    3. 如果W不存在,则退回到第一步,从V的下一个节点继续调用
    4. 如果W存在,判断W是否访问过
       1. 如果W没有访问过,标记W为已访问,再以W为初始节点V继续递归深度算法
       2. 如果W已经访问过,节点V的W邻接点查找下一个邻接点,然后回到第三步

广度优先

类似一个分层搜索,广度优先遍历需要使用一个队列来保持访问过的节点的顺序,以便按这个顺序来访问这些节点的邻接节点,
    广度优先算法先将一个顶点的所有直接连接的顶点访问一遍,然后再访问下一个顶点所有直连的顶点依次循环

    实现步骤
    1. 1.访问初始节点V,标记节点V已访问
    2. 节点V入队列
    3. 当队列非空时,取对头节点,否则一直循环
    4. 出队列得到队头节点U
    5. 查找节点U的第一个邻接点W
    6. 若节点U的邻接点W,判断W是否存在
       1. W不存在访问第三步继续执行
       2. W存在 若W未访问,标记W为已访问,W加入队列,将U节点邻接W节点的后一个邻接点赋值给W 重复第六步
class Graph(object):
    def __init__(self, n):
        # 顶点集合
        self.vertexes = []
        # 邻接矩阵
        self.edges = [[0 for i in range(n)] for _ in range(n)]
        self.edges_count = 0

    def add_vertex(self, vertex):
        """
        添加顶点
        :param vertex:
        :return:
        """
        self.vertexes.append(vertex)

    def insert_edge(self, v1, v2, weight):
        """
        添加边
        :param v1:
        :param v2:
        :return:
        """
        self.edges[v1][v2] = weight
        self.edges[v2][v1] = weight

    def show(self):
        print(end='\t')
        for s in self.vertexes:
            print(s, end='\t')
        print()
        for i in range(len(self.edges)):
            edge = self.edges[i]
            print(self.vertexes[i], end='\t')
            for j in edge:
                print(j, end='\t')
            print()

    def dfs(self):
        visited = [False for i in range(len(self.vertexes))]
        for i in range(len(self.vertexes)):
            if visited[i] is False:
                self._dfs(i, visited)

    def get_first_neighbor(self, index):
        """
        获取某一个顶点的第一个邻接节点
        -1 表示没有任何节点与他连接
        返回第一个邻接节点的索引位置
        :return:
        """
        first_neighbor_index = 0
        while first_neighbor_index < len(self.vertexes):
            if self.edges[index][first_neighbor_index] == 1:
                return first_neighbor_index
            else:
                first_neighbor_index += 1
        return -1

    def get_next_neighbor(self, v1, v2):
        """
        获取从v1节点从v2节点开始之后的一个邻接节点
        :param v1:
        :param v2:
        :return:
        """
        next_index = v2 + 1
        for i in range(next_index, len(self.vertexes)):
            if self.edges[v1][i] > 0:
                return next_index
            else:
                next_index += 1
        return -1

    def _dfs(self, index, visited):
        """
        图的深度优先算法 depth first search

        1. 访问初始节点V,将V节点置为已访问状态
        2. 查询V节点的第一个邻接节点W
        3. 如果W不存在,则退回到第一步,从V的下一个节点继续调用
        4. 如果W存在,判断W是否访问过
           1. 如果W没有访问过,标记W为已访问,再以W为初始节点V继续递归深度算法
           2. 如果W已经访问过,节点V的W邻接点查找下一个邻接点,然后回到第三步
        :param index 访问顶点的索引位置
        :param visited 记录是否访问过顶点的数组
        :return:
        """
        # 1.访问初始节点V,将V节点置为已访问状态
        print(self.vertexes[index], end='->')
        visited[index] = True
        # 2.查询V节点的第一个邻接节点W
        w = self.get_first_neighbor(index)
        # 3. 如果W存在,判断W是否访问过
        while w != -1:
            # 1. 如果W没有访问过,标记W为已访问,再以W为初始节点V继续递归深度算法
            if not visited[w]:
                visited[w] = True
                self._dfs(w, visited)
            # 2. 如果W已经访问过,节点V的W邻接点查找下一个邻接点,然后回到第三步
            else:
                w = self.get_next_neighbor(index, w)
        # 4.如果W不存在,则退回到第一步,从V的下一个节点继续调用

    def bfs(self):
        visited = [False for _ in range(len(self.vertexes))]
        for i in range(len(self.vertexes)):
            self._bfs(i, visited)

    def _bfs(self, v, visited):
        """
        广度优先遍历 board first search
        类似一个分层搜索,广度优先遍历需要使用一个队列来保持访问过的节点的顺序,以便按这个顺序来访问这些节点的邻接节点,
        广度优先算法先将一个顶点的所有直接连接的顶点访问一遍,然后再访问下一个顶点所有直连的顶点依次循环

        实现步骤
        1. 1.访问初始节点V,标记节点V已访问
        2. 节点V入队列
        3. 当队列非空时,取对头节点,否则一直循环
        4. 出队列得到队头节点U
        5. 查找节点U的第一个邻接点W
        6. 若节点U的邻接点W,判断W是否存在
           1. W不存在访问第三步继续执行
           2. W存在 若W未访问,标记W为已访问,W加入队列,将U节点邻接W节点的后一个邻接点赋值给W 重复第六步
        :return:
        """
        # 访问初始节点V,标记节点V已访问
        v_queue = []
        visited[v] = True
        print(self.vertexes[v], end='->')
        vertexes.append(v)
        # 当队列非空时,取对头节点,否则一直循环
        while v_queue:
            # 出队列得到队头节点U
            u = v_queue.pop(0)
            # 查找节点U的第一个邻接点W
            w = self.get_first_neighbor(u)
            while w != -1:
                if not visited[w]:
                    # W存在 若W未访问,标记W为已访问
                    print(self.vertexes[w], end='->')
                    visited[w] = True
                    # W加入队列
                    v_queue.append(w)
                    # 将U节点邻接W节点的后一个邻接点赋值给W 重复第六步
                    w = self.get_next_neighbor(u, v)

            else:
                # W不存在访问第三步继续执行
                pass


#
if __name__ == '__main__':
    n = 5
    graph = Graph(5)
    vertexes = ["A", "B", "C", "D", "E"]
    for v in vertexes:
        graph.add_vertex(v)
    graph.insert_edge(0, 1, 1)  # A->B
    graph.insert_edge(0, 2, 1)  # A->C
    graph.insert_edge(1, 2, 1)  # B->C
    graph.insert_edge(1, 3, 1)  # B->D
    graph.insert_edge(1, 4, 1)  # B->E
    graph.show()
    graph.dfs()
    print()
    graph.bfs()