《圖說演算法-使用C#》要點摘錄 – 「圖形」

書名:《圖說演算法-使用C#》

作者:吳燦銘、胡昭民

所讀版本:博碩文化

圖形數據表示法

相鄰矩陣法
  • 假設圖有n個頂點,就利用一個n*n的二維矩陣來表示。當A(i, j) = 1,表示圖形中有一條邊(Vi, Vj)存在,反之則沒有。

  • 對無向圖而言,相鄰矩陣必然是對稱的,而且對角線一定為0。有向圖則不一定

  • 在無向圖中,任一節點i的分支度為i列所有元素的和;在有向圖中,節點i的出分支度為i列所有元素的和,入分支度為j行所有元素的和。

  • 相鄰矩陣法需要n^2空間,由於無向圖的相鄰矩陣一定具有對稱關係,所以扣除對角線全部為0外,僅需要儲存上三角形或下三角形的資料即可,因此僅需n(n-1)/2空間

相鄰串列法
  • 將一個n列的相鄰矩陣,表示成n個鏈結串列

  • 鏈結指向其他相連的頂點

  • 在無向圖中,因為對稱的關係,若有n個頂點、m個邊,則形成n個串列首,2m個節點;在有向圖中,則有n個串列首,m個節點

  • 求所有頂點分支的時間複雜度為O(n + m)

相鄰複合串列法
  • 如果處理的是「邊」,則需要使用相鄰複合串列法

  • 節點結構

    • M:記錄單元 => 該邊是否被找過

    • V1:邊線起點

    • V2:邊線終點

    • Link1:起點指標 => 尚有其他節點與V1相連下,Link1指向下一個與V1相連的節點

    • Link2:終點指標 => 尚有其他節點與V2相連下,Link2指向下一個與V2相連的節點

索引表格法
  • 以一維陣列依序儲存與各頂點相鄰的所有頂點,並建立索引表格

圖形遍歷法

DFS(深度優先)
  • 結合了遞歸和Stack兩種數據結構的技巧,從圖形的某一頂點開始走訪,被拜訪過的頂點就做上已拜訪的記號,接著走訪此頂點的所有相鄰且未拜訪過的頂點中的任意一個頂點,並做上已拜訪的記號,再以該點為新的起點繼續進行DFS

  • public class DFSNode
    {
       public int x;
       public DFSNode next;
       public DFSNode(int x)
      {
           this.x = x;
           next = null;
      }
    }

    public class GraphLink
    {
       public DFSNode first;
       public DFSNode last;
       public bool IsEmpty()
      {
           return first == null;
      }

       public void Print()
      {
           DFSNode curr = first;
           while(curr != null)
          {
               Console.WriteLine(curr.x + " ");
               curr = curr.next;
          }
           Console.WriteLine();
      }

       public void Insert(int x)
      {
           DFSNode newNode = new DFSNode(x);
           if (this.IsEmpty())
          {
               first = newNode;
               last = newNode;
          }
           else
          {
               last.next = newNode;
               last = newNode;
          }
      }
    }

    public static int[] run = new int[9];
    public static GraphLink[] Head = new GraphLink[9];
    public static void DFS(int curr)
    {
       run[curr] = 1;

       while(Head[curr].first != null)
      {
           if(run[Head[curr].first.x] == 0) { DFS(Head[curr].first.x); }
           Head[curr].first = Head[curr].first.next;
      }
    }

    static void Main(string[] args)
    {            
       int[,] 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, 5 }, { 5, 4 }, { 6, 7 }, { 7, 6 },
          { 5, 8 }, { 8, 5 }, { 6, 8 }, { 8, 6 }
      };

       for (int i = 1; i < 9; i++)
      {
           run[i] = 0;
           Head[i] = new GraphLink();
           for (int j = 0; j < 20; j++)
          {
               if(Data[j, 0] == i)
              {
                   int num = Data[j, 1];
                   Head[i].Insert(num);
              }
          }
           Head[i].Print();
      }

       Console.WriteLine();

       DFS(1);
    }
