• 08 图的数据结构和算法


    图的遍历

    • 深度优先遍历
      有些类似前序遍历,从图的某一顶点开始遍历,被访问过的顶点就做上已访问的记号,接着遍历此顶点所有相邻且未访问过的顶点中的任意一个顶点,并做上已访问的记号,再以该点为新的起点继续进行深度优先的搜索。
      这种遍历方法结合了递归和堆栈两种数据结构的技巧,由于此方法会造成无限循环,因此必须加入一个变量,判断该点是否已经遍历完毕。
      class Node:
          def __init__(self):
              self.val = 0
              self.next = None
      
      head = [Node()] * 9  # 声明一个节点类型的链表数组
      run = [0] * 9
      
      def dfs(current):
          run[current] = 1
          print("[%d] " % current, end='')
          ptr = head[current].next
          while ptr != None:
              if run[ptr.val] == 0:  # 该顶点未遍历
                  dfs(ptr.val)
              ptr = ptr.next
      
      # 声明图的边线数组
      data = [
          [1, 2], [2, 1], [1, 3], [3, 1],
          [2, 4], [4, 2], [2, 5], [5, 2],
          [3, 6], [6, 3], [3, 7], [7, 3],
          [4, 8], [8, 4], [5, 8], [8, 5],
          [6, 8], [8, 6], [7, 8], [8, 7]
      ]
      
      for i in range(1, 9):
          run[i] = 0
          head[i] = Node()
          head[i].val = i  # 给各个链表头设置初值
          head[i].next = None
          ptr = head[i]
          for j in range(20):  # 20条边线
              if data[j][0] == i:  # 如果起点和链表头相等,就把顶点加入链表
                  newnode = Node()
                  newnode.val = data[j][1]
                  newnode.next = None
                  while True:  # 这样写有什么优势?
                      ptr.next = newnode  # 加入新节点
                      ptr = ptr.next
                      if ptr.next == None:
                          break
      
      print("图的邻接表内容:")
      for i in range(1, 9):
          ptr = head[i]
          print("顶点 %d ==> " % i, end='')
          ptr = ptr.next
          while ptr != None:
              print(" [%d] " % ptr.val, end='')
              ptr = ptr.next
          print()
      
      print("深度优先遍历的顶点:")
      dfs(1)
      print()
    
    图的邻接表内容:
      顶点 1 ==>  [2]  [3] 
      顶点 2 ==>  [1]  [4]  [5] 
      顶点 3 ==>  [1]  [6]  [7] 
      顶点 4 ==>  [2]  [8] 
      顶点 5 ==>  [2]  [8] 
      顶点 6 ==>  [3]  [8] 
      顶点 7 ==>  [3]  [8] 
      顶点 8 ==>  [4]  [5]  [6]  [7] 
    深度优先遍历的顶点:
      [1] [2] [4] [8] [5] [6] [3] [7] 
    
    • 广度优先遍历
      利用队列和递归技巧。从图的某一顶点开始遍历,被访问过的顶点做上已访问的记号,接着遍历此顶点的所有相邻且未访问过的顶点中的任意一个顶点,并做上已访问的记号,再以该点为起点继续进行广度优先遍历。
      MAXSIZE = 10  # 定义队列最大容量
      
      front = -1  # 指向队列的前端
      rear = -1  # 执行队列的后端
      
      
      class Node:
          def __init__(self, x):
              self.val = x
              self.next = None
      
      
      class GraphLink:
          def __init__(self):
              self.first = None
              self.last = None
      
          def my_print(self):
              current = self.first
              while current != None:
                  print(" [%d] " % current.val, end='')
                  current = current.next
              print()
      
          def insert(self, x):
              newnode = Node(x)
              if self.first == None:
                  self.first = newnode
                  self.last = newnode
              else:
                  self.last.next = newnode
                  self.last = newnode
      
      
      # 入队
      def enqueue(value):
          global MAXSIZE
          global rear
          global queue
          if rear >= MAXSIZE:
              return
          rear += 1
          queue[rear] = value
      
      
      # 出队
      def dequeue():
          global front
          global queue
          if front == rear:
              return -1
          front += 1
          return queue[front]
      
      
      # 广度优先搜索
      def bfs(current):
          global front
          global rear
          global Head
          global run
          enqueue(current)
          run[current] = 1
          print(" [%d] " % current, end='')
          while front != rear:  # 判断当前队列是否为空
              current = dequeue()
              tempnode = Head[current].first  # 先记录当前顶点的位置
              while tempnode != None:
                  if run[tempnode.val] == 0:
                      enqueue(tempnode.val)
                      run[tempnode.val] = 1
                      print(" [%d] " % tempnode.val, end='')
                  tempnode = tempnode.next
      
      
      # 声明图的边线数组
      data = [[0] * 2 for row in range(20)]
      data = [
          [1, 2], [2, 1], [1, 3], [3, 1],
          [2, 4], [4, 2], [2, 5], [5, 2],
          [3, 6], [6, 3], [3, 7], [7, 3],
          [4, 8], [8, 4], [5, 8], [8, 5],
          [6, 8], [8, 6], [7, 8], [8, 7]
      ]
      run = [0] * 9  # 记录个顶点是否遍历过
      queue = [0] * MAXSIZE
      Head = [GraphLink] * 9
      
      print('图的邻接表内容:')
      for i in range(1, 9):  # 共有8个顶点
          run[i] = 0
          print("%d ==> " % i, end='')
          Head[i] = GraphLink()
          for j in range(20):
              if data[j][0] == i:
                  datanum = data[j][1]
                  Head[i].insert(datanum)
          Head[i].my_print()
      
      print("广度优先遍历的顶点:")
      bfs(1)
      print()
    
    图的邻接表内容:
      1 ==>  [2]  [3] 
      2 ==>  [1]  [4]  [5] 
      3 ==>  [1]  [6]  [7] 
      4 ==>  [2]  [8] 
      5 ==>  [2]  [8] 
      6 ==>  [3]  [8] 
      7 ==>  [3]  [8] 
      8 ==>  [4]  [5]  [6]  [7] 
    广度优先遍历的顶点:
      [1]  [2]  [3]  [4]  [5]  [6]  [7]  [8] 
    

    最小生成树(Minimum Cost Spanning Tree,MST)

    一个图的生成树(spanning tree)就是以最少的边来连通图中所有的顶点,且不造成回路(cycle)的树形结构。
    weighted graph / network

    • 贪婪法则(Greedy Rule)
      1. Prim 算法
        P氏法。对一个加权图形 G=(V, E),设 V = {1,2,...,n},假设 U = {1},也就是说,U 和 V 是两个顶点的集合。
        然后从 U-V 的差集所产生的集合中找出一个顶点 x,该顶点 x 能与 U 集合中的某点形成最小成本的边,且不会造成回路。然后将顶点 x 加入 U 集合中,反复执行同样的步骤,一直到 U 集合等于 V 集合( U=V )为止。
      2. Kruskal 算法
        K氏法。将各边按权值大小从小到大排列,接着从权值最低的边开始建立最小成本的生成树,如果加入的边会造成回路则舍弃不用,直到加入了 n-1 个边为止。

    Kruskal 算法

      VERTS = 6
      
      
      class Edge:
          def __init__(self):
              self.start = 0
              self.to = 0
              self.find = 0
              self.val = 0
              self.next = None
      
      
      v = [0] * (VERTS + 1)
      
      
      def findmincost(head):
          minval = 100
          ptr = head
          while ptr != None:
              if ptr.val < minval and ptr.find == 0:
                  minval = ptr.val
                  retptr = ptr
              ptr = ptr.next
          retptr.find = 1
          return retptr
      
      
      def mintree(head):
          global VERTS
          result = 0
          ptr = head
          for i in range(VERTS):
              v[i] = 0
          while ptr != None:
              mceptr = findmincost(head)
              v[mceptr.start] = v[mceptr.start] + 1
              v[mceptr.to] = v[mceptr.to] + 1
              if v[mceptr.start] > 1 and v[mceptr.to] > 1:
                  v[mceptr.start] -= 1
                  v[mceptr.to] -= 1
                  result = 1
              else:
                  result = 0
              if result == 0:
                  print("起始顶点 [%d] -> 终止顶点 [%d] -> 路径长度 [%d]" % (mceptr.start, mceptr.to, mceptr.val))
              ptr = ptr.next
      
      
      data = [
          [1, 2, 6], [1, 6, 12], [1, 5, 10],
          [2, 3, 3], [2, 4, 5], [2, 6, 8],
          [3, 4, 7], [4, 6, 11], [4, 5, 9],
          [5, 6, 16]
      ]
      
      # 建立图的链表
      head = None
      for i in range(10):
          for j in range(1, VERTS + 1):
              if data[i][0] == j:
                  newnode = Edge()
                  newnode.start = data[i][0]
                  newnode.to = data[i][1]
                  newnode.val = data[i][2]
                  newnode.find = 0
                  newnode.next = None
                  if head == None:
                      head = newnode
                      head.next = None
                      ptr = head
                  else:
                      ptr.next = newnode
                      ptr = ptr.next
      print('------------------------------------')
      print('建立最小成本生成树:')
      print('------------------------------------')
      mintree(head)
    
      ----------------------------------------
      建立最小成本生成树:
      ----------------------------------------
      起始顶点[2] -> 终止顶点[3] -> 路径长度[3]
      起始顶点[2] -> 终止顶点[4] -> 路径长度[5]
      起始顶点[1] -> 终止顶点[2] -> 路径长度[6]
      起始顶点[2] -> 终止顶点[6] -> 路径长度[8]
      起始顶点[4] -> 终止顶点[5] -> 路径长度[9]
    

    图的最短路径法

    MST 计算连通网络中的每一个顶点所需的最小花费,但是连通树中任意两顶点的路径不一定是一条花费最少的路径。

    • Dijkstra算法与A*算法

    Dijkstra算法 —— 实际权重

      SIZE = 7
      NUMBER = 6
      INFINITE = 99999
      
      Graph_Matrix = [[0] * SIZE for row in range(SIZE)]  # 图的数组
      distance = [0] * SIZE  # 路径长度数组
      
      
      def build_graph_matrix(path_cost):
          for i in range(1, SIZE):
              for j in range(1, SIZE):
                  if i == j:  # 自己到自己的距离
                      Graph_Matrix[i][j] = 0
                  else:
                      Graph_Matrix[i][j] = INFINITE
          # 存入图的边
          i = 0
          while i < SIZE:
              start_point = path_cost[i][0]  # 起点
              end_point = path_cost[i][1]  # 终点
              Graph_Matrix[start_point][end_point] = path_cost[i][2]  # 权值
              i += 1
      
      
      # 单点对全部顶点的最短距离
      def shortest_path(vertex1, vertex_total):
          shortest_vertex = 1  # 记录最短距离的顶点
          goal = [0] * SIZE  # 记录该点是否被选取
          for i in range(1, vertex_total + 1):
              goal[i] = 0
              distance[i] = Graph_Matrix[vertex1][i]
          goal[vertex1] = 1
          distance[vertex1] = 0
      
          for i in range(1, vertex_total):
              shortest_distance = INFINITE
              for j in range(1, vertex_total + 1):
                  if goal[j] == 0 and shortest_distance > distance[j]:
                      shortest_distance = distance[j]
                      shortest_vertex = j
      
              goal[shortest_vertex] = 1
              # 计算开始顶点到各顶点的最短距离
              for j in range(vertex_total + 1):
                  if goal[j] == 0 and distance[shortest_vertex] + Graph_Matrix[shortest_vertex][j] < distance[j]:
                      distance[j] = distance[shortest_vertex] + Graph_Matrix[shortest_vertex][j]
      
      
      # global path_cost
      path_cost = [
          [1, 2, 29], [2, 3, 30], [2, 4, 35],
          [3, 5, 28], [3, 6, 87], [4, 5, 42],
          [4, 6, 75], [5, 6, 97],
      ]
      build_graph_matrix(path_cost)
      shortest_path(1, NUMBER)
      print('------------------------------')
      print('顶点 1 到各顶点最短距离的最终结果:')
      print('------------------------------')
      for i in range(1, SIZE):
          print('顶点 1 到顶点 %d 的最短距离为 %d ' % (i, distance[i]))
    
      ------------------------------
      顶点 1 到各顶点最短距离的最终结果:
      ------------------------------
      顶点 1 到顶点 1 的最短距离为 0 
      顶点 1 到顶点 2 的最短距离为 29 
      顶点 1 到顶点 3 的最短距离为 59 
      顶点 1 到顶点 4 的最短距离为 64 
      顶点 1 到顶点 5 的最短距离为 87 
      顶点 1 到顶点 6 的最短距离为 139 
    

    效率不高,因为在寻找起点到各个顶点距离的过程中,无论哪一个点,都要实际去计算起点与各个顶点之间的距离,以便获得最后的一个判断:到底哪一个顶点距离与起点最近。也就是说该算法在带有权重值的有向图间的最短路径的寻找方式,只是简单的使用了广度优先进行查找,完全忽略了许多有用的信息,这种查找算法会消耗很多系统资源,包括CPU的时间和内存空间。

    A*算法 —— 实际权重+推测权重
    结合了在路径查找过程中从起点到各个顶点的“实际权重”及各个顶点预估到达终点的“推测权重”(heuristic cost)两个因素,可以有效减少不必要的查找操作,提高效率。

    • 距离评估函数
      1. 曼哈顿距离(Manhattan distance):D = | x1 - x2 | + | y1 - y2 |
      2. 切比雪夫距离(Chebysev distance):D = max( | x1 - x2 |, | y1 - y2 | )
      3. 欧式几何平面直线距离(Euclidean distance):D = ( ( x1 - x2)^2 + (y1 - y2 )^2 )^1/2
    • A* 算法主要步骤
      1. 首先确定各个顶点到终点的“推测权重”。“推测权重”可采取各个顶点和终点之间的直线距离(四舍五入后的值),直线距离的计算函数,可用上述其一。
      2. 分别计算从起点可抵达的各个顶点的权重,其计算方法是由起点到该顶点的“实际权重”,加上该顶点抵达终点的“推测权重”。计算完毕后,选出权重最小的点,并标示为查找完毕的点。
      3. 接着计算从查找完毕的顶点出发到各个顶点的权重,并在从其中选出一个权重最小的顶点,并再将其标示为查找完毕的顶点。以此类推。。。,反复同样的计算过程,一直到抵达最后的终点。

    A* 算法适用于可以事先获得或预估各个顶点到终点距离的情况,万一无法获得各个顶点到达目的地终点的距离信息时,就无法使用A*算法。效率不是总优于 Dijkstra 算法,当“推测权重”的距离和实际两个顶点间的距离相差很大时,A* 算法的查找效率可能比 Dijkstra 算法更差,甚至还会误导方向,造成无法得到最短路径的最终答案。
    但是,如果推测权重所设置的距离和实际两个顶点间的真实距离误差不大时,A* 算法的查找效率远大于 Dijkstra算法。
    A* 算法常被用于游戏中角色追逐与快速突破关卡的设计。

    Floyd算法
    Dijkstra 算法只能求出某一点到其他顶点的距离,如果要求出图中任意两点甚至所有点间最短的距离,就必须使用 Floyd 算法。

      SIZE = 7
      NUMBER = 6
      INFINITE = 99999
      
      Graph_Matrix = [[0] * SIZE for row in range(SIZE)]  # 图的数组
      distance = [[0] * SIZE for row in range(SIZE)]  # 路径长度数组
      
      
      def build_graph_matrix(path_cost):
          for i in range(1, SIZE):
              for j in range(1, SIZE):
                  if i == j:
                      Graph_Matrix[i][j] = 0
                  else:
                      Graph_Matrix[i][j] = INFINITE
      
          # 图的边
          i = 0
          while i < SIZE:
              start_point = path_cost[i][0]
              end_point = path_cost[i][1]
              Graph_Matrix[start_point][end_point] = path_cost[i][2]
              i += 1
      
      
      def shortest_path(vertex_total):
          # 初始化图的长度数组
          for i in range(1, vertex_total + 1):
              for j in range(i, vertex_total + 1):
                  distance[i][j] = Graph_Matrix[i][j]
                  distance[j][i] = Graph_Matrix[i][j]
      
          # 使用 Floyd 算法找出所有顶点两两之间的最短距离
          for k in range(1, vertex_total + 1):
              for i in range(1, vertex_total + 1):
                  for j in range(1, vertex_total + 1):
                      if distance[i][k] + distance[k][j] < distance[i][j]:
                          distance[i][j] = distance[i][k] + distance[k][j]
      
      
      path_cost = [
          [1, 2, 20], [2, 3, 30], [2, 4, 25],
          [3, 5, 28], [4, 5, 32], [4, 6, 95],
          [5, 6, 67]
      ]
      
      build_graph_matrix(path_cost)
      print('=================================================')
      print('所有顶点两两之间的最短距离:')
      print('=================================================')
      shortest_path(NUMBER)
      print('        顶点1   顶点2  顶点3  顶点4   顶点5  顶点6')
      for i in range(1, NUMBER + 1):
          print('顶点 %d' % i, end='')
          for j in range(1, NUMBER + 1):
              print('%6d ' % distance[i][j], end='')
          print()
    
      =================================================
      所有顶点两两之间的最短距离:
      =================================================
              顶点1   顶点2  顶点3  顶点4   顶点5  顶点6
      顶点 1     0     20     50     45     77    140 
      顶点 2    20      0     30     25     57    120 
      顶点 3    50     30      0     55     28     95 
      顶点 4    45     25     55      0     32     95 
      顶点 5    77     57     28     32      0     67 
      顶点 6   140    120     95     95     67      0 
    

    拓扑排序的步骤:

    1. 寻找图中任何一个没有先行者的起点
    2. 输出此顶点,并将此顶点的所有边删除
    3. 重复上述步骤,处理所有顶点
  • 相关阅读:
    HTTP协议 (二) 基本认证
    HTTP协议详解
    Wireshark基本介绍和学习TCP三次握手
    Fiddler 教程
    UNIX网络编程——线程池模式比较(ICE线程池模型和L/F领导者跟随者模式)
    修改一行SQL代码 性能提升了100倍
    修改一行SQL代码 性能提升了100倍
    vector map 多层嵌套使用
    vector map 多层嵌套使用
    应该记住的话
  • 原文地址:https://www.cnblogs.com/catyuang/p/11760507.html
Copyright © 2020-2023  润新知