20192313 2020-2021-1 《数据结构与面向对象程序设计》实验九报告
课程:《程序设计与数据结构》
班级: 1923
姓名: 陈宇帆
学号:20192313
实验教师:王志强
实验日期:2020年12月24日
必修/选修: 必修
1.实验内容
(1) 初始化:根据屏幕提示(例如:输入1为无向图,输入2为有向图)初始化无向图和有向图(可用邻接矩阵,也可用邻接表),图需要自己定义(顶点个数、边个数,建议先在草稿纸上画出图,然后再输入顶点和边数)(2分)
(2) 图的遍历:完成有向图和无向图的遍历(深度和广度优先遍历)(4分)
(3) 完成有向图的拓扑排序,并输出拓扑排序序列或者输出该图存在环(3分)
(4) 完成无向图的最小生成树(Prim算法或Kruscal算法均可),并输出(3分)
(5) 完成有向图的单源最短路径求解(迪杰斯特拉算法)(3分)
实验要求
(1)完成代码交到码云上。
(2)提交运行截图。
2. 实验过程及结果
实验代码:
GraphTest
package Task9;
import java.util.Stack;
public class GraphTest {
public static void main(String[] args) {
Graph9 graph = new Graph9();
System.out.println("该图的邻接表为:");
outputGraph(graph);
}
// 输出图的邻接表的方法。
public static void outputGraph(Graph9 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(Graph9 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);
}
}
}
}
Vertex
package Task9;
public class Vertex {
// 节点存储的内容
String verName;
// 顶点的边链
Edge edgeLink;
}
Edge
package Task9;
public class Edge {
//边的尾部节点名称
String tailName;
// 头节点的其他边
Edge broEdge;
}
Edge1
package Task9;
public class Edge1 implements Comparable{
public int i,j,w;
public Edge1(int i,int j,int w){
this.i=i;
this.j=j;
this.w=w;
}
@Override
public int compareTo(Object o) {
Edge1 to=(Edge1)o;
if(this.w>to.w) return 1;
else if(this.w==to.w) return 0;
else return -1;
}
@Override
public String toString()
{
return "start="+i+"||end="+j+"||w="+w;
}
}
Graph9
package Task9;
import java.util.Scanner;
//图类,在构造方法中完成图的构造
public class Graph9 {
//图的节点个数
int verNum;
// 图的边的条数
int edgeNum;
//图的邻接表中存储节点的数组
Vertex[] verArray;
// Graph类的构造方法,依次读取节点、边等信息,完成图的构建。
public Graph9() {
Scanner scan = new Scanner(System.in);
System.out.println("请选择你想要构建有向图还是无向图: (有向图输入0,无向图输入1)");
int choose = scan.nextInt();
System.out.println("请输入节点个数和边的个数:");
verNum = scan.nextInt();
edgeNum = scan.nextInt();
verArray = new Vertex[verNum];
System.out.println("请依次输入节点的名称:");
for (int i=0;i<verNum;i++){
Vertex vertexmem = new Vertex();
vertexmem.verName = scan.next();
vertexmem.edgeLink = null;
verArray[i] = vertexmem;
}
System.out.println("请按(头节点 尾节点 回车)的形式依次输入边的信息:");
for (int i=0;i<edgeNum;i++){
String preName = scan.next();
String folName = scan.next();
Vertex preV = getVertex(preName);
Vertex folV = getVertex(folName);
if (preV == null || folV == null){
System.out.println("输入错误!输入了不存在的顶点!请重新输入:");
i--;
continue;
}
Edge edge = new Edge();
edge.tailName = folName;
//将边加入到节点的链表中去
edge.broEdge = preV.edgeLink;//循环
preV.edgeLink = edge;//放在出始地的后面
//如果加上下面这段,则创建的是无向图,不加则是有向图
if(choose==1){
Edge edgeelse = new Edge();
edgeelse.tailName = preName;
edgeelse.broEdge = folV.edgeLink;
folV.edgeLink = edgeelse;}
}
}
//根据节点名称获取该节点
public Vertex getVertex(String verName){
for (int i=0;i<verNum;i++){
if (verArray[i].verName.equals(verName))
return verArray[i];
}
return null;
}
}
GraphLoopTest
package Task9;
import java.util.*;
/**
* 使用java实现图的图的广度优先 和深度优先遍历算法。
*/
public class GraphLoopTest {
private Map<String, List<String>> graph = new HashMap<String, List<String>>();
/**
* 初始化图数据:使用邻居表来表示图数据。
*/
public void initGraphData() {
// 图结构如下
// 1
// / \
// 2 3
// / \
// 4 5
graph.put("1", Arrays.asList("2", "3"));
graph.put("2", Arrays.asList("1", "4"));
graph.put("3", Arrays.asList("1", "5"));
graph.put("4", Arrays.asList("2"));
graph.put("5", Arrays.asList("3"));
}
/**
* 宽度优先搜索(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("1");}
if(choose==1){
System.out.println("深度优先遍历: ");
test.DFSSearch("1");}
}
}
pim
package Task9;
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={
{1,2,3,4,5,6},
{2,3,4,5,6,7},
{3,4,5,6,7,8},
{4,5,6,7,8,9},
{5,6,7,8,9,10},
{6,7,8,9,10,11}
};
Prim.PRIM(tree, 0, 6);
}
}
ToPo
package Task9;
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");
}
}
}
ToPoTest
package Task9;
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("该图存在环");
e.printStackTrace();
}
}
}
DijstraAlgorithm
package Task9;
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[]{'Q', 'W', 'E', 'R', 'T'};
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;
}
}
}
实验运行结果:
(1) 初始化:
(2) 图的遍历:
(3) 完成有向图的拓扑排序,并输出拓扑排序序列或者输出该图存在环
(4) 完成无向图的最小生成树(Prim算法或Kruscal算法均可),并输出
(5) 完成有向图的单源最短路径求解(迪杰斯特拉算法)
3. 实验过程中遇到的问题和解决过程
实验第五个卡壳了,还好参考了学长代码和方法,重新看了一遍课件。
其他(感悟、思考等)
java最终告一段落了,虽然有很多不舍,但还是顺利完成了所有任务,Java学习真快乐,王老师世界第一!