20202302 实验九《数据结构与面向对象程序设计》实验报告

课程:《程序设计与数据结构》
班级: 2023
姓名: 吉相融
学号:20202302
实验教师:王志强
实验日期:2021年12月19日
必修/选修: 必修

1.实验内容

(1) 初始化:根据屏幕提示(例如:输入1为无向图,输入2为有向图)初始化无向图和有向图(可用邻接矩阵,也可用邻接表),图需要自己定义(顶点个数、边个数,建议先在草稿纸上画出图,然后再输入顶点和边数)(2分)
(2) 图的遍历:完成有向图和无向图的遍历(深度和广度优先遍历)(4分)
(3) 完成有向图的拓扑排序,并输出拓扑排序序列或者输出该图存在环(3分)
(4) 完成无向图的最小生成树(Prim算法或Kruscal算法均可),并输出(3分)
(5) 完成有向图的单源最短路径求解(迪杰斯特拉算法)(3分)

PS:本题12分。目前没有明确指明图的顶点和连通边,如果雷同或抄袭,本次实验0分。
实验报告中要根据所编写的代码解释图的相关算法

2. 实验过程及结果

先画出用于检验的图:

 加权后:

 

主函数:

 public static void main(String[] args) {
        System.out.println("       输入1构造无向图");
        System.out.println("       输入2构造有向图");

        int select;
        do{
            select = scan.nextInt();
            if(select == 1){
                CreateUndigraph();
            }
            if(select == 2){
                CreateDigraph();
            }
        }while (select != 1 && select != 2);     //避免用户输入其他字符

        System.out.println("初始化,该图的邻接矩阵为:");
        Adjacency_Matrix();         //生成并输出图的邻接矩阵

        origin = search();         //寻找起点

        System.out.println("图的遍历:广度优先遍历:");
        System.out.print(origin + " ");            //出起点,将其标记
        visited[origin - 1] = 1; //被访问的点要做标记,1代表被访问过
        Breadth_Traversal(origin);

        for(int i = 0; i < dot; i++){               //因广度优先遍历时已经访问过所有点,需要重新初始化
            visited[i] = 0;
        }

        System.out.println("\n2.2、图的遍历:深度优先遍历:");
        System.out.print(origin + " ");            //先输出起点并将其标记,进行深度优先遍历
        visited[origin - 1] = 1;
        Depth_Traversal(origin);

        if(select == 1){
            System.out.println("\n4、Prim算法构建无向图的最小生成树:");
            for(int i = 0; i < dot; i++){              //因遍历时已经访问过,此处需要重新初始化
                visited[i] = 0;
            }
            for(int i = 0; i < dot; i++){
                for(int j = 0; j < dot; j++){
                    if(concern[i][j] == 1){
                        System.out.print("请输入" + (i+1) + " " + (j+1) + "两顶点之间边的权值:");
                        concern[i][j] = scan.nextInt();
                        concern[j][i] = concern[i][j];             //对称先置零避免重复运算
                    }
                }
            }
            System.out.print("请输入起始顶点编号:");
            int v = scan.nextInt();
            Prim(concern, v);
            System.out.println("最小权值:" + weight);
        }

        if(select == 2){                   //拓扑和迪杰斯特拉算法可能改变邻接矩阵,因此此处分开进行
            System.out.println("        输入1完成有向图的拓扑排序");
            System.out.println("     输入2完成有向图的单源最短路径求解");

            do{
                select = scan.nextInt();
            }while (select != 1 && select != 2);     //避免用户输入其他字符

            if(select == 1){
                System.out.println("拓扑排序:");
                total2 = 0;
                for(int i = 0; i < dot; i++){              //因之前的方法已经访问过,此处需要重新初始化
                    visited[i] = 0;
                }
                Topology(concern);
            }

            if(select == 2){
                System.out.println("用迪杰斯特拉算法完成有向图的单源最短路径求解:");
                for(int i = 0; i < dot; i++){                        //因遍历时已经访问过,此处需要重新初始化
                    visited[i] = 0;
                }
                System.out.print("请输入起始顶点编号:");
                int v = scan.nextInt();
                for(int i = 0; i < dot; i++){
                    for(int j = 0; j < dot; j++){
                        if(concern[i][j] == 1){
                            System.out.print("请输入" + (i+1) + " " + (j+1) + "两顶点之间边的权值:");
                            concern[i][j] = scan.nextInt();
                        }
                    }
                }

                for(int i = 0; i < dot; i++){
                    dist[i] = concern[v - 1][i];      //记录起始最短距离,0为自身或无穷大
                    if(concern[v - 1][i] != 0){
                        pre[i] = v;                  //记录起始点所连接的点位
                    }
                }

                for(int i = 0; i < dot; i++){                        //先选出距离最短的点
                    if(dist[i] != 0 && dist[i] < temp){
                        temp = dist[i];
                        temp2 = i;
                    }
                }
                concern[v-1][v-1] = 1;              //标记表示已访问
                total2 = 1;                  //初始化并+1
                Dijkstra(concern, temp2 + 1);

                for(int i = 0; i < dot; i++){
                    if(i+1 != v){
                        int tempp = i;
                        while (pre[tempp] != 0){                 //不断访问其上一个点
                            stack2.push(tempp + 1);
                            tempp = pre[tempp] - 1;
                        }
                        System.out.print(v + "到" + (i+1) + "的最短路径为:" + v + " ");
                        while (!stack2.isEmpty()){
                            System.out.print(stack2.pop() + " ");
                        }
                        System.out.println("长度:" + dist[i]);
                    }
                }
            }
        }
    }

 1.1初始化:将无向图存入邻接矩阵:读入顶点数与边数后,用link数组存储后再存入邻接矩阵中,一次可存储位于对称位置上的两个数。各自减一是因为在存储的最后j+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;
        }
    }

 

