20202309葛鵬宇《資料結構與面向物件程式設計》實驗九實驗報告
# 學號20202309 2021-2022-1 《資料結構與面向物件程式設計》實驗9報告
課程:《程式設計與資料結構》
班級: 2023
姓名: 葛鵬宇
學號:20202309
實驗教師:王志強
實驗日期:2021年12月16日
必修/選修: 必修
## 1.實驗內容
(1) 初始化:根據螢幕提示(例如:輸入1為無向圖,輸入2為有向圖)初始化無向圖和有向圖(可用鄰接矩陣,也可用鄰接表),圖需要自己定義(頂點個數、邊個數,建議先在草稿紙上畫出圖,然後再輸入頂點和邊數)(2分)
(2) 圖的遍歷:完成有向圖和無向圖的遍歷(深度和廣度優先遍歷)(4分)
(3) 完成有向圖的拓撲排序,並輸出拓撲排序序列或者輸出該圖存在環(3分)
(4) 完成無向圖的最小生成樹(Prim演算法或Kruscal演算法均可),並輸出(3分)
(5) 完成有向圖的單源最短路徑求解(迪傑斯特拉演算法)(3分)
## 2. 實驗過程及結果
碼雲:實驗九 · gpy/gpy20202309 - 碼雲 - 開源中國 (gitee.com)
(1) 初始化:根據螢幕提示(例如:輸入1為無向圖,輸入2為有向圖)初始化無向圖和有向圖(可用鄰接矩陣,也可用鄰接表),圖需要自己定義(頂點個數、邊個數,建議先在草稿紙上畫出圖,然後再輸入頂點和邊數)(2分)
/* 圖的頂點類 */ public class Vertex { String verName; Vertex nextNode; String color; int discoverTime; int finishTime; Vertex parent;int weight; double key; }
/* 自定義圖類 */ public class Graph { Vertex[] vertexArray=new Vertex[100]; int verNum=0; int edgeNum=0; }
import java.util.Scanner; public class CreateGraph { public Vertex getVertex(Graph graph, String str){ for(int i=0;i<graph.verNum;i++){if(graph.vertexArray[i].verName.equals(str)){ return graph.vertexArray[i]; } } return null; } /* 根據使用者輸入的資料初始化一個圖,以鄰接表的形式構建! */ public void initialGraph(Graph graph){ @SuppressWarnings("resource") Scanner scan=new Scanner(System.in); System.out.println("請輸入頂點數和邊數:"); graph.verNum=scan.nextInt(); graph.edgeNum=scan.nextInt(); System.out.println("請輸入無向圖(1)或有向圖(0):"); int choose=scan.nextInt(); System.out.println("請依次輸入頂點名稱:"); for(int i=0;i<graph.verNum;i++){ Vertex vertex=new Vertex(); String name=scan.next(); vertex.verName=name; vertex.nextNode=null; graph.vertexArray[i]=vertex; } System.out.println("請依次輸入圖的邊(頭節點 回車 尾節點):"); for(int i=0;i<graph.edgeNum;i++){ String preV=scan.next(); String folV=scan.next(); System.out.println("----------"); Vertex v1=getVertex(graph,preV); if(v1==null) System.out.println("輸入邊存在圖中沒有的頂點!"); //連結串列操作 Vertex v2=new Vertex(); v2.verName=folV; v2.nextNode=v1.nextNode; v1.nextNode=v2; //下面的程式碼是構建無向圖的 if(choose==1){ Vertex reV2=getVertex(graph,folV); if(reV2==null) System.out.println("輸入邊存在圖中沒有的頂點!"); Vertex reV1=new Vertex(); reV1.verName=preV; reV1.nextNode=reV2.nextNode; reV2.nextNode=reV1; } } } public void outputGraph(Graph graph){ System.out.println("輸出圖的鄰接連結串列為:"); for(int i=0;i<graph.verNum;i++){ Vertex vertex=graph.vertexArray[i]; System.out.print(vertex.verName); Vertex current=vertex.nextNode; while(current!=null){ System.out.print("-->"+current.verName); current=current.nextNode; } System.out.println(); } } public static void main(String[] args) { Graph graph=new Graph(); CreateGraph createGraph=new CreateGraph(); createGraph.initialGraph(graph); createGraph.outputGraph(graph); } }
(2) 圖的遍歷:完成有向圖和無向圖的遍歷(深度和廣度優先遍歷)(4分)
import java.util.*; /*圖的BFS遍歷和DFS遍歷:*/ public class CreateGraph1 { int time=0; Stack<Vertex> stackVertex=new Stack<Vertex>(); public static void main(String[] args) { Graph graph=new Graph(); CreateGraph1 createGraph=new CreateGraph1(); createGraph.initialGraph(graph); createGraph.outputGraph(graph); System.out.println("通過DFS搜尋路徑(遞迴實現)輸入:1"); System.out.println("通過DFS搜尋路徑(棧實現)輸入:2"); System.out.println("通過BFS搜尋路徑輸入:3"); Scanner sc=new Scanner(System.in); int x=sc.nextInt(); if(x==1){ System.out.println("DFS搜尋路徑為(遞迴實現):"); createGraph.DFS(graph); } if(x==2){ System.out.println("DFS搜尋路徑為(棧實現):"); createGraph.stackMain(graph); } if(x==3){ System.out.println("BFS搜尋路徑為:"); createGraph.BFS(graph);} } /* 根據使用者輸入的string型別的頂點返回該頂點 */ public Vertex getVertex(Graph graph,String str){ for(int i=0;i<graph.verNum;i++){ if(graph.vertexArray[i].verName.equals(str)){ return graph.vertexArray[i]; } } return null; } /* 根據使用者輸入的資料初始化一個圖,以鄰接表的形式構建! */ public void initialGraph(Graph graph){ @SuppressWarnings("resource") Scanner scan=new Scanner(System.in); System.out.println("請輸入頂點數和邊數:"); graph.verNum=scan.nextInt(); graph.edgeNum=scan.nextInt(); System.out.println("請輸入無向圖(1)或有向圖(0):"); int choose=scan.nextInt(); System.out.println("請依次輸入頂點名稱:"); for(int i=0;i<graph.verNum;i++){ Vertex vertex=new Vertex(); String name=scan.next(); vertex.verName=name; vertex.color="white"; vertex.discoverTime=0; vertex.finishTime=0; vertex.nextNode=null; graph.vertexArray[i]=vertex; } System.out.println("請依次輸入圖的邊(頭節點 回車 尾節點):"); for(int i=0;i<graph.edgeNum;i++){ String preV=scan.next(); String folV=scan.next(); System.out.println("---------"); Vertex v1=getVertex(graph,preV); if(v1==null) System.out.println("輸入邊存在圖中沒有的頂點!"); //連結串列操作 Vertex v2=new Vertex(); v2.verName=folV; v2.nextNode=v1.nextNode; v1.nextNode=v2; //下面的程式碼是構建無向圖的 if(choose==1){ Vertex reV2=getVertex(graph,folV); if(reV2==null) System.out.println("輸入邊存在圖中沒有的頂點!"); Vertex reV1=new Vertex(); reV1.verName=preV; reV1.nextNode=reV2.nextNode; reV2.nextNode=reV1; } } } /* 輸出圖的鄰接表 */ public void outputGraph(Graph graph){ System.out.println("輸出圖的鄰接連結串列為:"); for(int i=0;i<graph.verNum;i++){ Vertex vertex=graph.vertexArray[i]; System.out.print(vertex.verName); Vertex current=vertex.nextNode; while(current!=null){ System.out.print("-->"+current.verName); current=current.nextNode; } System.out.println(); } } /* DFS遍歷輔助函式,標記顏色是輔助,即根據頂點返回其下標 */ public int index(Vertex vertex,Graph graph){ for(int i=0;i<graph.verNum;i++){ if(vertex.verName.equals(graph.vertexArray[i].verName)) return i; } return -1; } /* DFS深度優先遍歷初始化 */ public void DFS(Graph graph){ for(int i=0;i<graph.verNum;i++){ if(graph.vertexArray[i].color.equals("white")){ DfsVisit(graph.vertexArray[i],graph); System.out.println(); } } } /* DFS遞迴函式 */ public void DfsVisit(Vertex vertex,Graph graph){ vertex.color="gray"; time=time+1; vertex.discoverTime=time; System.out.print(vertex.verName+"-->"); Vertex current=vertex.nextNode; while(current!=null){ Vertex currentNow=getVertex(graph, current.verName); if(currentNow.color.equals("white")) DfsVisit(currentNow,graph); current=current.nextNode; } vertex.color="black"; time=time+1; vertex.finishTime=time; } /* 尋找一個節點的鄰接點中是否還有白色節點 返回白色節點或是null */ public Vertex getAdj(Graph graph,Vertex vertex){ Vertex ver=getVertex(graph, vertex.verName); Vertex current=ver.nextNode; if(current==null) return null; else{ Vertex cur=getVertex(graph, current.verName); while(current!=null && cur.color.equals("gray")){ current=current.nextNode; } if(cur.color.equals("white")){ Vertex currentNow=getVertex(graph, current.verName); return currentNow; }else{ return null; } } } /* 通過棧實現dfs遍歷 */ public void stackOperator(Graph graph,Vertex vertex){ vertex.color="gray"; stackVertex.push(vertex); System.out.print(vertex.verName+"-->"); while(!stackVertex.isEmpty()){ Vertex ver=stackVertex.peek(); Vertex current=getAdj(graph,ver); if(current!=null){ stackVertex.push(current); current.color="gray"; System.out.print(current.verName+"-->"); }else{ stackVertex.pop(); } } } /* DFS遍歷主函式 */ public void stackMain(Graph graph){ for(int i=0;i<graph.verNum;i++){ if(graph.vertexArray[i].color.equals("white")){ stackOperator(graph,graph.vertexArray[i]); System.out.println(); } } } /* BFS廣度優先搜尋實現 */ public void BFS(Graph graph){ Vertex current=graph.vertexArray[0]; current.color="gray"; time=time+1; current.discoverTime=time; Queue<Vertex> queue=new LinkedList<Vertex>(); queue.offer(current); while(queue.peek()!=null){ Vertex ver=queue.poll(); time=time+1; ver.finishTime=time; System.out.print(ver.verName+"-->"); Vertex cur=ver.nextNode; while(cur!=null){ Vertex curNow=getVertex(graph, cur.verName); if(curNow.color.equals("white")){ curNow.color="gray"; time=time+1; curNow.discoverTime=time; queue.offer(curNow); } cur=cur.nextNode; } } System.out.println("null"); } }
(3) 完成有向圖的拓撲排序,並輸出拓撲排序序列或者輸出該圖存在環(3分)
/* 用於儲存頂點物件的連結串列 */ public class VertexNode { Vertex vertexData; VertexNode next; public VertexNode(Vertex ver){ vertexData=ver; } }
public class LinkList { VertexNode first; /* 建構函式,用於初始化頭節點 */ public LinkList(){ this.first=null; } /* 向空連結串列中插入頭結點 */ public void addFirstNode(Vertex vertex){ VertexNode vertexNode=new VertexNode(vertex); vertexNode.next=first; first=vertexNode; } /* 從連結串列尾部插入節點 */ public void addTailNode(Vertex vertex){ VertexNode vertexNode=new VertexNode(vertex); VertexNode current=first; if(current==null){ vertexNode.next=current; first=vertexNode; }else{ VertexNode present=current; while(current!=null){ present=current; current=current.next; } present.next=vertexNode; } } }
import java.util.ArrayList; import java.util.Scanner; public class TopologicalSort { int time; // 下面連結串列用於記錄拓撲序列 LinkList linkList=new LinkList(); /* 根據使用者輸入的string型別的頂點返回該頂點 */ public Vertex getVertex(Graph graph,String str){ for(int i=0;i<graph.verNum;i++){ if(graph.vertexArray[i].verName.equals(str)){ return graph.vertexArray[i]; } } return null; } /** * 根據使用者輸入的資料初始化一個圖,以鄰接表的形式構建! * @param graph 生成的圖 */ public void initialGraph(Graph graph){ @SuppressWarnings("resource") Scanner scan=new Scanner(System.in); System.out.println("請輸入頂點數和邊數:"); graph.verNum=scan.nextInt(); graph.edgeNum=scan.nextInt(); System.out.println("請依次輸入頂點名稱:"); for(int i=0;i<graph.verNum;i++){ Vertex vertex=new Vertex(); String name=scan.next(); vertex.verName=name; vertex.color="white"; vertex.discoverTime=0; vertex.finishTime=0; vertex.parent=null; vertex.nextNode=null; graph.vertexArray[i]=vertex; } System.out.println("請依次輸入圖的邊(頭節點 回車 尾節點):"); for(int i=0;i<graph.edgeNum;i++){ String preV=scan.next(); String folV=scan.next(); System.out.println("---------"); Vertex v1=getVertex(graph,preV); if(v1==null) System.out.println("輸入邊存在圖中沒有的頂點!"); Vertex v2=new Vertex(); v2.verName=folV; v2.nextNode=v1.nextNode; v1.nextNode=v2; } } /* 輸出圖的鄰接表 */ public void outputGraph(Graph graph){ for(int i=0;i<graph.verNum;i++){ Vertex vertex=graph.vertexArray[i]; System.out.print(vertex.verName); Vertex current=vertex.nextNode; while(current!=null){ System.out.print("-->"+current.verName); current=current.nextNode; } System.out.println(); } } /* 利用圖的DFS遍歷完成拓撲排序主函式 */ public void dfsTopoligical(Graph graph){ for(int i=0;i<graph.verNum;i++){ if(graph.vertexArray[i].color.equals("white")){ topological(graph.vertexArray[i],graph); System.out.println(); } } } /* 利用DFS完成圖的拓撲排序輔助函式 */ public void topological(Vertex vertex,Graph graph){ vertex.color="gray"; time=time+1; vertex.discoverTime=time; System.out.print(vertex.verName+"-->"); Vertex current=vertex.nextNode; while(current!=null){ Vertex currentNow=getVertex(graph, current.verName); if(currentNow.color.equals("white")) topological(currentNow,graph); current=current.nextNode; } vertex.color="black"; time=time+1; vertex.finishTime=time; linkList.addFirstNode(vertex); } public static void main(String[] args) { TopologicalSort topologicalSort=new TopologicalSort(); Graph graph=new Graph(); topologicalSort.initialGraph(graph); System.out.println("輸出圖的鄰接連結串列表示為:"); topologicalSort.outputGraph(graph); System.out.println("\n輸出圖的DFS遍歷結果為:"); topologicalSort.dfsTopoligical(graph); System.out.println("\n圖的拓撲排序結果為:"); VertexNode current=topologicalSort.linkList.first; while(current!=null){ System.out.print(current.vertexData.verName+"-->"); current=current.next; } System.out.println("null"); } }
(4) 完成無向圖的最小生成樹(Prim演算法或Kruscal演算法均可),並輸出(3分)
import java.util.Scanner; public class PrimTree { int currentSize=0; int maxSize=0; Vertex[] minHeap=new Vertex[20]; public Vertex getVertex(Graph graph, String str) { for (int i = 0; i < graph.verNum; i++) { if (graph.vertexArray[i].verName.equals(str)) { return graph.vertexArray[i]; } } return null; } /* 根據使用者輸入的資料初始化一個圖,以鄰接表的形式構建! */ public void initialGraph(Graph graph) { @SuppressWarnings("resource") Scanner scan = new Scanner(System.in); System.out.println("請輸入頂點數和邊數:"); graph.verNum = scan.nextInt(); graph.edgeNum = scan.nextInt(); System.out.println("請依次輸入頂點名稱:"); for (int i = 0; i < graph.verNum; i++) { Vertex vertex = new Vertex(); String name = scan.next(); vertex.verName = name; vertex.color = "white"; vertex.discoverTime = 0; vertex.finishTime = 0; vertex.parent = null; vertex.nextNode = null; graph.vertexArray[i] = vertex; } System.out.println("請依次輸入圖的邊(頭節點 尾節點 權值):"); for (int i = 0; i < graph.edgeNum; i++) { String preV = scan.next(); String folV = scan.next(); int weight=scan.nextInt(); System.out.println("---------"); Vertex v1=getVertex(graph,preV); if(v1==null) System.out.println("輸入邊存在圖中沒有的頂點!"); Vertex v2=new Vertex(); v2.verName=folV; v2.weight=weight; v2.nextNode=v1.nextNode; v1.nextNode=v2; Vertex reV2=getVertex(graph,folV); if(reV2==null) System.out.println("輸入邊存在圖中沒有的頂點!"); Vertex reV1=new Vertex(); reV1.verName=preV; reV1.weight=weight; reV1.nextNode=reV2.nextNode; reV2.nextNode=reV1; } } public void outputGraph(Graph graph){ System.out.println("輸出加權圖的鄰接連結串列:"); for(int i=0;i<graph.verNum;i++){ Vertex vertex=graph.vertexArray[i]; System.out.print(vertex.verName); Vertex current=vertex.nextNode; while(current!=null){ System.out.print("-->"+current.verName); current=current.nextNode; } System.out.println(); } } /* 通過weight構建以EdgeNode為節點的最小堆 */ public void createMinHeap(Vertex[] verArray){ currentSize=verArray.length; maxSize=minHeap.length; if(currentSize>=maxSize){ maxSize*=2; minHeap=new Vertex[maxSize]; } for(int i=0;i<currentSize;i++) minHeap[i+1]=verArray[i]; double y; int c; for(int i=currentSize/2;i>=1;i--){ Vertex ver=minHeap[i]; y=ver.key; c=2*i; while(c<=currentSize){ if(c<currentSize && minHeap[c].key>minHeap[c+1].key) c++; if(minHeap[c].key>=y) break; minHeap[c/2]=minHeap[c]; c=c*2; } minHeap[c/2]=ver; } } public Vertex deleteMinHeap(){ if(currentSize<1) System.out.println("堆已經為空!無法執行刪除"); Vertex ver=minHeap[1]; minHeap[1]=minHeap[currentSize]; currentSize-=1; int c=2,j=1; Vertex ver1=minHeap[currentSize+1]; while(c<=currentSize){ if(c<currentSize && minHeap[c].key>minHeap[c+1].key) c++; if(ver1.key<=minHeap[c].key) break; minHeap[j]=minHeap[c]; j=c; c=c*2; } minHeap[j]=ver1; return ver; } /* 返回minHeap中的頂點物件 */ public Vertex getHeapVertex(String name){ for(int i=1;i<=currentSize;i++){ if(minHeap[i].verName.equals(name)) return minHeap[i]; } return null; } /* MST的Prim演算法具體實現函式 */ public void primSpanningTree(Graph graph){ System.out.println("請輸入根節點:"); @SuppressWarnings("resource") Scanner scan=new Scanner(System.in); String root=scan.next(); Vertex verRoot=getVertex(graph,root); verRoot.key=0; Vertex[] verArray=new Vertex[graph.verNum]; for(int i=0;i<graph.verNum;i++){ verArray[i]=graph.vertexArray[i]; } createMinHeap(verArray); System.out.println("利用prim演算法依次加入到MST中的頂點順序為:"); while(currentSize>=1){ Vertex[] vArray=new Vertex[currentSize]; for(int i=0;i<currentSize;i++){ vArray[i]=minHeap[i+1]; } createMinHeap(vArray); Vertex u=deleteMinHeap(); System.out.println(">."+u.verName); Vertex current=u.nextNode; while(current!=null){ Vertex currentNow=getHeapVertex(current.verName); if(currentNow!=null && current.weight<currentNow.key){ currentNow.parent=u; currentNow.key=current.weight; } current=current.nextNode; } } } public static void main(String[] args) { Graph graph=new Graph(); PrimTree create=new PrimTree(); create.initialGraph(graph); create.outputGraph(graph); PrimTree prim=new PrimTree(); prim.primSpanningTree(graph); } }
(5) 完成有向圖的單源最短路徑求解(迪傑斯特拉演算法)(3分)
import java.util.*; public class Dijstra { //不能設定為Integer.MAX_VALUE,否則兩個Integer.MAX_VALUE相加會溢位導致出現負權 public static int MaxValue = 100000; public static void main(String[] args) { Scanner input = new Scanner(System.in); System.out.println("請輸入頂點數和邊數:"); //頂點數 int vertex = input.nextInt(); //邊數 int edge = input.nextInt(); int[][] matrix = new int[vertex][vertex]; //初始化鄰接矩陣 for (int i = 0; i < vertex; i++) { for (int j = 0; j < vertex; j++) { matrix[i][j] = MaxValue; } } for (int i = 0; i < edge; i++) { System.out.println("請輸入第" + (i + 1) + "條邊與其權值(頭節點 尾節點 權值):"); int source = input.nextInt(); int target = input.nextInt(); int weight = input.nextInt(); matrix[source][target] = weight; } //單源最短路徑,源點 System.out.println("請輸入根節點:"); int source = input.nextInt(); //呼叫dijstra演算法計算最短路徑 dijstra(matrix, source); } public static void dijstra(int[][] matrix, int source) { //最短路徑長度 int[] shortest = new int[matrix.length]; //判斷該點的最短路徑是否求出 int[] visited = new int[matrix.length]; //儲存輸出路徑 String[] path = new String[matrix.length]; //初始化輸出路徑 for (int i = 0; i < matrix.length; i++) { path[i] = new String(source + "->" + i); } //初始化源節點 shortest[source] = 0; visited[source] = 1; for (int i = 1; i < matrix.length; i++) { int min = Integer.MAX_VALUE; int index = -1; for (int j = 0; j < matrix.length; j++) { //已經求出最短路徑的節點不需要再加入計算並判斷加入節點後是否存在更短路徑 if (visited[j] == 0 && matrix[source][j] < min) { min = matrix[source][j]; index = j; } } //更新最短路徑 shortest[index] = min; visited[index] = 1; //更新從index跳到其它節點的較短路徑 for (int m = 0; m < matrix.length; m++) { if (visited[m] == 0 && matrix[source][index] + matrix[index][m] < matrix[source][m]) { matrix[source][m] = matrix[source][index] + matrix[index][m]; path[m] = path[index] + "->" + m; } } } //列印最短路徑 for (int i = 0; i < matrix.length; i++) { if (i != source) { if (shortest[i] == MaxValue) { System.out.println(source + "到" + i + "不可達"); } else { System.out.println(source + "到" + i + "的最短路徑為:" + path[i] + ",最短距離是:" + shortest[i]); } } } } }
## 3. 實驗過程中遇到的問題和解決過程
- 問題1:這個實驗需要鍵盤輸入的資訊較多,按錯鍵就需要重新執行編譯引起了不便。
- 問題1解決方案:例如在鍵入頂點間的邊時判斷輸入的是否於之前輸入的頂點名相匹配,如果不匹配就重新進行此次迴圈,不需要重新執行。
## 其他(感悟、思考等)
這次實驗題目邏輯性強,在理解各種演算法的過程上花了較多的時間,很多時候既要頭腦中構造出清晰的邏輯,又要在程式碼中留心每個細節處,讓一個程式執行成功太困難了。
## 參考資料
- [《Java程式設計與資料結構教程(第4版)》]