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 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版)》]