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,明白这是因为函数调用栈太深,对程序进行调整后解决问题。

其他(感悟、思考等)

这部分内容上课讲的比较急、比较少,但是貌似跟我的课程设计内容有关,因此可以说是全程自己编写,耗时很长,在这个过程中我对图的掌握明显得到了提升。

参考资料

posted on 2020-12-24 00:35  王老师铁杆粉  阅读(132)  评论(0编辑  收藏  举报

导航