20182315 邹家伟 实验9实验报告
学号 20182334 《数据结构与面向对象程序设计》实验九报告
课程:《程序设计与数据结构》
班级: 1823
姓名: 邹家伟
学号:20182315
实验教师:王志强
实验日期:2019年12月1日
必修/选修: 必修
1.实验内容
-
初始化:根据屏幕提示(例如:输入1为无向图,输入2为有向图)初始化无向图和有向图(可用邻接矩阵,也可用邻接表),图需要自己定义
-
图的遍历:完成有向图和无向图的遍历
-
完成有向图的拓扑排序,并输出拓扑排序序列或者输出该图存在环。
-
完成无向图的最小生成树(Prim算法或Kruscal算法均可),并输出
-
完成有向图的单源最短路径求解(迪杰斯特拉算法)。
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;
}
- 实验五用迪杰斯特拉算法算出最短路径
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));
3. 实验过程中遇到的问题和解决过程
-
问题1:不知道迪杰斯特拉算法怎么实现。
-
问题1解决方案:
-
(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解决方案:
通过加入最小邻接边的方法来建立最小生成树算法。首先构造一个零图,在选一个初始顶点加入到新集合中,然后分别在原先的顶点集合中抽取一个顶点,使得构成的边为权值最小,然后将该笔边加入到图中,并将抽出的顶点加入到新集合中,重复这个过程,知道新集合等于原先的集合。
其他(感悟、思考等)
图确实是数据结构里最难的部分,不仅难在对图的理解上,还难在对前面学的知识的融会贯通,所以在编写时我特意对前面的知识进行复习,弥补了我之前知识的漏洞,所以,温故而知新。古人诚不欺我