20192316 2020-2021-1 《数据结构与面向对象程序设计》实验九报告
20192316 2020-2021-1 《数据结构与面向对象程序设计》实验九报告
课程:《程序设计与数据结构》
班级: 1923
姓名: 贝世之
学号:20192316
实验教师:王志强
实验日期:2020年12月17日
必修/选修: 必修
1.实验内容
(1) 初始化:根据屏幕提示(例如:输入1为无向图,输入2为有向图)初始化无向图和有向图(可用邻接矩阵,也可用邻接表),图需要自己定义(顶点个数、边个数,建议先在草稿纸上画出图,然后再输入顶点和边数)(2分)
(2) 图的遍历:完成有向图和无向图的遍历(深度和广度优先遍历)(4分)
(3) 完成有向图的拓扑排序,并输出拓扑排序序列或者输出该图存在环(3分)
(4) 完成无向图的最小生成树(Prim算法或Kruscal算法均可),并输出(3分)
(5) 完成有向图的单源最短路径求解(迪杰斯特拉算法)(3分)
2. 实验过程及结果
- 定义若干静态变量之后,设置主程序,根据屏幕提示构建无向图或有向图
public static void main(String[] args) {
System.out.println("====================================");
System.out.println(" 输入1构造无向图");
System.out.println(" 输入2构造有向图");
System.out.println("====================================");
int select;
do{
select = scan.nextInt();
if(select == 1){
CreateUndigraph();
}
if(select == 2){
CreateDigraph();
}
}while (select != 1 && select != 2); //避免用户输入其他奇奇怪怪的数字
初始化
- 构建无向图(邻接矩阵)
public static void CreateUndigraph(){
System.out.print("顶点数:");
dot = scan.nextInt();
System.out.print("边数:");
side = scan.nextInt();
int j = 0;
for(int i = 0; i < side; i++){
System.out.print("请输入第" + (i+1) + "条边相连的两点(中间用空格分开):");
link[j] = scan.nextInt();
link[j + 1] = scan.nextInt();
j = j + 2;
}
for(int i = 0; i < j; i = i + 2){
concern[link[i] - 1][link[i+1] - 1] = 1;
concern[link[i+1] - 1][link[i] - 1] = 1;
}
}
- 构建有向图(邻接矩阵)
public static void CreateDigraph(){
System.out.print("顶点数:");
dot = scan.nextInt();
System.out.print("边数:");
side = scan.nextInt();
int j = 0;
for(int i = 0; i < side; i++){
System.out.print("请输入第" + (i+1) + "条边首尾相连的两点(先首后尾,中间用空格分开):");
link[j] = scan.nextInt();
link[j + 1] = scan.nextInt();
j = j + 2;
}
for(int i = 0; i < j; i = i + 2){
concern[link[i] - 1][link[i+1] - 1] = 1;
}
}
- 编写寻找起点的方法,便于接下来的遍历和排序
public static int search(){ //寻找起点
int origin = 0, temp = 0, temp2 = 0; //origin记录起点,temp记录最高的出度,令其值为-1防止直接进入if,temp2记录该temp便于下次比较
for(int i = 0; i < dot; i++){
for(int j = 0; j < dot; j++){
if(concern[i][j] == 1){
temp++;
}
}
if(temp == temp2){ //若出度相等则选择入度较小的点为起点
int temp3 = 0, temp4 = 0; //temp3、temp4分别记录两者的入度数
for(int q = 0; q < dot; q++){
if(concern[q][origin-1] == 0){
temp3++;
}
if(concern[q][i] == 0){
temp4++;
}
}
if(temp3 < temp4){
origin = i + 1;
temp2 = temp;
}
}
if(temp > temp2){
origin = i + 1;
temp2 = temp;
}
temp = 0;
}
return origin;
}
图的遍历
- 广度优先遍历(在其他功能的验证中均有体现)
使用递归的方法完成广度优先遍历,每遍历一个点就将该点标记,使用列表记录遍历的顺序,以便于完成输出。
public static void Breadth_Traversal(int origin){
for(int i = 0; i < dot; i++){
if(concern[origin - 1][i] == 1){ //寻找该点的邻接点
if(visited[i] != 1 ) { //若该点位被访问过,则输出该点
System.out.print((i + 1) + " ");
list.add(i + 1); //降邻接点加入队列
visited[i] = 1; //标记邻接点,表示已访问
}
}
}
while(!list.isEmpty()){
int temp = (int) list.poll();
Breadth_Traversal(temp); //递归至遍历完毕
}
}
- 深度优先遍历(在其他功能的验证中均有体现)
使用递归的方法完成遍历,方法与广度优先遍历类似,但不需要使用列表。
public static void Depth_Traversal(int origin){
for(int i = 0; i < dot; i++){
if(concern[origin - 1][i] == 1){ //寻找该点的第一个邻接点
if(visited[i] != 1 ) { //若该点未被访问过,则输出该点
System.out.print((i + 1) + " ");
visited[i] = 1; //标记邻接点,表示已访问
Depth_Traversal(i + 1); //递归至遍历完毕
}
}
}
}
有向图的拓扑排序
若该图不存在环,则逐个删除入度为0的顶点(入栈)及其出度的边,完成拓扑排序序列输出(出栈),否则输出该图存在环(当不存在入度为0的点且顶点未全部输出时)。
public static void Topology(int AdjMatrix[][]){
int i ,j, total = 0, temp2 = 0; //total记录顶点在矩阵中的行数,temp2记录当前入度为0的顶点的个数
for(j = 0; j < dot; j++){
for (i = 0; i < dot; i++){
if(AdjMatrix[i][j] == 0)
total++;
if(AdjMatrix[i][j] == 1)
break;
}
if(total == dot && visited[j] != 1){ //total=dot时表示入度为0
stack.push(j);
visited[j] = 1;
temp2++;
}
total = 0;
}
while (!stack.isEmpty()){
int temp = (int) stack.pop(); //temp记录该点所指向的点
for(int k = 0; k < dot; k++){
AdjMatrix[temp][k] = 0; //去掉该点时该点的指向全部消失
}
System.out.print((temp+1) + " ");
total2++;
}
if(total2 < dot && temp2 != 0){
Topology(AdjMatrix);
}if(total2 < dot && temp2 == 0){
System.out.println("存在环,停止拓扑");
}
}
- 有向图的拓扑排序,以下图为例,运行截图如下
- 存在环的情况,以下图为例,运行截图如下
用Prim算法完成无向图的最小生成树,并输出
进入该方法前需先输入各边的权值。
我采用的是直接将邻接矩阵的1改成权值的方法,并将已访问的点之间权值改为-1,到最后输出前再改回邻接矩阵的形式,便于直观观察最下生成树,并输出其最小权值weight。
因没有采用老师上课讲的方法,编写该自创的方法用了较长的时间 ┭┮﹏┭┮。
public static void Prim(int AdjMatrix[][], int v){
int temp = 9999, temp2 = 0, temp3 = 0; //temp记录与该点相连的边中最小的权值,temp2记录该点所在列数,temp3记录该点所在行数
visited2[total2] = v;
total2++;
for(int i = 0; i < total2; i++){ //寻找已访问的点中权值最小的邻接边
for(int k = 0; k < dot; k++){
if(AdjMatrix[visited2[i] - 1][k] > 0 && AdjMatrix[visited2[i] - 1][k] < temp && visited[k] != 1){
temp = AdjMatrix[visited2[i] - 1][k];
temp2 = k;
temp3 = visited2[i] - 1;
}
}
}
visited[temp3] = 1;
visited[temp2] = 1;
AdjMatrix[temp3][temp2] = -1; //将已访问的点在矩阵中的权值设为-1,避免重复运算
AdjMatrix[temp2][temp3] = -1;
weight += temp;
if(total2 < dot - 1){
Prim(AdjMatrix, temp2 + 1);
}else {
System.out.println("该最小生成树的图的邻接矩阵为:");
for(int i = 0; i < dot; i++){ //重新转化成邻接矩阵便于直观观察
for(int j = 0; j < dot; j++){
if(AdjMatrix[i][j] == -1){
AdjMatrix[i][j] = 1;
}else {
AdjMatrix[i][j] = 0;
}
System.out.print(AdjMatrix[i][j] + " ");
}
System.out.println();
}
}
}
- 用Prim算法完成无向图的最小生成树,以下图为例,运行截图如下
用迪杰斯特拉算法完成有向图的单源最短路径求解
进入该方法前要先输入起点编号,并用dist[]记录起点到各点的距离,用pre[]记录起点所连接的点位。
遍历的过程实际上是不断更改dist[]和pre[]的过程,通过将矩阵中该点的值改成1表示已遍历。在输出时通过反复对pre[]调用栈完成路径输出,用dist[]输出最短长度。
我在编写该方法时参考了老师给的ppt,但是仍然在调试和改写上花了非常多的时间 (@_@)。
public static void Dijkstra(int AdjMatrix[][], int v){
int temp = 9999, temp2 = 0, j; //temp存权值,temp2存最小权值的边指向的顶点在矩阵中的列数,temp2+1为顶点编号
AdjMatrix[v-1][v-1] = 1;
for (j = 0; j < dot; j++){
if(AdjMatrix[v-1][j] != 0 && j != v-1) {
if(dist[j] == 0 || dist[j] > dist[v-1] + AdjMatrix[v-1][j]) {
dist[j] = dist[v - 1] + AdjMatrix[v - 1][j];
pre[j] = v;
}
}
}
for(int i = 0; i < dot; i++){ //找出下一个距离最下的点
if(dist[i] != 0 && dist[i] < temp && AdjMatrix[i][i] != 1){
temp = dist[i];
temp2 = i;
}
}
total2++;
if(total2 < dot) {
Dijkstra(AdjMatrix, temp2 + 1);
}
}
- 用迪杰斯特拉算法完成有向图的单源最短路径求解,以下图(老师上课用图)为例,运行截图如下
3. 实验过程中遇到的问题和解决过程
-
问题1:用list执行add时抛出java.lang.NullPointerException错误。
-
问题1解决方案:参考对list执行add时,遇到java.lang.NullPointerException将list实例化,解决问题。
-
问题2:在用迪杰斯特拉算法完成有向图的单源最短路径求解时,抛出java.lang.StackOverflowError错误,当时并不知道这是什么玩意。
-
问题2解决方案:参考StackOverflowError,明白这是因为函数调用栈太深,对程序进行调整后解决问题。
其他(感悟、思考等)
这部分内容上课讲的比较急、比较少,但是貌似跟我的课程设计内容有关,因此可以说是全程自己编写,耗时很长,在这个过程中我对图的掌握明显得到了提升。