BFS(廣度優先)
  • 結合了遞歸和Queue的技巧,從圖形的某一頂點開始走訪,被拜訪過的頂點就做上已拜訪的記號,接著走訪此頂點的所有相鄰且未拜訪過的頂點中的任意一個頂點,並做上已拜訪的記號,再以該點為新的起點繼續進行DFS

  • public const int MAXSIZE = 10;
    static int[] queue = new int[MAXSIZE];
    static int front = -1;
    static int rear = -1;
    public static void Enqueue(int value)
    {
       if (rear >= MAXSIZE) { return; }
       rear++;
       queue[rear] = value;
    }
    public static int Dequeue()
    {
       if (front == rear) { return -1; }
       front++;
       return queue[front];
    }
    public static void BFS(int curr)
    {
       D_BFSNODE tmpNode;
       Enqueue(curr);
       run[curr] = 1;
       while(front != rear)
      {
           curr = Dequeue();
           tmpNode = Head[curr].first;
           while(tmpNode != null)
          {
               if(run[tmpNode.x] == 0)
              {
                   Enqueue(tmpNode.x);
                   run[tmpNode.x] = 1;
              }
               tmpNode = tmpNode.next;
          }
      }
    }

擴張樹

  • 以最小的邊來連結圖形中所有的點,且不造成循環的樹狀結構

  • 當一個圖形連通時,則使用DFS/BFS必能拜訪圖形中所有的頂點,且G=(V,E)的所有邊可分成兩個集合:T和B(T = 搜尋時經過的所有邊,B = 未被經過的邊)。

  • if S=(V,T)為G中的擴張樹,具有三種性質

    • E=T+B

    • 加入B中的任一邊到S中,則會產生循環

    • V中的任何2頂點Vi、Vj在S中存在唯一的一條簡單路徑

  • 使用DFS可產生縱向擴張樹

  • 使用BFS可產生橫向擴張樹

最小花費擴張樹
  • 在樹的邊加上一個權重,使之變成「加權圖形」,如果這個權重代表了頂點間的距離/成本,這類圖形稱為「網絡」

  • 可通過Prim’s演算法或Kruskal’s演算法找到一個無向連通圖形中的最小花費樹

    • Kruskal’s演算法

      • 將各邊線依權值大小由小到大排列,接著從權值最低的邊線開始架構最小成本擴張樹,如果加入的邊線會造成迴路則捨棄不用,直到加入了n-1個邊線為止。

最短路徑算法

Dijkstra演算法
  • 找到從起點前往各頂點的距離,選擇當中距離最小路徑的頂點並加到集合中。再把起點經過新頂點後,到其他頂點的累計距離進行比較,如果有更小的距離就將路徑更新,然後再選擇距離最小路徑的頂點,如此類推。

A*演算法
  • Dijkstra演算法的升級版,Dijkstra在尋找最短路徑的過程中,需要實際去計算起點與各頂點的距離。

    • 也就是說,Dijkstra’s演算法在帶有權重的有向圖形間的尋路只是簡單做BFS的工作。

  • A*結合了在路徑搜尋過程中從起點到各頂點的「實際權重」,及各頂點預估到達終點的「推測權重」

    • 曼哈頓距離

    • 切比雪夫距離

    • 歐氏幾何平面直線距離

  • 主要步驟

    • 決定各頂點到終點的「推測權重」

    • 分別計算從起點可抵達的各個頂點的權重(起點到該頂點的實際權重 + 該頂點到終點的推測權重),選出權重最小的頂點,並標示為搜尋完畢的頂點

    • 計算從搜尋完畢的頂點出發到各點的權重,再從中選出一個權重最小的點,再將該點標為搜尋完畢。以此類推,直到終點。

Floyd演算法
  • A^(k)[i,j] = min{A^(k-1)[i, j], A^(k-1)[i, k] + A^(k-1)[k, j]}, k>=1

    • k表示經過的頂點,A^(k)[i, j]為從頂點i到j的經由k頂點的最短路徑

  • A^(0)[i, j] = COST[i, j](即A^(0) = COST),A^(0)為頂點i到j的直通距離

  • A^(n)[i, j]代表i 到 j的最短距離,即A^(n) 便是我們所要求的最短路徑成本矩陣。