一、基於鄰接矩陣表示法的無向圖
鄰接矩陣是一種利用一維數組記錄點集信息、二維數組記錄邊集信息來表示圖的表示法,因此我們可以將圖抽象成一個類,點集信息和邊集信息抽象成類的屬性,就可以在Java中描述出來,代碼如下:
1 class AMGraph{ 2 3 private String[] vexs = null; //點集信息 4 5 private int[][] arcs = null; //邊集信息 6 7 }
每一個具體的圖,就是該類的一個實例化對象,因此我們可以在構造函數中實現圖的創建,代碼如下:
1 public AMGraph(int vexNum,int arcNum) { //輸入點的個數和邊的個數 2 3 this.vexs = new String[vexNum]; 4 this.arcs = new int[vexNum][vexNum]; 5 6 System.out.print("請依次輸入頂點值,以空格隔開:"); 7 Scanner sc = new Scanner(System.in); 8 for(int i = 0; i < vexNum; i++) { //根據輸入建立點集 9 this.vexs[i] = sc.next(); 10 } 11 12 for(int i = 0; i < vexNum; i++) { //初始化邊集 13 for(int j = 0; j < vexNum; j++) { 14 this.arcs[i][j] = 0; //0表示該位置所對應的兩頂點之間沒有邊 15 } 16 } 17 18 start:for(int i = 0; i < arcNum; i++) { //開始建立邊集 19 20 sc = new Scanner(System.in); 21 int vex1Site = 0; 22 int vex2Site = 0; 23 String vex1 = null; 24 String vex2 = null; 25 26 System.out.print("請輸入第" + (i+1) + "條邊所依附的兩個頂點,以空格隔開:"); 27 vex1 = sc.next(); 28 vex2 = sc.next(); 29 for(int j = 0; j < this.vexs.length; j++) { //查找輸入的第一個頂點的位置 30 if (this.vexs[j].equals(vex1)) { 31 vex1Site = j; 32 break; 33 } 34 if (j == this.vexs.length - 1) { 35 System.out.println("未找到第一個頂點,請重新輸入!"); 36 i--; 37 continue start; 38 } 39 } 40 for (int j = 0; j < this.vexs.length; j++) { //查找輸入的第二個頂點的位置 41 if(this.vexs[j].equals(vex2)) { 42 vex2Site = j; 43 break; 44 } 45 if (j == this.vexs.length - 1) { 46 System.out.println("未找到第二個頂點,請重新輸入!"); 47 i--; 48 continue start; 49 } 50 } 51 if(this.arcs[vex1Site][vex2Site] != 0) { //檢測該邊是否已經輸入 52 System.out.println("該邊已存在!"); 53 i--; 54 continue start; 55 }else { 56 this.arcs[vex1Site][vex2Site] = 1; //1表示該位置所對應的兩頂點之間有邊 57 this.arcs[vex2Site][vex1Site] = 1; //對稱邊也置1 58 } 59 } 60 System.out.println("基於鄰接矩陣的無向圖創建成功!"); 61 sc.close(); 62 }
創建好圖后,我們還要實現圖的遍歷。由於圖已經被我們抽象成一個類,因此我們可以將圖的遍歷定義成類的方法。對於連通圖,調用遍歷算法后即可訪問所有結點,但對於非連通圖,調用遍歷算法后仍有一些結點沒有被訪問,需要從圖中另選一個未被訪問的頂點再次調用遍歷算法。因此需要附設一個訪問標志數組visited[n],來記錄被訪問的結點。增加了訪問標志數組的類屬性如下:
1 class AMGraph{ 2 3 private String[] vexs = null; 4 5 private int[][] arcs = null; 6 7 private boolean[] visited = null; //false表示該位置的頂點未訪問,true表示已訪問 8 9 }
圖的遍歷分為深度優先遍歷和廣度優先遍歷,接下來我們來分別實現它們。深度優先遍歷代碼如下:
1 public void dFSTraverse() { 2 3 this.visited = new boolean[this.vexs.length]; //初始化訪問標志數組 4 for(int i = 0; i < this.visited.length; i++) { 5 this.visited[i] = false; 6 } 7 8 for(int i = 0; i < this.visited.length; i++) { 9 if(!this.visited[i]) { //對未訪問的頂點調用深度優先遍歷算法 10 dFS_AM(i); 11 } 12 } 13 }
1 public void dFS_AM(int site) { //輸入深度優先遍歷的開始頂點 2 System.out.println(this.vexs[site]); //輸出該頂點 3 this.visited[site] = true; //置訪問標志為true 4 for(int i = 0; i < this.vexs.length; i++) { //依次查找未訪問鄰接點,並以該鄰接點為始點調用深度優先遍歷算法 5 if(this.arcs[site][i] != 0 && !this.visited[i]) { 6 this.dFS_AM(i); 7 } 8 } 9 }
廣度優先遍歷代碼如下:
1 public void bFSTraverse() { 2 3 this.visited = new boolean[this.vexs.length]; //初始化訪問標志數組 4 for(int i = 0; i < this.visited.length; i++) { 5 this.visited[i] = false; 6 } 7 8 for(int i = 0; i < this.visited.length; i++) { 9 if(!this.visited[i]) { //對未訪問的頂點調用廣度優先遍歷算法 10 bFS_AM(i); 11 } 12 } 13 }
1 public void bFS_AM(int site) { //輸入開始頂點 2 System.out.println(this.vexs[site]); //輸出該頂點 3 this.visited[site] = true; //置訪問標志為true 4 LinkedList<Integer> linkedList = new LinkedList<Integer>(); //借助隊列來實現廣度優先遍歷 5 linkedList.offer(site); //將訪問過的頂點入隊 6 while(!linkedList.isEmpty()) { 7 int vexSite = linkedList.poll(); //隊頭頂點出隊 8 for(int i = 0; i < this.vexs.length; i++) { 9 if(this.arcs[vexSite][i] != 0 && !this.visited[i]) { //依次查找未訪問的鄰接點進行訪問后入隊 10 System.out.println(this.vexs[i]); 11 this.visited[i] = true; 12 linkedList.offer(i); 13 } 14 } 15 } 16 }
以上基於鄰接矩陣表示法的無向圖的類就定義好了,接着我們在客戶端里使用即可:
1 public class Main { 2 3 public static void main(String[] args) { 4 5 Scanner sc = new Scanner(System.in); 6 int vexNum = 0; 7 int arcNum = 0; 8 while(true) { 9 System.out.print("請輸入要建立無向圖的總頂點數和總邊數,以空格隔開:"); 10 try { 11 vexNum = sc.nextInt(); 12 arcNum = sc.nextInt(); 13 break; 14 } catch (Exception e) { 15 System.out.println("輸入不合法!"); 16 continue; 17 } 18 } 19 20 AMGraph aMGraph = new AMGraph(vexNum, arcNum); 21 System.out.println("由深度優先遍歷得:"); 22 aMGraph.dFSTraverse(); 23 System.out.println("由廣度優先遍歷得:"); 24 aMGraph.bFSTraverse(); 25 26 sc.close(); 27 } 28 29 }
二、基於鄰接表表示法的無向圖
鄰接表是一種基於鏈式存儲結構的表示法。在鄰接表中,對圖的每個頂點建立一個單鏈表,單鏈表的第一個結點存放頂點信息,稱為點結點,其余結點存放邊信息,稱為邊結點。此外,還需要一個頂點數組,存儲對所有單鏈表的引用。因此我們需要定義三個類,第一個類為頂點類,用來生成點結點;第二個類為邊類,用來生成邊結點;第三個類為圖類,里面定義有屬性——頂點數組和方法——圖的遍歷。代碼如下:
1 class ALGraph_Head{ //頂點類 2 3 private String data = null; //點結點值 4 5 private ALGraph_Arc firstArc= null; //第一條邊的指針 6 7 public String getData() { 8 return data; 9 } 10 11 public ALGraph_Arc getFirstArc() { 12 return firstArc; 13 } 14 15 public void setFirstArc(ALGraph_Arc firstArc) { 16 this.firstArc = firstArc; 17 } 18 19 public ALGraph_Head(String data) { 20 this.data = data; 21 } 22 }
1 class ALGraph_Arc{ //邊類 2 3 private int adjVexSite = 0; //該邊所連接的頂點的鄰接點的位置 4 5 private ALGraph_Arc nextArc = null; //下一條邊的指針 6 7 public int getAdjVexSite() { 8 return adjVexSite; 9 } 10 11 public ALGraph_Arc getNextArc() { 12 return nextArc; 13 } 14 15 public ALGraph_Arc(int adjVexSite, ALGraph_Arc nextArc) { 16 this.adjVexSite = adjVexSite; 17 this.nextArc = nextArc; 18 } 19 }
1 class ALGraph{ //圖類 2 3 private ALGraph_Head[] aLGraph_Heads = null; //頂點數組 4 5 }
同樣的,我們在構造方法中進行圖的建立,代碼如下:
1 public ALGraph(int vexNum,int arcNum) { //輸入頂點個數,邊的個數 2 3 this.aLGraph_Heads = new ALGraph_Head[vexNum]; 4 5 System.out.print("請依次輸入頂點值,以空格隔開:"); 6 Scanner sc = new Scanner(System.in); 7 for(int i = 0; i < vexNum; i++) { //建立頂點數組存儲點結點 8 this.aLGraph_Heads[i] = new ALGraph_Head(sc.next()); 9 } 10 11 start:for(int i = 0; i < arcNum; i++) { //開始存儲邊信息 12 13 sc = new Scanner(System.in); 14 int vex1Site = 0; 15 int vex2Site = 0; 16 String vex1 = null; 17 String vex2 = null; 18 19 System.out.print("請輸入第" + (i+1) + "條邊所依附的兩個頂點,以空格隔開:"); 20 vex1 = sc.next(); 21 vex2 = sc.next(); 22 for(int j = 0; j < this.aLGraph_Heads.length; j++) { //查找第一個頂點的位置 23 if (this.aLGraph_Heads[j].getData().equals(vex1)) { 24 vex1Site = j; 25 break; 26 } 27 if (j == this.aLGraph_Heads.length - 1) { 28 System.out.println("未找到第一個頂點,請重新輸入!"); 29 i--; 30 continue start; 31 } 32 } 33 for (int j = 0; j < this.aLGraph_Heads.length; j++) { //查找第二個頂點的位置 34 if(this.aLGraph_Heads[j].getData().equals(vex2)) { 35 vex2Site = j; 36 break; 37 } 38 if (j == this.aLGraph_Heads.length - 1) { 39 System.out.println("未找到第二個頂點,請重新輸入!"); 40 i--; 41 continue start; 42 } 43 } 44 ALGraph_Arc aLGraph_Arc = this.aLGraph_Heads[vex1Site].getFirstArc(); //獲取點結點里的邊指針 45 while(aLGraph_Arc != null) { //判斷邊是否已存儲 46 if(aLGraph_Arc.getAdjVexSite() == vex2Site) { 47 System.out.println("該邊已存在!"); 48 i--; 49 continue start; 50 } 51 aLGraph_Arc = aLGraph_Arc.getNextArc(); 52 } 53 this.aLGraph_Heads[vex1Site].setFirstArc(new ALGraph_Arc(vex2Site, this.aLGraph_Heads[vex1Site].getFirstArc())); //將邊結點加入單鏈表中 54 this.aLGraph_Heads[vex2Site].setFirstArc(new ALGraph_Arc(vex1Site, this.aLGraph_Heads[vex2Site].getFirstArc())); //對稱邊結點也加入單鏈表 55 } 56 System.out.println("基於鄰接表的無向圖創建成功!"); 57 sc.close(); 58 }
接着實現圖的遍歷,同樣需要附設一個訪問標志數組,因此將圖類屬性修改如下:
1 class ALGraph{ 2 3 private ALGraph_Head[] aLGraph_Heads = null; 4 5 private boolean[] visited = null; //訪問標志數組 6 7 }
深度優先遍歷:
1 public void dFSTraverse() { 2 3 this.visited = new boolean[this.aLGraph_Heads.length]; //建立並初始化訪問標志數組 4 for(int i = 0; i < this.visited.length; i++) { 5 this.visited[i] = false; 6 } 7 8 for(int i = 0; i < this.visited.length; i++) { //以未訪問的點為始點調用深度優先遍歷算法 9 if(!this.visited[i]) { 10 dFS_AM(i); 11 } 12 } 13 }
1 public void dFS_AM(int site) { 2 System.out.println(this.aLGraph_Heads[site].getData()); //輸出點值 3 this.visited[site] = true; //置訪問標志為true 4 ALGraph_Arc aLGraph_Arc = this.aLGraph_Heads[site].getFirstArc(); //獲取點結點中的邊指針 5 while(aLGraph_Arc != null) { 6 if(!visited[aLGraph_Arc.getAdjVexSite()]) { //如果該邊所連接的頂點的鄰接點未訪問,則以該鄰接點為始點調用深度優先遍歷算法 7 this.dFS_AM(aLGraph_Arc.getAdjVexSite()); 8 } 9 aLGraph_Arc = aLGraph_Arc.getNextArc(); //獲取下一條邊 10 } 11 }
廣度優先遍歷:
1 public void bFSTraverse() { 2 3 this.visited = new boolean[this.aLGraph_Heads.length]; //建立並初始化訪問標志數組 4 for(int i = 0; i < this.visited.length; i++) { 5 this.visited[i] = false; 6 } 7 8 for(int i = 0; i < this.visited.length; i++) { //以未訪問的點為始點調用廣度優先遍歷算法 9 if(!this.visited[i]) { 10 bFS_AM(i); 11 } 12 } 13 }
1 public void bFS_AM(int site) { 2 System.out.println(this.aLGraph_Heads[site].getData()); //輸出點值 3 this.visited[site] = true; //置訪問標志為true 4 LinkedList<Integer> linkedList = new LinkedList<Integer>(); //利用隊列實現廣度優先遍歷 5 linkedList.offer(site); //點入隊 6 while(!linkedList.isEmpty()) { 7 int vexSite = linkedList.poll(); //點出隊 8 ALGraph_Arc aLGraph_Arc = this.aLGraph_Heads[vexSite].getFirstArc(); //獲取點結點中的邊指針 9 while(aLGraph_Arc != null) { 10 vexSite = aLGraph_Arc.getAdjVexSite(); //獲取該邊所連接的頂點的鄰接點 11 if(!this.visited[vexSite]) { //如果該鄰接點未訪問,則訪問 12 System.out.println(this.aLGraph_Heads[vexSite].getData()); 13 this.visited[vexSite] = true; 14 linkedList.offer(vexSite); //被訪問的點入隊 15 } 16 aLGraph_Arc = aLGraph_Arc.getNextArc(); //獲取下一個鄰接點 17 } 18 } 19 }
客戶端代碼如下:
1 public class Main { 2 3 public static void main(String[] args) { 4 5 Scanner sc = new Scanner(System.in); 6 int vexNum = 0; 7 int arcNum = 0; 8 while(true) { 9 System.out.print("請輸入要建立無向圖的總頂點數和總邊數,以空格隔開:"); 10 try { 11 vexNum = sc.nextInt(); 12 arcNum = sc.nextInt(); 13 break; 14 } catch (Exception e) { 15 System.out.println("輸入不合法!"); 16 continue; 17 } 18 } 19 20 ALGraph aLGraph = new ALGraph(vexNum, arcNum); 21 System.out.println("由深度優先遍歷得:"); 22 aLGraph.dFSTraverse(); 23 System.out.println("由廣度優先遍歷得:"); 24 aLGraph.bFSTraverse(); 25 26 sc.close(); 27 } 28 29 }
三、小結
以上雖然只實現了無向圖,但其實有向圖、無向網、有向網的建立和遍歷都同理,只需將代碼稍作修改,在邊信息中增加權值信息,用對稱邊記錄反方向的邊即可。