圖論與廣度優先/深度優先算法


圖形介紹

圖形共有兩種:一種是無向圖形,一是有向圖形。五香圖形以(V1, V2)表示邊線,有線圖形以<V1,V2>表示邊線

圖形由頂點和邊所組成,以G=(V, E)來表示:其中V為所有頂點的集合,E為所有邊的集合

 

 

他們的數據結構可以表示為:

G1=(V1,E1), 其中 V1={a,b,c,d},E1={(a,b),(a,c),(a,d),(b,d),(c,d)},

G2=(V2,E2),其中 V2={1,2,3}, E2={<1,2>,<1,3>,<2,3>,<3,1>}

完全圖:在無向圖形中,N個定點正好有N(N-1)/2條邊,則稱為完全圖。但是在有向圖形中,若要成為完全圖,則必須有N(N-1)條邊

  • 路徑:兩個不同頂點間所經過的邊稱為路徑

  • 回路:起始頂點及終止頂點為同一個點的簡單路徑稱為回路

  • 相連:在無向圖形中,若頂點Vi到頂點Vj間存在路徑,則Vi和Vj是相連的

  • 相連圖形:如果圖形G中,任兩個頂點均相連,則次圖形稱為相連圖形,否則稱為非相連圖形

  • 路徑長度:路徑上所包含邊的總數為路徑長度

  • 相連單元:圖形中相連在一起的最大子圖總數

  • 強相連:在有向圖形中,若兩頂點間有兩條方向相反的邊稱為強相連

  • 度:在無向圖形中,一個頂點所擁有邊的總數為度

  • 入/出度: 在有向圖形中,以頂點V為箭頭終點的邊之個數為入度,反之則由V出發的箭頭總數為出度

圖形表示法

相鄰矩陣法

相關特性:

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

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

  • 用相鄰矩陣法表示圖形共需要n**2空間,由於無向圖形的相鄰矩陣一定具有堆成關系,所以出去對角線全部為零外,僅需要存儲上三角形或下三角形的數據即可,因此僅需n(n-1)/2空間

 

相鄰表法

相關特性:

  • 每一個頂點使用一個表

  • 在無向圖中,n個頂點e個邊工序n個表頭節點及2*e個節點:有向圖則需n個表頭節點及e個節點。在相鄰表中,計算所有頂點的度所需的時間復雜度為O(n+e)

相鄰多元列表法

結構:M----V1----V2----LINK1----LINK2

  • M:記錄改邊是否被找過的一個字段

  • V1及V2:所記錄的邊的起點與終點

  • LINK1:在尚有其他頂點與V1相連的情況下,此字段會指向下一個與V1相連的邊節點,如果已經沒有任何頂點與V1相連時,則指向null

  • LINK2:在尚有其他頂點與V2相連的情況下,此字段會指向下一個與V2相連的邊節點,如果已經沒有任何頂點與V2相連時,則指向null

 

圖形的遍歷

一個圖形G=(V,E),存在某一頂點v∈V,我們希望從v開始,通過此節點相鄰的節點而去訪問G中其他節點,這成為圖形的遍歷

先深后廣法

先深后廣遍歷的方式有點類似於前序遍歷。從圖形的某一頂點開始遍歷,被訪問過的頂點就做上訪問的記號,接着遍歷此頂點的所有相鄰且未訪問過的頂點中的任意一個頂點,並做上已訪問的記號。在以改點為新的起點繼續進行先深后廣的搜索。由於此方法會造成無限循環,所以必須加入一個變量,判斷改點是否已經遍歷完畢

Node類

 package DFS算法;
 
 /**
  * @author YanAemons
  * @date 2021/10/16 16:11
  */
 public class Node {
     int x;
     Node next;
     public Node(int x)
    {
         this.x = x;
         this.next = null;
    }
 
 }
 

GrapLink類

 package DFS算法;
 
 /**
  * @author YanAemons
  * @date 2021/10/16 16:11
  */
 public class GraphLink {
     public Node first;
     public Node last;
 
     public boolean isEmpty()
    {
         return first == null;
    }
 
     public void print()
    {
         Node current = first;
         while (current != null)
        {
             System.out.print("["+ current.x+"]");
             current = current.next;
        }
         System.out.println();
    }
 
     public void insert(int x)
    {
         Node newNode = new Node(x);
         if (this.isEmpty())
        {
             first = newNode;
             last = newNode;
        }
         else
        {
             last.next = newNode;
             last = newNode;
        }
    }
 }
 
 

