学号 2019-2020-2314《数据结构与面向对象程序设计》实验九实验报告
课程:《程序设计与数据结构》
班级: 1823
姓名: 鞠明翰
学号:20182314
实验教师:王志强
实验日期:2019年12月2日
必修/选修: 必修
1.实验内容
完成图的综合实践
(1)初始化:根据屏幕提示(例如:输入1为无向图,输入2为有向图)初始化无向图和有向图(可用邻接矩阵,也可用邻接表),图需要自己定义(顶点个数、边个数,建议先在草稿纸上画出图,然后再输入顶点和边数)
(2)图的遍历:完成有向图和无向图的遍历(深度和广度优先遍历)
(3)完成有向图的拓扑排序,并输出拓扑排序序列或者输出该图存在环
(4)完成无向图的最小生成树(Prim算法或Kruscal算法均可),并输出
(5)完成有向图的单源最短路径求解(迪杰斯特拉算法)
2. 实验过程
生成图:
import java.io.*; public class ToPoTest { public static void main(String[] args) throws IOException { ToPo directedGraph = new ToPo(); try{ directedGraph.topoSort(); }catch(Exception e){ System.out.println("graph has circle"); e.printStackTrace(); } } }
package com.company; import java.util.*; /* * 用来实现拓扑排序的有向无环图 */ public class ToPo { private class Vertex{ private String vertexLabel;// 顶点标识 private List<Edge> adjEdges; private int inDegree;// 该顶点的入度 public Vertex(String verTtexLabel) { this.vertexLabel = verTtexLabel; inDegree = 0; adjEdges = new LinkedList<Edge>(); } } private class Edge { private Vertex endVertex; public Edge(Vertex endVertex) { this.endVertex = endVertex; } } private Map<String, Vertex> directedGraph; public ToPo() { directedGraph = new LinkedHashMap<String, ToPo.Vertex>(); buildGraph(); } private void buildGraph(){ Vertex startNode, endNode; String startNodeLabel, endNodeLabel; Edge e; Scanner scan = new Scanner(System.in); System.out.println("请输入有向图的边数:"); int m = scan.nextInt(); for (int i = 0; i <m; i++) { System.out.println("请输入第"+(i+1)+"边头和尾: "); startNodeLabel = scan.next(); endNodeLabel = scan.next(); startNode = directedGraph.get(startNodeLabel); if (startNode == null) { startNode = new Vertex(startNodeLabel); directedGraph.put(startNodeLabel, startNode); } endNode = directedGraph.get(endNodeLabel); if (endNode == null) { endNode = new Vertex(endNodeLabel); directedGraph.put(endNodeLabel, endNode); } e = new Edge(endNode);//每读入一行代表一条边 startNode.adjEdges.add(e);//每读入一行数据,起始顶点添加一条边 endNode.inDegree++;//每读入一行数据,终止顶点入度加1 } } public void topoSort() throws Exception{ int count = 0; Queue<Vertex> queue = new LinkedList<>();// 拓扑排序中用到的栈,也可用队列. //扫描所有的顶点,将入度为0的顶点入队列 Collection<Vertex> vertexs = directedGraph.values(); for (Vertex vertex : vertexs) if(vertex.inDegree == 0) queue.offer(vertex); while(!queue.isEmpty()){ Vertex v = queue.poll(); System.out.print(v.vertexLabel + " "); count++; for (Edge e : v.adjEdges) if(--e.endVertex.inDegree == 0) queue.offer(e.endVertex); } if(count != directedGraph.size()){ throw new Exception("Graph has circle"); } } }
prim算法
public class Prim { public static void PRIM(int [][] graph,int start,int n){ int [][] mins=new int [n][2]; for(int i=0;i<n;i++){ 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; } } for(int i=0;i<n-1;i++){ int minV=-1,minW=Integer.MAX_VALUE; for(int j=0;j<n;j++){ if(mins[j][1]!=0&&minW>mins[j][1]){ minW=mins[j][1]; minV=j; } } mins[minV][1]=0; System.out.println("最小生成树的第"+i+"条最小边=<"+(mins[minV][0]+1)+","+(minV+1)+">,权重="+minW); for(int j=0;j<n;j++){ if(mins[j][1]!=0){ 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={ {3,7,2,7,2,1}, {7,8,6,7,4,2}, {2,6,1,6,7,5}, {6,-1,6,4,-1,3}, {3,4,7,-4,-6,7}, {-7,-2,5,3,7,1} }; Prim.PRIM(tree, 0, 6); } }
DjistraAlgorithm
package com.company; import java.util.ArrayList; import java.util.Arrays; import java.util.Scanner; import java.util.Stack; public class DijstraAlgorithm { public static void main(String[] args) { int vertexNum = 5; char[] vertexs = new char[] { 'Z', 'T', 'H', 'N', 'B' }; int[][] matrix = new int[][] { { 0, 10, Integer.MAX_VALUE / 2, Integer.MAX_VALUE / 2, 5 }, { Integer.MAX_VALUE / 2, 0, 1, Integer.MAX_VALUE / 2, 2 }, { Integer.MAX_VALUE / 2, Integer.MAX_VALUE / 2, 0, 4, Integer.MAX_VALUE / 2 }, { 7, Integer.MAX_VALUE / 2, 6, 0, Integer.MAX_VALUE / 2 }, { Integer.MAX_VALUE / 2, 3, 9, 2, 0 } }; // matrix[i][j]为0表示i==j,matrix[i][j]为Integer.MAX_VALUE/2表示两个顶点不是图的边,否则表示边的权值 Graph g = new Graph(vertexNum, vertexs, matrix); Scanner sc = new Scanner(System.in); int srcIndex; do{ System.out.print("请输入源点索引(0~4):"); srcIndex = sc.nextInt(); }while(srcIndex < 0 || srcIndex > 4); System.out.println(g.vertexs[srcIndex] + "作为源点"); Info info = dijkstra(g, srcIndex); // 指定将索引为srcIndex的顶点作为源点 for(int i : info.pathSerials){ System.out.print(g.vertexs[i] + " "); } System.out.println(); int index = 0; for(int[] path : info.paths){ for(int i : path){ System.out.print(g.vertexs[i]); } System.out.println(": " + info.distances[index++]); } sc.close(); } // 通过迪杰斯特拉(Dijkstra)算法求以vertex[srcIndex]顶点作为源点到其余各顶点的最短路径 public static Info dijkstra(Graph g, int srcIndex) { if(srcIndex < 0 || srcIndex >= g.vertexNum){ return null; } int[] pathSerials = new int[g.vertexNum]; // pathSerials[i]表示从源点到顶点i的最短路径(即若P(srcIndex,j)={V(srcIndex)...Vk...Vs...Vj}是从源点srcIndex到j的最短路径,则有P(srcIndex,j)=P(srcIndex,k)+P(k,s)+P(s,j)) int[] path = new int[g.vertexNum]; // path[i]表示从源点到顶点i(i为vertexs中的索引)的最短路径中顶点i的前驱顶点 int index = 0; pathSerials[index] = srcIndex; // 源点加入序列中 g.visited[srcIndex] = true; // 源点已在最短路径序列中 Arrays.fill(path, -1); // -1表示顶点没有前驱顶点 int[] distances = new int[g.vertexNum]; // distances[i]表示从源点到顶点i(i为vertexs中的索引)的当前最短路径长度 for (int i = 0; i < g.vertexNum; i++) { // 初始化distances为其余顶点到源点的权值 distances[i] = g.matrix[srcIndex][i]; } int minIndex = srcIndex; while (minIndex != -1) { // 仍有未加入到最短路径序列中的顶点 index++; for (int i = 0; i < g.vertexNum; i++) { if (!g.visited[i]) { // 更新仍未加入到最短路径序列中的顶点的从源点到它的值 // 这些仍未加入到最短路径序列中的顶点的distances[i]值为(刚加入的顶点minIndex的distances[minIndex]与minIndex到顶点i之和)与(顶点minIndex刚加入之前源点到i的距离值distances[i])两者之间的较小者 distances[i] = Math.min(distances[i], distances[minIndex] + g.matrix[minIndex][i]); // 如果当前顶点i的distances[i]值为新加入的顶点minIndex,则顶点i的前驱为minIndex,否则不变 if(distances[i] == distances[minIndex] + g.matrix[minIndex][i] && distances[i] != Integer.MAX_VALUE / 2){ // distances[i] != Integer.MAX_VALUE / 2表示仍不可达,就没有前驱 path[i] = minIndex; } } } minIndex = indexOf(g, distances); // 选出的最小顶点 if(minIndex == -1){ break; } pathSerials[index] = minIndex; // 刚选出的最小顶点加入到最短路径序列中 g.visited[minIndex] = true; } return new Info(distances, pathSerials, getPathOfAll(path, pathSerials)); } // 找到图中仍未加入到最短路径序列顶点集中到源点距离最小的顶点的索引 public static int indexOf(Graph g, int[] distances) { int min = Integer.MAX_VALUE / 3; int minIndex = -1; // 当前数组distances剩余元素最小值(-1表示无剩余元素)--剩余元素就是仍未加入到最短路径序列中的顶点 for(int i = 0; i < g.vertexNum; i++){ if(!g.visited[i]){ // 如果i顶点仍未加入到最短路径序列中 if(distances[i] < min){ min = distances[i]; minIndex = i; } } } return minIndex; } // 得到指定顶点i的从源点到顶点i的最短路径(均以顶点集vertexs中索引表示) public static int[] getPath(int[] path, int i){ Stack<Integer> s = new Stack<Integer>(); s.push(i); int pre = path[i]; while(pre != -1){ s.push(pre); pre = path[pre]; } int size = s.size(); int[] pathOfVertex = new int[size]; while(!s.isEmpty()){ pathOfVertex[size - s.size()] = s.pop(); } return pathOfVertex; } public static ArrayList<int[]> getPathOfAll(int[] path, int[] pathSerials){ ArrayList<int[]> paths = new ArrayList<int[]>(); for(int i = 0; i < pathSerials.length; i++){ paths.add(getPath(path, i)); } return paths; } public static class Graph{ private int vertexNum; private char[] vertexs; private int[][] matrix; private boolean visited[]; public Graph(int vertexNum, char[] vertexs, int[][] matrix){ this.vertexNum = vertexNum; this.vertexs = vertexs; this.matrix = matrix; visited = new boolean[vertexNum]; } } public static class Info{ private int[] distances; // 源点到各个顶点的最短距离 private int[] pathSerials; // 整个最短路径序列 private ArrayList<int[]> paths; // 源点到各个顶点的确切最短路径序列 public Info(int[] distances, int[] pathSerials, ArrayList<int[]> paths) { this.distances = distances; this.pathSerials = pathSerials; this.paths = paths; } } }
使用java实现图的图的广度优先 和深度优先遍历算法。
import java.util.*; /** * 使用java实现图的图的广度优先 和深度优先遍历算法。 */ public class GraphLoopTest { private Map<String, List<String>> graph = new HashMap<String, List<String>>(); /** * 初始化图数据:使用邻居表来表示图数据。 */ public void initGraphData() { // 图结构如下 // 3 // / \ // 4 5 // / \ / \ // 6 7 8 9 // \ | / \ / // 10 11 graph.put("3", Arrays.asList("4", "5")); graph.put("4", Arrays.asList("3", "6", "7")); graph.put("5", Arrays.asList("3", "8", "9")); graph.put("6", Arrays.asList("4", "10")); graph.put("7", Arrays.asList("4", "10")); graph.put("8", Arrays.asList("5", "10", "11")); graph.put("9", Arrays.asList("5", "11")); graph.put("10", Arrays.asList("6", "7", "8")); graph.put("11", Arrays.asList("8", "9")); } /** * 宽度优先搜索(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(); } } 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("3");} if(choose==1){ System.out.println("深度优先遍历: "); test.DFSSearch("3");} } }
3. 实验结果
4.实验中遇到的问题
- 问题1:
什么是有向图的单源最短路径求解(迪杰斯特拉算法) - 问题1解决方案:
1)初始化:集合vertex_set初始为{source_vertex},dist数组初始值为dist[i]=G.arc[source_vertex][i],i=0,1,…,n−1
(2)从顶点集合V-vertex_set中选出vj,满足dist[j]=Min{dist[i]|vi∈V−vertex_set},那么vj就是当前求得的一条从source_vertex出发的最短路径的终点,并令vertex_set=vertex_set∪j。
(3)修改从source_vertex出发到集合V-vertex_set上任一顶点vk可达的最短路径长度:如果dist[j]+arc[j][k]<dist[k],则令dist[k]=dist[j]+G.arc[j][k]。
(4)重复(2)~(3)操作共n-1次,直到所有的顶点都包含在vertex_set中。
#include<iostream> #include<unordered_map> #include<queue> #include<cstring> #include<cstdlib> #include<cmath> #include<algorithm> #include<sstream> #include<set> #include<map> using namespace std; #define MAX_NUM 100 #define INF 0x7fffffff /* dijkstra算法的实现 参数说明: 1.source_vertex:表示源点 2.G:表示图(此处以邻接矩阵为例) 3.dist数组:表示源点到其他所有顶点的最短路径的长度。例如dist[j]表示源点到顶点Vj的最短路径长度 4.vertex_set数组:表示已找到最短路径的顶点的集合,其中vertex_set[i] = true表示顶点dist[i]已经最终确定 5.pre数组:用以表示顶点的最短路径的前驱顶点。例如,pre[i] = k表示顶点vi的最短路径上的前驱顶点为vk。 */ class Graph { public: int vertexNum;//顶点个数 int arcNum;//弧的个数 int vertex[MAX_NUM];//顶点表 int arc[MAX_NUM][MAX_NUM] = {{0,INF,10,INF,30,100}, {INF,0,5,INF,INF,INF}, {INF,INF,0,50,INF,INF}, {INF,INF,INF,0,INF,10}, {INF,INF,INF,20,0,60}, {INF,INF,INF,INF,INF,0}};//弧信息表 }; void Dijkstra(Graph &G,int source_vertex,int dist[],bool vertex_set[],int pre[]) { int _min; int k; int vertex_num = G.vertexNum;//顶点个数 //初始化 for(int i = 0 ; i < vertex_num; i++) { dist[i] = G.arc[source_vertex][i];//初始化dist数组 if(i == source_vertex) vertex_set[i] = true;//将顶点source_vertex加入vertex_set数组 else vertex_set[i] = false; pre[i] = 0;//前驱顶点为v0,后面会更新 } //遍历n-1次,每次找到一个顶点的最短路径 for(int i = 1; i < vertex_num; i++) { _min = INF;//初始化辅助变量 //在未获取最短路径的顶点中,找到离source_vertex最近的顶点vk。 for(int j = 0; j < vertex_num; j++) { if(vertex_set[j] == false && dist[j] < _min) { _min = dist[j]; k = j; } } //此时源点到顶点vk的最短距离已经找到,即dist[k]已经确定,将k加入到vertes_set中 vertex_set[k] = true; //对dist[j]进行检验更新 for(int j = 0; j < vertex_num; j++) { int tmp = (G.arc[k][j]== INF ? INF : _min + G.arc[k][j]);//防止溢出 if(vertex_set[j] == false && tmp < dist[j]) { dist[j] = _min + G.arc[k][j];//更新满足条件的顶点的dist数组值 pre[j] = k;//更新前驱顶点 } } } } int main() { Graph G; G.vertexNum = 6; int source_vertex = 0; int dist[6] = {0}; bool vertex_set[6]; int pre[100] = {0}; Dijkstra(G,source_vertex,dist,vertex_set,pre); cout<<dist[0]<<endl; }
5.感悟:
实验终于没了吧!等我把app做完,期末考试考完,我还是爸爸。