1.2初始化:将有向图存入邻接矩阵:与构建无向图原理相同。但有向图的存储由于在邻接矩阵中没有对称性,所以一次只能存储一个位置。

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 void Adjacency_Matrix(){
        for(int i = 0; i < dot; i++){
            for(int j = 0; j < dot; j++){
                System.out.print(concern[i][j] + " ");
            }
            System.out.println();
        }
    }

 

 寻找起点:根据已经创建的邻接矩阵,利用其行表示出度,列表示入度的性质,来确定起点。

先判断出度,即当矩阵的一行中有一个1,temp++,由此得出顶点出度作为比较依据;若出度相等,则比较入度,入度小的作为起点。

这里要注意,origin-1是为了规避第一次进行出度比较时可能出错,由此可让出度判断向前推进。

 public static int search(){                      //寻找起点
        int origin = 0, temp = 0, temp2 = 0;         //origin记录起点,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;
    }

 2.1广度优先遍历:利用矩阵遍历,将之前未访问过的邻接点访问并标记,直至每个临界点被标记。把根节点标记,搜索所有在它下一级的元素,把这些元素标记。并把这个元素记为它下一级元素的前驱。找到所要找的元素时结束程序。如果遍历整个树还没有找到,结束程序。

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);                  //递归至遍历完毕
        }
    }

2.2深度优先遍历:利用矩阵遍历,将之前未访问过的邻接点访问并标记,直至每个临界点被标记。把根节点标记,搜索所有在它上一级的元素,把这些元素标记。并把这个元素记为它上一级元素的前驱。找到所要找的元素时结束程序。如果遍历整个树还没有找到,结束程序。

 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); //递归至遍历完毕 } } } }

 3.拓扑排序:先找出入度为零的点,输出该点后,将它的指向全部删除,由此循环往复,一直输出入度较小的点,直至所有点被输出。方法的关键在于删除输出邻接点的指向性,用到栈的一些方法。

   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("存在环,停止拓扑");
        }
    }

4.1 prim算法生成树:

 

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

5.迪杰斯特拉算法算最短路径:

 

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

 运行结果:

1.无向图:

 

 有向图:

 

若存在环,以下图作为检验:

 运行结果:

 

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

1.邻接矩阵一开始在存储无向图的时候,存储不对称(如图),原因是在代码中,有一步将行与列的字母i、j搞反了,直接裂开。。。。。。找了好久问题所在,没想到是这个。

 

 2.构造树以及在寻找起点时,origin一开始我设置为0,导致寻找起点时非常不顺利,起点卡到origin不动了。后来发现如果origin设置为零,那么它的入度一定一定是最小的,根本就不用找了。于是在下面进入循环时设置为origin-1,来规避这个问题。

3.拓扑排序时,去掉输出点的指向性这一步不知如何去做。最后在学长和同学的帮助下利用栈的操作去掉了指向性。

其他(感悟、思考等)

这实验怎么能这么难难难难难难难难难难难,一整个蚌埠住了。完全就是在搞心态,断断续续做了好多天。打辩论还要想着怎么弄图的实验的感受谁能懂????这算法多就算了吧,一个个还贼*********难,出事了调试也耗时间,理解起来也贼费劲。我*****,才把实验搞出来。

不过这也是最后一次实验了,好耶!

这次实验确实很难做,好多实现的理解没跟上,还是在学长的讲解下理解编写的。关于图的内容理解我感觉还好,但是实现就成了难上加难。但是做完实验感觉收获也不小吧。

转眼间本学期的最后一次实验就过去了,我们也成为了最后一批超人带的数据结构与Java课的学生。一学期下来,上Java课感觉还是很欢乐的哈哈哈哈哈哈哈哈哈哈哈哈,而且也对数据结构和Java有了充分理解。最后感谢一学期来同舟共济的同学们,还有我们的超人!祝各位未来一切顺利身体健康哈哈哈哈哈。

我们更高处见!

参考资料



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

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

posted @ 2021-12-23 00:35  20202302吉相融  阅读(80)  评论(0编辑  收藏  举报