Main類

 package DFS算法;
 
 
 /**
  * @author YanAemons
  * @date 2021/10/16 16:15
  */
 public class Main {
     public static int run[] = new int[9];
     public static GraphLink Head[] = new GraphLink[9];
     public static void dfs(int current)
    {
         run[current] = 1;
         System.out.print("["+current+"]");
 
         while (Head[current].first != null)
        {
             if (run[Head[current].first.x] == 0)
                 dfs(Head[current].first.x);
             Head[current].first = Head[current].first.next;
        }
    }
 
     public 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}};
         int DataNum;
         int i, j;
         System.out.println("圖形的鄰接表內容");
         for (i = 1; i < 9; i++)
        {
             run[i] = 0;
             Head[i] = new GraphLink();
             System.out.print("頂點"+i+"=>");
             for (j = 0; j < 20; j++)
            {
                 if (Data[j][0] == i)
                {
                     DataNum = Data[j][1];
                     Head[i].insert(DataNum);
                }
            }
             Head[i].print();
        }
         System.out.println("深度優先遍歷頂點:");
         dfs(1);
         System.out.println("");
    }
 }
 

列表run:用來記錄頂點是否被遍歷過,創建時所有數據都為0,當頂點被遍歷時,對應的索引的值變為1以此來記錄頂點被遍歷過

鏈表Head:該鏈表的每一個Node代表一個頂點,在創建時每個節點內創建了一個新鏈表,新鏈表用來記錄該頂點所有的相鄰頂點

遍歷時設置一個初始頂點(例中為頂點1),然后在Head鏈表里找到對應的索引根據Node里面的鏈表獲取下一個頂點,獲取到后根據run列表的記錄判斷是否被遍歷過。

  • 若未被遍歷過就執行遞歸打印,並且Head鏈表根據此頂點索引到對應的Node,重新根據該Node的鏈表重復操作

  • 若被遍歷過,就根據Node內的鏈表獲取下一個值重復判斷

 

先廣后深

Node類和GrapLink類和上文一樣

Main

 package BFS算法;
 
 /**
  * @author YanAemons
  * @date 2021/10/16 17:48
  */
 public class Main {
     public static int run[] = new int[9];
     public static GraphLink Head[] = new GraphLink[9];
     public final static int MAXSIZE = 10;
     static int[] queue = new int[MAXSIZE];
     static int front = -1;
     static int rear = -1;
 
     public static int dequeue()
    {
         if (front == rear ) return  -1;
         front++;
         return queue[front];
    }
 
     public static void enqueue(int value)
    {
         if (rear >= MAXSIZE) return;
         rear++;
         queue[rear] = value;
    }
 
     public static void bfs(int current)
    {
         Node tempnode;
         enqueue(current);
         run[current] = 1;
         System.out.print("["+current+"]");
         while (front != rear)
        {
             current = dequeue();
             tempnode = Head[current].first;
             while (tempnode != null)
            {
                 if (run[tempnode.x] == 0)
                {
                     enqueue(tempnode.x);
                     run[tempnode.x] = 1;
                     System.out.print("["+tempnode.x+"]");
                }
                 tempnode = tempnode.next;
            }
        }
    }
 
     public 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}};
         int DataNum;
         int i,j;
         System.out.println("圖形的鄰接表內容:");
         for (i = 1; i < 9; i++)
        {
             run[i] = 0;
             Head[i] = new GraphLink();
             System.out.print("頂點"+i+"=>");
             for ( j = 0; j < 20; j++)
            {
                 if (Data[j][0] == i)
                {
                     DataNum = Data[j][1];
                     Head[i].insert(DataNum);
                }
            }
             Head[i].print();
        }
         System.out.println("廣度優先遍歷頂點:");
         bfs(1);
         System.out.println("");
    }
 }
 

列表run和鏈表Head同先深后廣的用法與結構一樣。在先廣后深中還多了個queue用來記錄曾經遍歷過的頂點。

遍歷時設置一個初始頂點(例中為頂點1),然后在Head鏈表里找到對應的索引,在遍歷該Node里的鏈表並在queue中記錄Node內鏈表的值。遍歷完Node內的鏈表后退出,根據queue的順序重新確定下一個要遍歷的頂點並重復操作

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM