20202301 2021-2022-1 《数据结构与面向对象程序设计》实验九报告
课程:《程序设计与数据结构》
班级: 2023
姓名: 贾奕琦
学号:20202301
实验教师:王志强
实验日期:2021年12月19日
必修/选修: 必修
1.实验内容
(1) 初始化:根据屏幕提示(例如:输入1为无向图,输入2为有向图)初始化无向图和有向图(可用邻接矩阵,也可用邻接表),图需要自己定义(顶点个数、边个数,建议先在草稿纸上画出图,然后再输入顶点和边数)(2分)
(2) 图的遍历:完成有向图和无向图的遍历(深度和广度优先遍历)(4分)
(3) 完成有向图的拓扑排序,并输出拓扑排序序列或者输出该图存在环(3分)
(4) 完成无向图的最小生成树(Prim算法或Kruscal算法均可),并输出(3分)
(5) 完成有向图的单源最短路径求解(迪杰斯特拉算法)(3分)
PS:本题12分。目前没有明确指明图的顶点和连通边,如果雷同或抄袭,本次实验0分。
实验报告中要根据所编写的代码解释图的相关算法
2. 实验过程及结果
(1) 初始化:根据屏幕提示(例如:输入1为无向图,输入2为有向图)初始化无向图和有向图(可用邻接矩阵,也可用邻接表),图需要自己定义(顶点个数、边个数,建议先在草稿纸上画出图,然后再输入顶点和边数)(2分)
System.out.println("输入1为无向图,输入2为有向图:");
System.out.print("请输入顶点数:");
int n, m;
char[] a = new char[100];
char[] b=new char[100];
int [] w=new int[100];
Scanner scanner = new Scanner(System.in);
n = scanner.nextInt();
System.out.print("请输入边数:");
m = scanner.nextInt();
char[] vexs = new char[n];
for (int i = 0; i < n; i++) {
System.out.print("请输入第" + (i + 1) + "个顶点");
vexs[i] = scanner.next().charAt(0);
System.out.println();
}
EData[] bian = new EData[m];
for (int j = 0; j <= m-1; j++) {
System.out.print("请输入第" + (j + 1) + "条边的信息");
a[j]= scanner.next().charAt(0);
b[j]= scanner.next().charAt(0);
w[j]= scanner.nextInt();
bian[j]= new EData(a[j], b[j], w[j]);
}
Sorting list;
list = new Sorting(vexs, bian);
实现:
public Sorting(char[] dingdian, EData[] bian) {
int lenv = dingdian.length;//顶点的数量
int elen = bian.length;//边的数量
// 初始化顶点
Node = new N[lenv];
for (int i = 0; i < Node.length; i++) {
Node[i] = new N();
Node[i].dingdian = dingdian[i];
Node[i].firstX = null;
}
// 初始化边
Edge = elen;
for (int i = 0; i < elen; i++) {
char c1 = bian[i].start;
char c2 = bian[i].end;
int weight = bian[i].weight;
int p1 = pG(c1);
int p2 = pG(c2);
Bian node1 = new Bian();
node1.i = p2;
node1.w = weight;
if (Node[p1].firstX == null)
Node[p1].firstX = node1;
else
Connect(Node[p1].firstX, node1);
Bian node2 = new Bian();
node2.i = p1;
node2.w = weight;
if (Node[p2].firstX == null)
Node[p2].firstX = node2;
else
Connect(Node[p2].firstX, node2);
}
}
private void Connect(Bian list, Bian node) {
Bian p = list;
while (p.nextX != null)
p = p.nextX;
p.nextX = node;
}
private int pG(char ch) {
for (int i = 0; i < Node.length; i++)
if (Node[i].dingdian == ch)
return i;
return -1;
}
![](https://img2020.cnblogs.com/blog/2532413/202112/2532413-20211223141139352-549066519.png)
(2) 图的遍历:完成有向图和无向图的遍历(深度和广度优先遍历)(4分)
代码:
//深度优先
private void DFS(int i, boolean[] BL) {
Bian node;
BL[i] = true;
System.out.printf("%c ", Node[i].dingdian);
node = Node[i].firstX;
while (node != null) {
if (!BL[node.i])
DFS(node.i, BL);
node = node.nextX;
}
}
public void DFS() {
boolean[] BL = new boolean[Node.length];
for (int i = 0; i < Node.length; i++)
BL[i] = false;
for (int i = 0; i < Node.length; i++) {
if (!BL[i])
DFS(i, BL);
}
System.out.printf("\n");
}
先遍历一支,将节点直接输出,再返回上一个节点,寻找下一个分支,重新遍历输出
/*
广度优先
*/
public void BFS() {
int head = 0;
int rear = 0;
int[] queue = new int[Node.length];
boolean[] BL = new boolean[Node.length];
for (int i = 0; i < Node.length; i++)
BL[i] = false;
for (int i = 0; i < Node.length; i++) {
if (!BL[i]) {
BL[i] = true;
System.out.printf("%c ", Node[i].dingdian);
queue[rear++] = i; // 入队列
}
while (head != rear) {
int j = queue[head++]; // 出队列
Bian node = Node[j].firstX;
while (node != null) {
int k = node.i;
if (!BL[k]) {
BL[k] = true;
System.out.printf("%c ", Node[k].dingdian);
queue[rear++] = k;
}
node = node.nextX;
}
}
}
System.out.printf("\n");
}
一层层遍历输出
(3) 完成有向图的拓扑排序,并输出拓扑排序序列或者输出该图存在环(3分)
//Kahn算法
private static class KahnTopo {
private List<Node> result; // 用来存储结果集
private Queue<Node> setOfZeroIndegree; // 用来存储入度为0的顶点
private Graph graph;
//构造函数,初始化
public KahnTopo(Graph di) {
this.graph = di;
this.result = new ArrayList<Node>();
this.setOfZeroIndegree = new LinkedList<Node>();
// 对入度为0的集合进行初始化
for(Node iterator : this.graph.vertexSet){
if(iterator.pathIn == 0){
this.setOfZeroIndegree.add(iterator);
}
}
}
//拓扑排序处理过程
private void process() {
while (!setOfZeroIndegree.isEmpty()) {
Node v = setOfZeroIndegree.poll();
// 将当前顶点添加到结果集中
result.add(v);
if(this.graph.adjaNode.keySet().isEmpty()){
return;
}
// 遍历由v引出的所有边
for (Node w : this.graph.adjaNode.get(v) ) {
// 将该边从图中移除,通过减少边的数量来表示
w.pathIn--;
if (0 == w.pathIn) // 如果入度为0,那么加入入度为0的集合
{
setOfZeroIndegree.add(w);
}
}
this.graph.vertexSet.remove(v);
this.graph.adjaNode.remove(v);
}
// 如果此时图中还存在边,那么说明图中含有环路
if (!this.graph.vertexSet.isEmpty()) {
System.out.println("Has Cycle !");
}
}
//结果集
public Iterable<Node> getResult() {
return result;
}
}
对入度为0的节点进行删除与入队操作,再对该节点的所有边进行删除,再对入度为0的节点进行删除和入队,重复上述操作,直到全部入队
![](https://img2020.cnblogs.com/blog/2532413/202112/2532413-20211223142923495-335388369.png)
(4) 完成无向图的最小生成树(Prim算法或Kruscal算法均可),并输出(3分)
public void kruskal(int num) {
int index = 0;
int[] vends = new int[num]; // 用于保存"已有最小生成树"中每个顶点在该最小树中的终点。
EData[] rets = new EData[num]; // 结果数组,保存kruskal最小生成树的边
EData[] edges; // 图对应的所有边
// 获取"图中所有的边"
edges = getEdges();
// 将边按照"权"的大小进行排序(从小到大)
sortEdges(edges, num);
for (int i=0; i<num; i++) {
int p1 = gPs(edges[i].start); // 获取第i条边的"起点"的序号
int p2 = gPs(edges[i].end); // 获取第i条边的"终点"的序号
int m = getEnd(vends, p1); // 获取p1在"已有的最小生成树"中的终点
int n = getEnd(vends, p2); // 获取p2在"已有的最小生成树"中的终点
// 如果m!=n,意味着"边i"与"已经添加到最小生成树中的顶点"没有形成环路
if (m != n) {
vends[m] = n; // 设置m在"已有的最小生成树"中的终点为n
rets[index++] = edges[i]; // 保存结果
}
}
// 统计并打印"kruskal最小生成树"的信息
int length = 0;
for (int i = 0; i < index; i++)
length += rets[i].weight;
System.out.printf("Kruskal=%d: ", length);
for (int i = 0; i < index; i++)
System.out.printf("(%c,%c) ", rets[i].start, rets[i].end);
System.out.printf("\n");
}
// 获取i的终点
private int getEnd(int[] vends, int p1) {
while (vends[p1] != 0)
p1 = vends[p1];
return p1;
}
private void sortEdges(EData[] edges, int elen) {
for (int i = 0; i < elen-1; i++) {
for (int j = i + 1; j < elen; j++) {
if (edges[i].weight > edges[j].weight) {
// 交换"边i"和"边j"
EData tmp = edges[i];
edges[i] = edges[j];
edges[j] = tmp;
}
}
}
}
private EData[] getEdges() {
int index = 0;
EData[] edges;
edges = new EData[Edge];
for (int i = 0; i < Node.length; i++) {
Bian node = Node[i].firstX;
while (node != null) {
if (node.i > i) {
edges[index++] = new EData(Node[i].dingdian, Node[node.i].dingdian, node.w);
}
node = node.nextX;
}
}
return edges;
}
private int getWeight(int start, int end) {
if (start == end)
return 0;
Bian node = Node[start].firstX;
while (node != null) {
if (end == node.i)
return node.w;
node = node.nextX;
}
return INF;
}
private int gPs(char ch) {
for(int i=0; i<Node.length; i++)
if(Node[i].dingdian==ch)
return i;
return -1;
}
对所有边的权值进行比较,将最小的边的两顶点存入数组,重复上述操作,直至所有顶点全部进入数组,且没有环
(5) 完成有向图的单源最短路径求解(迪杰斯特拉算法)(3分)
public void dijkstra(int s, int[] q, int[] t) {
// flag[i]=true→最短路径获取。
boolean[ ] flag = new boolean[Node.length];
// 初始化
for (int i = 0; i < Node.length; i++) {
flag[i] = false;
q[i] = 0; // 顶点i的前一个顶点为0。
t[i] = getWeight(s, i);
}
flag[s] = true;
t[s] = 0;
int k = 0;
for (int i = 1; i < Node.length; i++) {
// 寻找当前最小的路径;
int min = INF;
for (int j = 0; j < Node.length; j++) {
if (flag[j] == false && t[j] < min) {
min = t[j];
k = j;
}
}
// 获取到最短路径
flag[k] = true;
for (int j = 0; j < Node.length; j++) {
int tmp = getWeight(k, j);
tmp = (tmp == INF ? INF : (min + tmp));
if (flag[j] == false && (tmp < t[j])) {
t[j] = tmp;
q[j] = k;
}
}
}
System.out.printf("dijkstra(%c): \n", Node[s].dingdian);
for (int i = 0; i < Node.length; i++)
System.out.printf(" (%c, %c)=%d\n", Node[s].dingdian, Node[i].dingdian, t[i]);
}
初始化dis[start]=0
找出一个与start点距离dis最小的未确定最短路径的点x,标记它为已经确定的点
遍历所有以x为起点的边得到(x, y, d),如果dis[y] > dis[x] + d, 则更新dis[y] = dis[x] + d
重复2,3步直到所有的点都被标记为确定最短路径的点
邻接表:
public linjiebiao_yes(char[] vertexs, char[][] edges) {
size = vertexs.length;
this.vertexLists = new LinkedList[size];
for (int i = 0; i < size; i++) {
this.vertexLists[i] = new LinkedList<Character>();
vertexLists[i].add(vertexs[i]);
}
for (char[] c : edges) {
int p = getPosition(c[0]);
this.vertexLists[p].add(c[1]);
}
}
private int getPosition(char ch) {
for (int i = 0; i < size; i++)
if (vertexLists[i].get(0) == ch)
return i;
return -1;
}
public void print() {
for (int i = 0; i < size; i++) {
LinkedList<Character> temp = vertexLists[i];
for (int j = 0; j < temp.size(); j++) {
System.out.print(temp.get(j));
if (j + 1 < temp.size())
System.out.print("→");
}
System.out.println();
}
}
![](https://img2020.cnblogs.com/blog/2532413/202112/2532413-20211223144330948-1979022798.png)
3. 实验过程中遇到的问题和解决过程
1.拓扑排序的成环情况时,出现断点。后将最开始使用的人为抛出异常
throw new IllegalArgumentException("Has Cycle !");
方法,改为直接输出
其他(感悟、思考等)
在这次实验进行过程中,我学会了一个很重要的道理,女人何苦为难自己!!!
换个思路说不定他就过了!
最后一次实验了,做的依然很痛苦。。。
我以后的路还有很长,未来的痛苦还有很多,这门课结束了,但学生必将谨记老师教的所有道理,不管是做事或求学,都要学会,该偷懒的偷懒,该努力的绝不偷懒。
愿未来一路顺利。
参考资料
- 《Java程序设计教程(第九版)》
- 《Java软件结构与数据结构(第四版)》