java 數據結構 圖


以下內容主要來自大話數據結構之中,部分內容參考互聯網中其他前輩的博客,主要是在自己理解的基礎上進行記錄。

  圖的定義

         圖是由頂點的有窮非空集合和頂點之間邊的集合組成,通過表示為G(V,E),其中,G標示一個圖,V是圖G中頂點的集合,E是圖G中邊的集合。

         無邊圖:若頂點Vi到Vj之間的邊沒有方向,則稱這條邊為無項邊(Edge),用序偶對(Vi,Vj)標示。

       

         有向圖:若從頂點Vi到Vj的邊是有方向的,則成這條邊為有向邊,也稱為弧(Arc)。用有序對(Vi,Vj)標示,Vi稱為弧尾,Vj稱為弧頭。如果任意兩條邊之間都是有向的,則稱該圖為有向圖。

                     有向圖G2中,G2=(V2,{E2}),頂點集合(A,B,C,D),弧集合E2={<A,D>,{B,A},<C,A>,<B,C>}.

         權(Weight):有些圖的邊和弧有相關的數,這個數叫做權(Weight)。這些帶權的圖通常稱為網(Network)。

    

  圖的存儲結構

  圖的存儲結構一般分為鄰接矩陣和十字鏈表

  鄰接矩陣:圖的鄰接矩陣存儲方式是用兩個數組來標示圖。一個一位數組存儲圖頂點的信息,一個二維數組(稱為鄰接矩陣)存儲圖中邊或者弧的信息。

  設圖G有n個頂點,則鄰接矩陣是一個n*n的方陣,定義為:

        

  

  十字鏈表: 

  頂點表結點結構:

  

  firstin:表示入邊表頭指針,指向該頂點的入邊表中第一個結點。

  firstout:表示出邊表頭指針,指向該頂點的出邊表中的第一個結點。

  邊表結點結構:

  

  tailvex:指弧起點在頂點表的下標。

  headvex:指弧終點在頂點表中的下標。

  headlink:指入邊表指針域。

  taillink:指邊表指針域。

  如果是網,還可以再增加一個weight域來存儲權值。

  

  藍線表示出度,紅線表示入度

  十字鏈表的優點

  十字鏈表是把鄰接表和逆鄰接表整合在一起,這樣既容易找到以Vi為尾的弧,也容易找到以Vi為頭的弧,

  因而容易求的頂點的出度和入度。

  

  圖的搜索:

  深度優先遍歷:也有稱為深度優先搜索,簡稱DFS。其實,就像是一棵樹的前序遍歷。它從圖中某個結點v出發,訪問此頂點,然后從v的未被訪問的鄰接點出發深度優先遍歷圖,直至圖中所有和v有      路徑相通的頂點都被訪問到。若圖中尚有頂點未被訪問,則另選圖中一個未曾被訪問的頂點作起始點,重復上述過程,直至圖中的所有頂點都被訪問到為止。

  基本實現思想:

  (1)訪問頂點v;

  (2)從v的未被訪問的鄰接點中選取一個頂點w,從w出發進行深度優先遍歷;

  (3)重復上述兩步,直至圖中所有和v有路徑相通的頂點都被訪問到。

  

  廣度優先遍歷:也稱廣度優先搜索,簡稱BFS。BFS算法是一個分層搜索的過程,和樹的層序遍歷算法類同,它也需要一個隊列以保持遍歷過的頂點順序,以便按出隊的順序再去訪問這些頂點的鄰接頂點。 

  基本實現思想:

  (1)頂點v入隊列。

  (2)當隊列非空時則繼續執行,否則算法結束。

  (3)出隊列取得隊頭頂點v;訪問頂點v並標記頂點v已被訪問。

  (4)查找頂點v的第一個鄰接頂點col。

  (5)若v的鄰接頂點col未被訪問過的,則col入隊列。

  (6)繼續查找頂點v的另一個新的鄰接頂點col,轉到步驟(5)。

        直到頂點v的所有未被訪問過的鄰接點處理完。轉到步驟(2)。

  廣度優先遍歷圖是以頂點v為起始點,由近至遠,依次訪問和v有路徑相通而且路徑長度為1,2,……的頂點。為了使“先被訪問頂點的鄰接點”先於“后被訪問頂點的鄰接點”被訪問,需設置隊列存儲訪問的頂點。

  最小生成樹

    我們把構造連通網的最小代價生成的樹稱為最小生成樹,即權值最小的生成樹。

 

  實現方式:

  1、普利姆算法(Prim)

     基本思想:假設G=(V,E)是連通的,TE是G上最小生成樹中邊的集合。算法從U={u0}(u0∈V)、TE={}開始。重復執行下列操作:

       在所有u∈U,v∈V-U的邊(u,v)∈E中找一條權值最小的邊(u0,v0)並入集合TE中,同時v0並入U,直到V=U為止。

      此時,TE中必有n-1條邊,T=(V,TE)為G的最小生成樹。

      Prim算法的核心:始終保持TE中的邊集構成一棵生成樹。

   注意:prim算法適合稠密圖,其時間復雜度為O(n^2),其時間復雜度與邊得數目無關,而kruskal算法的時間復雜度為O(eloge)跟邊的數目有關,適合稀疏圖。

    示例:

    

 

 

  (1)圖中有6個頂點v1-v6,每條邊的邊權值都在圖上;在進行prim算法時,我先隨意選擇一個頂點作為起始點,當然我們一般選擇v1作為起始點,好,現在我們設U集合為當前所找到最小生成樹里面的   頂點,TE集合為所找到的邊,現在狀態如下:    

      U={v1}; TE={};

  (2)現在查找一個頂點在U集合中,另一個頂點在V-U集合中的最小權值,如下圖,在紅線相交的線上找最小值。

   

 

  通過圖中我們可以看到邊v1-v3的權值最小為1,那么將v3加入到U集合,(v1,v3)加入到TE,狀態如下:

