# 20202323 2021-2022-1 《数据结构与面向对象程序设计》实验九报告
课程:《程序设计与数据结构》
班级: 2023
姓名: 蒙思洋
学号:20202323
实验教师:王志强
实验日期:2021年12月20日
必修/选修: 必修
## 1.实验内容
(1) 初始化:根据屏幕提示(例如:输入1为无向图,输入2为有向图)初始化无向图和有向图(可用邻接矩阵,也可用邻接表),图需要自己定义(顶点个数、边个数,建议先在草稿纸上画出图,然后再输入顶点和边数)(2分)
(2) 图的遍历:完成有向图和无向图的遍历(深度和广度优先遍历)(4分)
(3) 完成有向图的拓扑排序,并输出拓扑排序序列或者输出该图存在环(3分)
(4) 完成无向图的最小生成树(Prim算法或Kruscal算法均可),并输出(3分)
(5) 完成有向图的单源最短路径求解(迪杰斯特拉算法)(3分)
PS:本题12分。目前没有明确指明图的顶点和连通边,如果雷同或抄袭,本次实验0分。
实验报告中要根据所编写的代码解释图的相关算法
## 2. 实验过程及结果
(1)初始化:根据屏幕提示(例如:输入1为无向图,输入2为有向图)初始化无向图和有向图(可用邻接矩阵,也可用邻接表),图需要自己定义(顶点个数、边个数,建议先在草稿纸上画出图,然后再输入顶点和边数)
首先先画出有向图和无向图,如下:
无向图测试:
有向图测试:
package Edit; import java.util.Stack; public class Test { public static void main(String[] args) { Graph graph = new Graph(); System.out.println("该图的邻接表为:"); outputGraph(graph); } /** * 输出图的邻接表的方法。 * @param graph 要输出的图 */ public static void outputGraph(Graph graph){ for (int i=0;i<graph.verNum;i++){ Vertex vertex = graph.verArray[i]; System.out.print(vertex.verName); Edge current = vertex.edgeLink; while (current != null){ System.out.print("-->"+current.tailName); current = current.broEdge; } System.out.println(); } } public static void outputtuota(Graph graph,int[] a){ Stack<Integer> stack = new Stack<>(); for (int i=0;i<graph.verNum;i++){ if(a[i]==0){ System.out.println(i+1); } } } }
(2)图的遍历:完成有向图和无向图的遍历(深度和广度优先遍历)
这个程序主要是将自己所构造的图分别用深度优先遍历与广度优先遍历两种方法遍历一遍,然后接下来是对两种遍历的简单介绍:
深度遍历
广度遍历
package Bianli; import java.util.*; /** * 使用java实现图的图的广度优先 和深度优先遍历算法。 */ public class GraphLoopTest { private Map<String, List<String>> graph = new HashMap<String, List<String>>(); /** * 初始化图数据:使用邻居表来表示图数据。 */ public void initGraphData() { graph.put("1", Arrays.asList("2", "3")); graph.put("2", Arrays.asList("1", "4", "5")); graph.put("3", Arrays.asList("1", "6", "7")); graph.put("4", Arrays.asList("2", "8")); graph.put("5", Arrays.asList("2", "8")); graph.put("6", Arrays.asList("3", "8", "9")); graph.put("7", Arrays.asList("3", "9")); graph.put("8", Arrays.asList("4", "5", "6")); graph.put("9", Arrays.asList("6", "7")); } /** * 宽度优先搜索(BFS, Breadth First Search) * BFS使用队列(queue)来实施算法过程 */ private Queue<String> queue = new LinkedList<String>(); private Map<String, Boolean> status = new HashMap<String, Boolean>(); /** * 开始点 * * @param startPoint */ public void BFSSearch(String startPoint) { //1.把起始点放入queue; queue.add(startPoint); status.put(startPoint, false); bfsLoop(); } private void bfsLoop() { // 1) 从queue中取出队列头的点;更新状态为已经遍历。 String currentQueueHeader = queue.poll(); //出队 status.put(currentQueueHeader, true); System.out.println(currentQueueHeader); // 2) 找出与此点邻接的且尚未遍历的点,进行标记,然后全部放入queue中。 List<String> neighborPoints = graph.get(currentQueueHeader); for (String poinit : neighborPoints) { if (!status.getOrDefault(poinit, false)) { //未被遍历 if (queue.contains(poinit)) continue; queue.add(poinit); status.put(poinit, false); } } if (!queue.isEmpty()) { //如果队列不为空继续遍历 bfsLoop(); } } /** * 深度优先搜索(DFS, Depth First Search) * DFS使用队列(queue)来实施算法过程 * stack具有后进先出LIFO(Last Input First Output)的特性,DFS的操作步骤如下: */ // 1、把起始点放入stack; // 2、重复下述3步骤,直到stack为空为止: // 从stack中访问栈顶的点; // 找出与此点邻接的且尚未遍历的点,进行标记,然后全部放入stack中; // 如果此点没有尚未遍历的邻接点,则将此点从stack中弹出。 private Stack<String> stack = new Stack<String>(); public void DFSSearch(String startPoint) { stack.push(startPoint); status.put(startPoint, true); dfsLoop(); } private void dfsLoop() { if(stack.empty()){ return; } //查看栈顶元素,但并不出栈 String stackTopPoint = stack.peek(); // 2) 找出与此点邻接的且尚未遍历的点,进行标记,然后全部放入queue中。 List<String> neighborPoints = graph.get(stackTopPoint); for (String point : neighborPoints) { if (!status.getOrDefault(point, false)) { //未被遍历 stack.push(point); status.put(point, true); dfsLoop(); } } String popPoint = stack.pop(); System.out.println(popPoint); } public static void main(String[] args) { Scanner scan = new Scanner(System.in); System.out.println("请选择深度(1)还是广度遍历(0)"); int choose = scan.nextInt(); GraphLoopTest test = new GraphLoopTest(); test.initGraphData(); if(choose==0){ System.out.println("广度优先遍历 :"); test.BFSSearch("1");} if(choose==1){ System.out.println("深度优先遍历: "); test.DFSSearch("1");} } }
(3)完成有向图的拓扑排序,并输出拓扑排序序列或者输出该图存在环
package Tuopu; import java.io.*; public class TestTuopuSort { public static void main(String[] args) throws IOException { DirectedGraph directedGraph = new DirectedGraph(); try{ directedGraph.topoSort(); }catch(Exception e){ System.out.println("graph has circle"); e.printStackTrace(); } } }
(4) 完成无向图的最小生成树(Prim算法或Kruscal算法均可),并输出。
package Prim; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.Set; /** * 图的最小树生成算法 * */ public class Prim { /** * 求图最小生成树的PRIM算法 * 基本思想:假设N=(V,{E})是联通网,TE是N上的最想生成树中的变得集合。算法从U={u0}(u0属于V), * TE={}开始,重复执行下述操作:在所有的u属于U,v属于V-U的边(u,v)属于E中找到一条代价最小 * 的边(u0,v0)并入集合TE,同事v0并入U,直至U=V为止。此时TE中必有n-1条边,则T=(V,{TE}) * 为N的最小生成树。 * @param graph 图 * @param start 开始节点 * @param n 图中节点数 */ public static void PRIM(int [][] graph,int start,int n){ int [][] mins=new int [n][2];//用于保存集合U到V-U之间的最小边和它的值,mins[i][0]值表示到该节点i边的起始节点 //值为-1表示没有到它的起始点,mins[i][1]值表示到该边的最小值, //mins[i][1]=0表示该节点已将在集合U中 for(int i=0;i<n;i++){//初始化mins if(i==start){ mins[i][0]=-1; mins[i][1]=0; }else if( graph[start][i]!=-1){//说明存在(start,i)的边 mins[i][0]=start; mins[i][1]= graph[start][i]; }else{ mins[i][0]=-1; mins[i][1]=Integer.MAX_VALUE; } // System.out.println("mins["+i+"][0]="+mins[i][0]+"||mins["+i+"][1]="+mins[i][1]); } for(int i=0;i<n-1;i++){ int minV=-1,minW=Integer.MAX_VALUE; for(int j=0;j<n;j++){//找到mins中最小值,使用O(n^2)时间 if(mins[j][1]!=0&&minW>mins[j][1]){ minW=mins[j][1]; minV=j; } } // System.out.println("minV="+minV); mins[minV][1]=0; System.out.println("最小生成树的第"+i+"条最小边=("+(mins[minV][0]+1)+","+(minV+1)+"),权重="+minW); for(int j=0;j<n;j++){//更新mins数组 if(mins[j][1]!=0){ // System.out.println("MINV="+minV+"||tree[minV][j]="+tree[minV][j]); if( graph[minV][j]!=-1&& graph[minV][j]<mins[j][1]){ mins[j][0]=minV; mins[j][1]= graph[minV][j]; } } } } } public static void main(String [] args){ int [][] tree={ {-1,6,1,5,-1,-1}, {6,-1,5,-1,3,-1}, {1,5,-1,5,6,4}, {5,-1,5,-1,-1,2}, {-1,3,6,-1,-1,6}, {-1,-1,4,2,6,-1} }; Prim.PRIM(tree, 0, 6); } }(5) 完成有向图的单源最短路径求解(迪杰斯特拉算法)
package com.liuzhen.chapter9; public class Dijkstra { /* * 参数adjMatrix:为图的权重矩阵,权值为-1的两个顶点表示不能直接相连 * 函数功能:返回顶点0到其它所有顶点的最短距离,其中顶点0到顶点0的最短距离为0 */ public int[] getShortestPaths(int[][] adjMatrix) { int[] result = new int[adjMatrix.length]; //用于存放顶点0到其它顶点的最短距离 boolean[] used = new boolean[adjMatrix.length]; //用于判断顶点是否被遍历 used[0] = true; //表示顶点0已被遍历 for(int i = 1;i < adjMatrix.length;i++) { result[i] = adjMatrix[0][i]; used[i] = false; } for(int i = 1;i < adjMatrix.length;i++) { int min = Integer.MAX_VALUE; //用于暂时存放顶点0到i的最短距离,初始化为Integer型最大值 int k = 0; for(int j = 1;j < adjMatrix.length;j++) { //找到顶点0到其它顶点中距离最小的一个顶点 if(!used[j] && result[j] != -1 && min > result[j]) { min = result[j]; k = j; } } used[k] = true; //将距离最小的顶点,记为已遍历 for(int j = 1;j < adjMatrix.length;j++) { //然后,将顶点0到其它顶点的距离与加入中间顶点k之后的距离进行比较,更新最短距离 if(!used[j]) { //当顶点j未被遍历时 //首先,顶点k到顶点j要能通行;这时,当顶点0到顶点j的距离大于顶点0到k再到j的距离或者顶点0无法直接到达顶点j时,更新顶点0到顶点j的最短距离 if(adjMatrix[k][j] != -1 && (result[j] > min + adjMatrix[k][j] || result[j] == -1)) result[j] = min + adjMatrix[k][j]; } } } return result; } public static void main(String[] args) { Dijkstra test = new Dijkstra(); int[][] adjMatrix = {{0,6,3,-1,-1,-1}, {6,0,2,5,-1,-1}, {3,2,0,3,4,-1}, {-1,5,3,0,2,3}, {-1,-1,4,2,0,5}, {-1,-1,-1,3,5,0}}; int[] result = test.getShortestPaths(adjMatrix); System.out.println("顶点0到图中所有顶点之间的最短距离为:"); for(int i = 0;i < result.length;i++) System.out.print(result[i]+" "); } }
三. 实验过程中遇到的问题和解决过程
这次是我做过最难的一个实验,很多代码都不会编写,在上网查询代码以及询问同学之后,终于解决了困难。
## 其他(感悟、思考等)
这是本学期最后一次实验,我做得比较认真。这学期的java课也已经结束了,我在java课上学到了许多东西,希望我以后能够牢记老师的教诲,不让自己忘掉java课上学习的宝贵财富。