实验九
学号 20182334 《数据结构与面向对象程序设计》实验九报告
课程:《程序设计与数据结构》
班级: 1823
姓名: 姬旭
学号:20182334
实验教师:王志强
实验日期:2019年12月1日
必修/选修: 必修
1.实验内容
实验(1)
初始化:根据屏幕提示(例如:输入1为无向图,输入2为有向图)初始化无向图和有向图(可用邻接矩阵,也可用邻接表),图需要自己定义(顶点个数、边个数,建议先在草稿纸上画出图,然后再输入顶点和边数)(2分)
实验(2)
图的遍历:完成有向图和无向图的遍历(深度和广度优先遍历)(4分)
实验(3)
完成有向图的拓扑排序,并输出拓扑排序序列或者输出该图存在环(3分)
实验(4)
完成无向图的最小生成树(Prim算法或Kruscal算法均可),并输出(3分)
实验(5)
完成有向图的单源最短路径求解(迪杰斯特拉算法)(3分)
2. 实验过程及结果
- 实验一创建图,初始化无向图和有向图
//设置边及它的权值
private class B {
int i,w;//设置权值
B nextX; //设置指针
}
//设置顶点
private class N {
char dingdian; // 设置顶点
B firstX;
};
private int Enum; // 边的数量
private N[] mV; // 顶点数组
//创建图
public Sorting(char[] dingdian, EData[] bian) {
int lenv = dingdian.length;
int elen = bian.length;
// 初始化顶点
mV= new N[lenv];
for (int i = 0; i < mV.length; i++) {
mV[i] = new N();
mV[i].dingdian = dingdian[i];
mV[i].firstX = null;
}
// 初始化边
Enum = 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 = gPs(c1);
int p2 = gPs(c2);
B node1 = new B ();
node1.i = p2;
node1.w = weight;
//连接
if(mV[p1].firstX == null)
mV[p1].firstX = node1;
else
Connect(mV[p1].firstX, node1);
B node2 = new B ();
node2.i = p1;
node2.w = weight;
//连接
if(mV[p2].firstX == null)
mV[p2].firstX = node2;
else
Connect(mV[p2].firstX, node2);
}
}
private void Connect(B list, B node) {
B p = list;
while(p.nextX!=null)
p = p.nextX;
p.nextX = node;
}
private int gPs(char ch) {
for(int i=0; i<mV.length; i++)
if(mV[i].dingdian==ch)
return i;
return -1;
}
- 实验二完成图的遍历
private void DFS(int i, boolean[] BL) {
B node;
BL[i] = true;
System.out.printf("%c ", mV[i].dingdian);
node = mV[i].firstX;
while (node != null) {
if (!BL[node.i])
DFS(node.i, BL);
node = node.nextX;
}
}
public void DFS() {
boolean[] BL = new boolean[mV.length];
for (int i = 0; i < mV.length; i++)
BL[i] = false;
for (int i = 0; i < mV.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[mV.length]; // 辅组队列
boolean[] BL = new boolean[mV.length]; // 顶点访问标记
for (int i = 0; i < mV.length; i++)
BL[i] = false;
for (int i = 0; i < mV.length; i++) {
if (!BL[i]) {
BL[i] = true;
System.out.printf("%c ", mV[i].dingdian);
queue[rear++] = i; // 入队列
}
while (head != rear) {
int j = queue[head++]; // 出队列
B node = mV[j].firstX;
while (node != null) {
int k = node.i;
if (!BL[k])
{
BL[k] = true;
System.out.printf("%c ", mV[k].dingdian);
queue[rear++] = k;
}
node = node.nextX;
}
}
}
System.out.printf("\n");
}
- 实验3完成拓扑排序
public int TpSort() {
int index = 0;
int num = mV.length;
int[] ins; // 入度数组
char[] tops;
Queue<Integer> queue;
ins = new int[num];
tops = new char[num];
queue = new LinkedList<Integer>();
// 统计每个顶点的入度数
for(int i = 0; i < num; i++) {
B node = mV[i].firstX;
while (node != null) {
ins[node.i]++;
node = node.nextX;
}
}
// 将所有入度为0的顶点入队列
for(int i = 0; i < num; i ++)
if(ins[i] == 0)
queue.offer(i); // 入队列
while (!queue.isEmpty()) { // 队列非空
int j = queue.poll().intValue(); // 出队列。j是顶点的序号
tops[index++] = mV[j].dingdian;
B node = mV[j].firstX;
while(node != null) {
// 入度减1。
ins[node.i]--;
// 若入度为0,则入队列
if( ins[node.i] == 0)
queue.offer(node.i); // 入队列
node = node.nextX;
}
}
if(index != num) {
System.out.printf("有向有环图\n");
return 1;
}
// 打印拓扑排序结果
System.out.printf("拓扑排序: ");
for(int i = 0; i < num; i ++)
System.out.printf("%c ", tops[i]);
System.out.printf("\n");
return 0;
}
- 实验4用Kruscal算法完成最小生成树
//完成无向图的最小生成树Kruscal算法并输出
//获得权重
private int getWeight(int start, int end) {
if (start==end)
return 0;
B node = mV[start].firstX;
while (node!=null) {
if (end==node.i)
return node.w;
node = node.nextX;
}
return INF;
}
public void kruskal() {
int index = 0;
int[] v = new int[Enum]; // 保存终点。
EData[] rets = new EData[Enum]; // 暂存结果数组
EData[] e; // 对应的所有边
e = getEdges();
// 将边按权排序
sortEdges(e, Enum);
for (int i=0; i<Enum; i++) {
int p1 = gPs(e[i].start);
int p2 = gPs(e[i].end);
int m = getEnd(v, p1);
int n = getEnd(v, p2);
// 如果m!=n,则没有形成环路
if (m != n) {
v[m] = n;
rets[index++] = e[i];
}
}
// print
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");
}
private EData[] getEdges() {
int index=0;
EData[] edges;
edges = new EData[Enum];
for (int i=0; i < mV.length; i++) {
B node = mV[i].firstX;
while (node != null) {
if (node.i > i) {
edges[index++] = new EData(mV[i].dingdian, mV[node.i].dingdian, node.w);
}
node = node.nextX;
}
}
return edges;
}
private void sortEdges(EData[] edges, int elen) {
for (int i=0; i<elen; 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;
}
}
}
}
/*
* 获取i的终点
*/
private int getEnd(int[] vends, int i) {
while (vends[i] != 0)
i = vends[i];
return i;
}
- 实验五用迪杰斯特拉算法算出最短路径
public void dijkstra(int s, int[] q, int[] t) {
// flag[i]=true表示最短路径已成功获取。
boolean[] flag = new boolean[mV.length];
// 初始化
for (int i = 0; i < mV.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 < mV.length; i++) {
// 寻找当前最小的路径;
// 寻找当前最小的路径;
// 寻找当前最小的路径;
int min = INF;
for (int j = 0; j < mV.length; j++) {
if (flag[j]==false && t[j]<min) {
min = t[j];
k = j;
}
}
// 获取到最短路径
flag[k] = true;
for (int j = 0; j < mV.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", mV[s].dingdian);
for (int i = 0; i < mV.length; i++)
System.out.printf(" (%c, %c)=%d\n", mV[s].dingdian, mV[i].dingdian, t[i]);
}
// 边的结构体
private static class EData {
char start; // 边的起点
char end; // 边的终点
int weight; // 边的权重
public EData(char start, char end, int weight) {
this.start = start;
this.end = end;
this.weight = weight;
}
};
3. 实验过程中遇到的问题和解决过程
-
问题1:不知道迪杰斯特拉算法怎么实现。
-
问题1解决方案:参考博客JAVA实现最短距离算法之迪杰斯特拉算法
-
1)判断是否达到终止条件,如果达到终止条件,结束本次算法,如果没有达到,执行下一步;(终止条件:下一次需要计算的节点队列没有数据或已经计算过的节点数等于节点总数)
-
2)获取下一次计算的节点A;
-
3)从起点到各节点之间的最短距离map中获取到达A点的最小距离L;
-
4)获取A节点的可达节点B,计算从起点先到A再到B是否优于已有的其他方式到B,如果优于,则更新B节点,否则不更新;
-
5)判断B是否是已经移除的节点,如果不是移除的节点,把B添加到下一次需要计算的节点队列中,否则不做操作;
-
6)判断A节点是否还有除B以外的其他节点,如果有,执行第4)步,否则执行下一步;
-
7)将A节点从下一次需要计算的节点中移除添加到已经计算过的节点中;
-
8)执行第一步。
-
问题2:不知道Prim算法。
-
问题2解决方案:生成代码如下:
/**
* 最小生成树的prim算法
* @author liuy
*/
public class Prim {
public static void prim(int num, float[][] weight) { //num为顶点数,weight为权
float[] lowcost = new float[num + 1]; //到新集合的最小权
int[] closest = new int[num + 1]; //代表与s集合相连的最小权边的点
boolean[] s = new boolean[num + 1]; //s[i] == true代表i点在s集合中
s[1] = true; //将第一个点放入s集合
for(int i = 2; i <= num; i++) { //初始化辅助数组
lowcost[i] = weight[1][i];
closest[i] = 1;
s[i] = false;
}
for(int i = 1; i < num; i++) {
float min = Float.MAX_VALUE;
int j = 1;
for(int k = 2; k <= num; k++) {
if((lowcost[k] < min) && (!s[k])) {//根据最小权加入新点
min = lowcost[k];
j = k;
}
}
System.out.println("加入点" + j + ". " + j + "---" + closest[j]);//新加入点的j和与j相连的点
s[j] = true;//加入新点j
for(int k = 2; k <= num; k++) {
if((weight[j][k] < lowcost[k]) && !s[k]) {//根据新加入的点j,求得最小权
lowcost[k] = weight[j][k];
closest[k] = j;
}
}
}
}
public static void main(String[] args) {
// ①
// / | /
// 6 1 5
// / | /
// ②-5--③--5--④
// / // /
// 3 6 4 2
// // //
// ⑤--6-⑥
//最小生成树为:
// ①
// |
// 1
// |
// ②-5--③ ④
// / / /
// 3 4 2
// / //
// ⑤ ⑥
//
float m = Float.MAX_VALUE;
float[][] weight = {{0, 0, 0, 0, 0, 0, 0},
{0, m, 6, 1, 5, m, m},
{0, 6, m, 5, m, 3, m},
{0, 1, 5, m, 5, 6, 4},
{0, 5, m, 5, m, m, 2},
{0, m, 3, 6, m, m, 6},
{0, m, m, 4, 2, 6, m}};//上图的矩阵
prim(weight.length - 1, weight);
//加入点3. 3---1
//加入点6. 6---3
//加入点4. 4---6
//加入点2. 2---3
//加入点5. 5---2
}
}
参考博客java实现最小生成树的prim算法和kruskal算法
其他(感悟、思考等)
学习java不易,学习数据结构同样不易。无论是哪种语言,都要经历自学和不断摸索,当然摸索的过程是苦涩的,是很烦人的,是崩溃的,是无助的,但当自己把问题解决了之后,你会发现自己学到了很多知识,是曾经的自己所无法匹敌的,也是自己所期待的。当所有问题都解决之后,永远会有新的问题出现,继续折磨你,如果说你感到无事可做,那种感觉就和死了一样,很恐怖,也会让人堕落。人的一生都是在不断创造问题解决问题,再创造问题的过程中不断前进,否定之否定,会让我们更强。摸索的过程是苦涩的,是很烦人的,是崩溃的,是无助的,但当自己把问题解决了之后,你会发现自己学到了很多知识,是曾经的自己所无法匹敌的,也是自己所期待的。
参考资料
- 《Java程序设计与数据结构教程(第二版)》学习指导
- ...