U={v1,v3}; TE={(v1,v3)};

(3)繼續尋找,現在狀態為U={v1,v3}; TE={(v1,v3)};在與紅線相交的邊上查找最小值。

  

  我們可以找到最小的權值為(v3,v6)=4,那么我們將v6加入到U集合,並將最小邊加入到TE集合,那么加入后狀態如下:

  U={v1,v3,v6}; TE={(v1,v3),(v3,v6)}; 如此循環一下直到找到所有頂點為止。

  (4)下圖像我們展示了全部的查找過程:

  

 

    

  2、克魯斯卡爾算法(Kruskal)

    

  假設連通網N=(V,{E})。則令最小生成樹的初始狀態為只有n個頂點而無邊的非連通圖T=(V,{}),圖中每個頂點自成一個連通分量。在E中選擇最小代價的邊,若該邊依附的頂點落在T中不同的連通分       量中,則將該邊加入到T中,否則舍去此邊而選擇下一條代價最小的邊,依次類推,直到T中所有頂點都在同一連通分量上為止。

  示例如下:

  

    圖中先將每個頂點看作獨立的子圖,然后查找最小權值邊,這條邊是有限制條件的,邊得兩個頂點必須不在同一個圖中,如上圖,第一個圖中找到最小權值邊為(v1,v3),且滿足限制條件,繼續查找邊              (v4,v6),(v2,v5),(v3,v6),當查找到最后一條邊時,僅僅只有(v2,v3)滿足限制條件,其他的如(v3,v4),(v1,v4)都在一個子圖里面,不滿足條件,至此已經找到最小生成樹的

           所有邊。

 

 

  上述所有的具體代碼實現:

  

  1 /**
  2  * java數據結構無向圖的實現
  3  * 2016/4/29
  4  */
  5 package cn.Link;
  6 
  7 import java.util.ArrayList;
  8 import java.util.LinkedList;
  9 import java.util.Queue;
 10 public class Graph {
 11             
 12     final static int MAX = 65535;  //兩個定點之間沒有路徑時的長度
 13     int verticts; //頂點數
 14     int sides;      //頂點為verticts的連通圖的邊數值 
 15     int[][] arc;//存儲圖的二維數組 
 16     String[] vex;
 17     Graph(int verticts){
 18         this.verticts = verticts;
 19         this.sides = 0;
 20         for(int i = verticts-1;i>0;i--){
 21             this.sides +=i;     //獲得連通圖的邊數值
 22         }
 23         this.arc = new int[verticts][verticts];
 24         this.vex = new String[verticts];
 25         //初始化一個有向圖,所有邊設為最大權值(即不可能達到的值)
 26         for(int i = 0; i < verticts;i++){
 27             for(int j = 0; j < verticts;j++){
 28                 if(i!=j){
 29                    this.arc[i][j] = MAX;
 30                 }else{
 31                     this.arc[i][j]=0;
 32                 }
 33                 
 34             }
 35         }
 36     }
 37     
 38     //假設有這樣一個圖
 39     public void addGraph(){
 40         //頂點數據
 41         this.vex[0] = "beijing";
 42         this.vex[1] = "shanghai";
 43         this.vex[2] = "tianjing";
 44         this.vex[3] = "chengdu";
 45         this.vex[4] = "changsha";
 46         this.vex[5] = "chongqing";
 47 
 48         //邊的權值
 49         for(int i = 1; i < verticts;i++){
 50             for(int j = 0; j < i;j++){
 51                 int n = (int)(Math.random()*100);    //隨機生成權值
 52                 if(n > 0){
 53                 this.arc[i][j]=this.arc[j][i] = n;
 54                 }else if(n == 0){
 55                     this.arc[i][j]=this.arc[j][i] = MAX;
 56                 }
 57                 
 58             }
 59         }
 60     }
 61 
 62     //利用圖的二維數組輸出
 63     public void printGraph(){
 64         for(int i = 0; i < verticts;i++){
 65             //輸出第一行的名稱
 66             if(i == 0){
 67                 System.out.print("*         ");
 68                 for(int x=0;x<verticts;x++){
 69                     System.out.print(this.vex[x]+"  ");
 70                 }
 71                 System.out.println();
 72                 System.out.println("         ==============================================================");
 73             }
 74             //給每行前面輸出地址
 75                 System.out.print(this.vex[i]+"     ");
 76             for(int j = 0; j < verticts;j++){
 77                 System.out.print(this.arc[i][j]+"        ");
 78             }
 79             System.out.println();
 80             System.out.println();
 81         }
 82     }
 83 
 84     //深度優先遍歷輸出
 85     public void DFS(Graph G,int i,boolean[] visited){
 86         int j;
 87         visited[i] = true;
 88         System.out.print(G.vex[i]+"  ");   //打印頂點的值
 89         for(j = 0;j < G.verticts;j++){
 90             if(G.arc[i][j]>0 && !visited[j]){
 91                 DFS(G,j,visited);       //對訪問的鄰接頂點遞歸調用
 92             }
 93         }
 94         
 95     }
 96     public void DFSTraverse(Graph  G){
 97         boolean[] visited = new boolean[G.verticts];
 98         for(int i = 0;i < G.verticts;i++){
 99             visited[i] = false;         //初始狀態所有頂點都是未訪問過的狀態
100         }
101         for(int i = 0;i < G.verticts;i++){
102             if(!visited[i])
103                 DFS(G,i,visited);       //對未訪問過的頂點調用DFS  如果是連通圖,則只會執行一次
104         }
105     }
106 
107 
108 
109     //廣度優先遍歷輸出
110     public void BFS(Graph G){
111         int i, j;
112         Queue<Integer> Q = new LinkedList<Integer>();
113         boolean[] visited = new boolean[G.verticts];
114         for(i = 0;i < G.verticts;i++){
115             visited[i] = false;
116         }
117         
118         for(i = 0; i < G.verticts;i++){       //對每一個頂點都進行循環
119             if(!visited[i]){
120                 visited[i] = true;
121                 System.out.print("##"+G.vex[i]+"  ");
122                 Q.offer(i);
123                 while(Q != null){
124                     if(Q.peek() != null){
125                     i = Q.poll(); //將隊首元素賦值給i  然后出隊列
126                     }else{return ;}
127                     for(j = 0;j < G.verticts;j++){
128                         //判斷其他頂點與當前頂點存在邊但未被訪問過
129                         if(G.arc[i][j] > 0 && !visited[j]){
130                             visited[j] = true;
131                             System.out.print("##"+G.vex[j]+"  ");
132                             Q.offer(j);
133                         }
134                     }
135                 }
136             }
137         }
138     }
139 
140     //得到最小生成樹之普利姆(Prim)算法
141     public void MiniSpanTree_Prim(Graph G){
142         int min, i, j, k;
143         int [] adjvex = new int[G.verticts];           //保存相關頂點下表
144         int [] lowcost = new int[G.verticts];          //保存相關頂點間邊的權值
145         lowcost[0] = 0;                         //初始化第一個權值為0 ,即vex[0]已經加入到生成樹
146         adjvex[0] = 0;                          //初始化第一個頂點下標為0
147         for(i = 1;i < G.verticts;i++){
148             lowcost[i] = G.arc[0][i];           //將vex[0]頂點與之有邊的權值存入數組
149             adjvex[i] = 0;                      //初始化都為vex[0]的下標
150         }
151 
152         for(i = 1;i < G.verticts;i++){
153             min =  MAX;                     //初始化最小權值為MAX:65535  
154             j = 1;
155             k = 0;
156             //循環所有頂點
157             while(j < G.verticts){
158                 if(lowcost[j] != 0 && lowcost[j] < min){    //如果權值不為0,而且小於最小值
159                     min = lowcost[j];
160                     k = j;
161                 }
162                 j++;
163             }
164 
165             System.out.println("("+k+", "+adjvex[k]+")"+"   權長:"+G.arc[adjvex[k]][k]);        //打印當前頂點邊中權值最小的邊
166             lowcost[k] = 0;                 //將當前頂點的權值設為0,表示此頂點已將完成任務
167             for(j = 1;j < G.verticts;j++){  //循環所有頂點
168                 if(lowcost[j] != 0 && G.arc[k][j] < lowcost[j]){       //如果下標為k的頂點各邊權值小於此前這些頂點未被加入生成權值
169                     lowcost[j] = G.arc[k][j];           //將較小的權值存入lowcost
170                     adjvex[j] = k;          //將下標為k的頂點存入adjvex
171                 }
172             }
173         }
174     }
175 
176 
177     //得到最小生成樹之克魯斯卡爾(Kruskal)算法
178     //得到邊集數組並按權由大到小排序 這一步是利用Edge類來實現的
179     //注意 此函書還有錯誤,有時候會輸出6條邊,尚待解決(MinispanTree_kruskal在尋找的過程中不可能形成環路,所以不可能多一條邊)
180     //上述錯誤已經解決,有兩個地方出來問題,第一:Edge的begin必須小於end,否則在Find函數中判斷將出現錯誤,因為如果end小於begin的話,end有可能
181     //出現等於0的情況,第二:循環每一條邊時,i應該小於G.sides;而我之前寫成了i<G.verticts
182     public void MiniSpanTree_Kruskal(Graph G){
183         Edge edge = new Edge(); 
184         edge.Edge_1(G);
185         //edge.PrintEdge();
186         int i, n, m;
187         int num = 0;  //記錄找到了多少條邊
188         int parent[] = new int[G.verticts];   //定義一個數組用來判斷邊與邊是否形成回路
189         for( i = 0;i < G.verticts;i++){
190             parent[i] = 0;      //初始化數組為-1
191         }
192         for(i = 0;i < G.sides;i++){        // 循環每一條邊,15為頂點
193             n = Find(parent,edge.edge[i].begin);
194             m = Find(parent,edge.edge[i].end);
195             if(n != m ){         //n不等於m  說明邊與邊沒有形成環路
196                 parent[n] = m;//將此邊的尾節點放入下標為起點的parent中
197                 System.out.println("("+edge.edge[i].begin+","+edge.edge[i].end+")"+"  權長:"+edge.edge[i].weight);
198                 num++;
199                 //for(int j = 0;j < G.verticts;j++){
200                     //System.out.print("  !!!!"+parent[j]);      //初始化數組為0
201                  //}
202             }
203             if(num >= G.verticts)  break;     //如果找到了(頂點數-1)條邊,並且沒有構成回路,就已經完成任務了,不用再找了,
204             
205         }
206     }
207     public int Find(int[] parent,int f){        //查找連線頂點的尾部下表
208         while(parent[f] > 0){
209             f = parent[f];
210         }
211         return f;
212     }
213 
214 
215     //測試函數
216     public static void main(String[] args){
217         Graph graph = new Graph(6);  //創建一個頂點個數為6的圖
218         graph.addGraph();
219         System.out.println("將圖以二維矩陣的方式輸出");
220         graph.printGraph(); 
221         System.out.println("深度優先搜索結果");
222         graph.DFSTraverse(graph); 
223         System.out.println();
224         System.out.print("廣度優先搜索結果");
225         System.out.println();
226         graph.BFS(graph);
227         System.out.println();
228         System.out.println("最小生成樹之普利姆算法Prim  ");
229         graph.MiniSpanTree_Prim(graph);
230         System.out.println();
231         System.out.println("最小生成樹之克魯斯卡爾算法Kruskal   ");
232         graph.MiniSpanTree_Kruskal(graph);
233 
234     }
235 }
236 
237 
238 
239 
240 //Edge類  利用深度優先遍歷得到樹的所有路徑以及這些路徑的權值,並根據權值的大小進行從小到大排序
241 class Edge{
242     public int begin;           //這兩個頂點的開始頂點
243     public int end;             //這兩個頂點的結束頂點
244     public int weight;          //兩個頂點之間的權值
245     Edge edge[] = new Edge[15]; //edge數組  圖的邊數沒有傳入,計算最大值
246     Edge(){}
247     public void Edge_1(Graph G){
248         DFSTraverse_1(G);       //得到edge數組
249         sortEdge();             //對dege進行排序
250     }
251     
252     public void SetEdge(int begin,int end,int weight){
253         this.begin = begin;
254         this.end = end;
255         this.weight = weight;
256         
257     }
258     int k=0;        //用於數組賦值是計數
259     //利用深度優先遍歷得到edge數組
260     public void  DFS_1(Graph G,int i,boolean[] visited){
261         int j;
262         
263         visited[i] = true;
264         //System.out.print(G.arc[i][i+1]+"  ");   //打印頂點的值
265         for(j = 0;j < G.verticts;j++){
266             if(G.arc[i][j]>0 && !visited[j]){
267                 //System.out.print(G.arc[i][j]+"  ");
268                 DFS_1(G,j,visited);       //對訪問的鄰接頂點遞歸調用
269             }
270             if(G.arc[i][j] > 0  && i > j){
271                 this.edge[this.k] = new Edge();
272                 edge[this.k].SetEdge(j,i,G.arc[i][j]);
273                 this.k++;
274             }
275             
276         }
277         
278     }
279     public void DFSTraverse_1(Graph  G){
280         boolean[] visited = new boolean[G.verticts];
281         for(int i = 0;i < G.verticts;i++){
282             visited[i] = false;         //初始狀態所有頂點都是未訪問過的狀態
283         }
284         for(int i = 0;i < G.verticts;i++){
285             if(!visited[i])
286                 DFS_1(G,i,visited);       //對未訪問過的頂點調用DFS  如果是連通圖,則只會執行一次
287         }
288     }
289     //對得到的數組進行排序
290     public void sortEdge(){
291         Edge newEdge = new Edge();
292         newEdge.edge[0] = new  Edge();
293         for(int i = 0;i < this.edge.length;i++){
294             for(int j =  i;j < this.edge.length;j++){
295                 if(this.edge[i].weight > this.edge[j].weight){
296                     newEdge.edge[0] = this.edge[i];
297                     this.edge[i] = this.edge[j];
298                     this.edge[j] = newEdge.edge[0];
299                 }
300             }
301         }
302     }
303     //輸出Edge數組,用以測試Edge是否創建、賦值成功
304     public void PrintEdge(){
305         for(int i = 0; i < this.edge.length;i++){
306             System.out.println("數組"+i+":    "+this.edge[i].begin+"  "+this.edge[i].end+"  "+this.edge[i].weight);
307         }
308     }
309 }

 


免責聲明!

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



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