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;
}

(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的节点进行删除和入队,重复上述操作,直到全部入队

 

 

 


(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;
}
}
}

//print
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();
}
}

 

3. 实验过程中遇到的问题和解决过程

1.拓扑排序的成环情况时,出现断点。后将最开始使用的人为抛出异常

throw new IllegalArgumentException("Has Cycle !");

方法,改为直接输出

 

 

其他(感悟、思考等)

在这次实验进行过程中,我学会了一个很重要的道理,女人何苦为难自己!!!

换个思路说不定他就过了!

最后一次实验了,做的依然很痛苦。。。

我以后的路还有很长,未来的痛苦还有很多,这门课结束了,但学生必将谨记老师教的所有道理,不管是做事或求学,都要学会,该偷懒的偷懒,该努力的绝不偷懒。

愿未来一路顺利。

参考资料



-  《Java程序设计教程(第九版)》

-  《Java软件结构与数据结构(第四版)》

 

posted @ 2021-12-23 15:07  20202301贾奕琦  阅读(25)  评论(0编辑  收藏  举报