20192307 2020-2021-1 《数据结构与面向对象程序设计》实验九报告

20192307 2020-2021-1 《数据结构与面向对象程序设计》实验九报告

  • 课程:《数据结构与面向对象程序设计》
  • 班级: 1923
  • 姓名: 常万里
  • 学号: 20192307
  • 实验教师:王志强老师
  • 实验日期:2020年12月29日
  • 必修/选修: 必修

一、实验内容

图的综合实践

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

二、实验过程及结果

本次实验我使用了两种方法去实现实验要求。代码一、代码二、代码三是第一种方法,代码四是同学讲述他的代码思路后,进行的优化调整。

第一种实现方法

Edge.java

package Graph;
/**
 * 这个类代表一条边,有起始节点和终止节点,以及边的长度
 * @author Shape Of My Heart
 */
public class Edge {
    String beginCity;
    String endCity;
    int cost;

    public Edge(String beginCity, String endCity, int cost) {
        super();
        this.beginCity = beginCity;
        this.endCity = endCity;
        this.cost = cost;
    }
}

Graph.java

package Graph;

/**
 * @author Shape Of My Heart
 */
public class Graph {
    public static final int MAX = Integer.MAX_VALUE >>1;
    int size;
    int[][] matrix;
    String[] cityArray;

    /**
     * @param cityArray 代表所有的城市信息
     * @param edges     代表所有的边
     * @param direction true代表构建有向图,false代表无向图
     */
    public Graph(String[] cityArray, Edge[] edges, boolean direction) {
        this.cityArray = cityArray;

        this.size = cityArray.length;

        matrix = new int[size][size];
        for (int i = 0; i < size; i++) {
            for (int j = 0; j < size; j++) {

                if (i == j) {
                    matrix[i][j] = 0;
                } else {
                    matrix[i][j] = Integer.MAX_VALUE;
                }
            }
        }

        for (Edge e : edges) {
            int begin = findIndex(e.beginCity, cityArray);
            int end = findIndex(e.endCity, cityArray);
            matrix[begin][end] = e.cost;
            if (!direction) {
                matrix[end][begin] = e.cost;
            }
        }

    }

    /**
     * 找出指定数组中某个元素的位置,如果找不到,则返回-1
     */
    public int findIndex(String city, String[] cityArray) {
        for (int i = 0; i < cityArray.length; i++) {
            if (city.equals(cityArray[i])) {
                return i;
            }
        }
        return -1;
    }

    public void print() {

        for (int i = 0; i < matrix.length; i++) {
            int[] ii = matrix[i];
            System.out.print(cityArray[i] + "  ");
            for (int j : ii) {
                System.out.printf("%-16d", j);
            }
            System.out.println();
        }
    }

    /**
     * 对图执行深度优先级遍历
     *
     * @param start
     *            遍历其实节点
     * @param visit
     *            保存所有节点的遍历状态
     */
    public void dfs(int start, int[] visit) {
        // 访问当前节点
        System.out.print(cityArray[start] + "  ");
        visit[start] = 1;
        for (int i = 0; i < visit.length; i++) {
            if (matrix[start][i] > 0 && visit[i] == 0) {
                dfs(i, visit);
            }
        }
    }

    /**
     * 对图执行广度优先级遍历
     *
     * @param start
     *            遍历开始节点
     */
    public void bfs(int start) {
        int[] visit = new int[size];
        int[] queue = new int[size];
        int front = -1;
        int tail = -1;

        // 根节点入队
        tail = (tail + 1) % queue.length;
        queue[tail] = start;
        visit[start] = 1;

        while (front != tail) {
            // 根节点出队
            front = (front + 1) % queue.length;
            int index = queue[front];

            // 访问出队节点
            System.out.print(cityArray[index] + "  ");

            for (int i = 0; i < cityArray.length; i++) {
                if (matrix[index][i] != 0 && visit[i] == 0) {
                    // 下一个节点入队
                    tail = (tail + 1) % queue.length;
                    queue[tail] = i;
                    visit[i] = 1;
                }
            }
        }
    }
}

测试代码

Test.java

package Graph;

import java.util.ArrayList;
import java.util.List;

/**
 * @author Shape Of My Heart
 */
public class Test {

    public static void main(String[] args) {

        Graph graph = createGraph(false);
        System.out.println("图的矩阵如下:");
        graph.print();

        System.out.println("\n深度优先遍历顺序如下:");
        int[] visit = new int[graph.size];
        graph.dfs(0, visit);

        System.out.println("\n广度优先遍历顺序如下:");
        graph.bfs(0);
        System.out.println("\n");
        int sum = prim(graph, 0);
        System.out.println("\n最小路径总长度是:" + sum);

        int[] dist = new int[graph.size];
        int[] path = new int[graph.size];
        djst(graph, 0, dist, path);
        System.out.println("\nDijkstra算法--最短路径");
        for(int i=0;i<graph.size;i++) {
            printPath(path, i);
            System.out.println();
        }

    }

    /**
     *
     * @param direction
     *            是否生成有向图
     * @return
     */
    public static Graph createGraph(boolean direction) {
        String[] citys = new String[] { "北京", "上海", "广州", "重庆", "武汉", "南昌" };

        List<Edge> edgeList = new ArrayList<>();
        edgeList.add(new Edge("北京", "广州", 10));
        edgeList.add(new Edge("北京", "上海", 11));
        edgeList.add(new Edge("上海", "南昌", 6));
        edgeList.add(new Edge("广州", "重庆", 14));
        edgeList.add(new Edge("广州", "武汉", 9));
        edgeList.add(new Edge("重庆", "武汉", 20));
        edgeList.add(new Edge("武汉", "北京", 13));
        edgeList.add(new Edge("武汉", "南昌", 12));
        edgeList.add(new Edge("南昌", "广州", 18));

        Edge[] edgeArray = new Edge[edgeList.size()];

        return new Graph(citys, edgeList.toArray(edgeArray), true);
    }

    public static int prim(Graph graph, int start) {
        if (graph != null) {
            int size = graph.size;
            int[] lowCost = new int[size];
            int[] visit = new int[size];

            // 初始化lowCost数组
            for (int i = 0; i < size; i++) {
                lowCost[i] = graph.matrix[start][i];
            }

            // 对进树节点的操作
            StringBuilder builder = new StringBuilder();
            builder.append(graph.cityArray[start]).append(" ");

            visit[start] = 1;

            int sum = 0;

            // 起始节点不需要找,所以我们总共要找(size-1)个节点,故这里n从1开始
            for (int n = 1; n < size; n++) {

                int min = Integer.MAX_VALUE;
                int k = -1;

                // 选出下一个进树的节点
                for (int i = 0; i < size; i++) {
                    if (visit[i] == 0 && lowCost[i] < min) {
                        min = lowCost[i];
                        k = i;
                    }
                }

                builder.append(graph.cityArray[k]).append(" ");
                visit[k] = 1;
                sum += min;

                // 更新剩下节点的lowCost
                for (int i = 0; i < size; i++) {
                    if (visit[i] == 0 && graph.matrix[k][i] < lowCost[i]) {
                        lowCost[i] = graph.matrix[k][i];
                    }
                }

            }

            System.out.println("Prim算法--数的构造顺序如下:");
            System.out.println(builder.toString());
            return sum;
        }

        return 0;
    }
    public static void djst(Graph graph, int start, int[] dist, int[] path) {
        int size = graph.size;
        int[][] matrix = graph.matrix;
        int[] visit = new int[size];

        // 初始化数组path和dist
        for (int i = 0; i < size; i++) {
            dist[i] = matrix[start][i];

            if (matrix[start][i] < Graph.MAX) {
                path[i] = start;
            } else {
                path[i] = -1;
            }
        }

        // 前面的for循环将起始节点的path值设为了start
        // 这里将起始节点的前一个节点为-1
        // 整个Djst算法结束后,只有起始节点的path值为-1
        path[start] = -1;

        visit[start] = 1;

        // 起始节点不需要找,所以我们总共要找(size-1)个节点,故这里n从1开始
        for (int n = 1; n < size; n++) {
            int min = Graph.MAX;
            int k = -1;

            // 找出下一个节点
            for (int i = 0; i < size; i++) {
                if (visit[i] == 0 && dist[i] < min) {
                    k = i;
                    min = dist[i];
                }
            }

            visit[k] = 1;

            // 找到了最短的节点之后,更新剩下节点的dist距离
            for (int i = 0; i < size; i++) {
                if ((visit[i] == 0) && (dist[k] + matrix[k][i] < dist[i])) {
                    dist[i] = dist[k] + matrix[k][i];
                    path[i] = k;
                }
            }
        }

    }

    /**
     * 打印出起始结点到每个节点的路径 path[]数组中保存了一棵双亲存储结构的树,默认输出的是叶子节点到根节点路径,
     *
     * 我们使用了一个栈,从而实现了逆向输出。
     *
     */
    public static void printPath(int[] path, int target) {

        //
        int[] stack = new int[path.length];
        int pos = -1;

        int index = target;

        while (index != -1) {
            stack[++pos] = index;
            index = path[index];
        }

        while (pos!=-1) {
            System.out.print(stack[pos--] + "  ");
        }
    }
}

实验测试截图

(二)第二种实现方法(完整版)

import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;
import java.util.Stack;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author Shape Of My Heart
 */
public class graphTest {
    //设有dot个顶点,顶点序号分别为1、2、3...dot。目前顶点数限定在16个以内。
    static int dot, side;                              //dot为顶点数,side为边数
    static int[][] concern = new int[15][15];          //矩阵信息
    static int[] link = new int[30];                   //用来存续输入的数据
    static int[] visited = new int[15];                //用来标记已遍历的点
    static Scanner scan = new Scanner(System.in);
    static int origin, weight = 0;                     //origin为起点,weight为权重
    static int total2 = 0;                             //记录各方法中已访问的顶点数
    static int temp = 9999, temp2 = 0;
    static int[] visited2 = new int[15];               //按顺序记录Prim算法中被选中的点的编号
    static int[] dist = new int[15];                   //记录最短路径长度
    static int[] pre = new int[15];                    //记录入度来自哪个顶点
    static Queue<Integer> list = new LinkedList<>();
    static Stack<Integer> stack = new Stack<>();
    static Stack<Integer> stack2 = new Stack<>();

    public static void main(String[] args) {

        System.out.println("====================================");
        System.out.println("       输入1构造无向图");
        System.out.println("       输入2构造有向图");
        System.out.println("====================================");

        AtomicInteger select = new AtomicInteger();
        do {
            select.set(scan.nextInt());
            if (select.get() == 1) {
                createUndirectedGraph();
            }
            if (select.get() == 2) {
                createDigraph();
            }
        } while (select.get() != 1 && select.get() != 2);     //避免用户数字不是1或2
    }


    public static void createUndirectedGraph() {
        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;
        }
        System.out.println("1、初始化,该图的邻接矩阵为:");
        adjacencyMatrix();         //生成并输出图的邻接矩阵

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

        System.out.println("2.1、图的遍历:广度优先遍历:");
        System.out.print(origin + " ");            //先输出起点并将其标记,然后进行广度优先遍历
        visited[origin - 1] = 1;
        breadthTraversal(origin);

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

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

        System.out.println("\n4、Prim算法构建无向图的最小生成树:");
        for (int i = 0; i < dot; i++) {              //因遍历时已经访问过,此处需要重新初始化
            visited[i] = 0;
        }
        for (int i = 0; i < dot; i++) {
            for (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);


    }

    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;
        }
        System.out.println("1、初始化,该图的邻接矩阵为:");
        adjacencyMatrix();         //生成并输出图的邻接矩阵

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

        System.out.println("2.1、图的遍历:广度优先遍历:");
        System.out.print(origin + " ");            //先输出起点并将其标记,然后进行广度优先遍历
        visited[origin - 1] = 1;
        breadthTraversal(origin);

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

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


        System.out.println("\n====================================");
        System.out.println("     输入1完成有向图的拓扑排序");
        System.out.println("     输入2完成有向图的单源最短路径求解");
        System.out.println("====================================");
        AtomicInteger select = new AtomicInteger();
        do {
            select.set(scan.nextInt());
        } while (select.get() != 1 && select.get() != 2);     //避免用户输入其他奇奇怪怪的数字

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

        if (select.get() == 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 (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 tempX = i;
                    while (pre[tempX] != 0) {                 //不断访问其上一个点
                        stack2.push(tempX + 1);
                        tempX = pre[tempX] - 1;
                    }
                    System.out.print(v + "到" + (i + 1) + "的最短路径为:" + v + " ");
                    while (!stack2.isEmpty()) {
                        System.out.print(stack2.pop() + " ");
                    }
                    System.out.println("长度:" + dist[i]);
                }
            }
        }
    }

    public static void adjacencyMatrix() {
        for (int i = 0; i < dot; i++) {
            for (int j = 0; j < dot; j++) {
                System.out.print(concern[i][j] + " ");
            }
            System.out.println();
        }
    }

    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 breadthTraversal(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 = list.poll();
            breadthTraversal(temp);                       //递归至遍历完毕
        }
    }

    public static void depthTraversal(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;                       //标记邻接点,表示已访问
                    depthTraversal(i + 1);        //递归至遍历完毕
                }
            }
        }
    }

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

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

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

有向图的拓扑排序,并输出拓扑排序序列或者输出该图存在环



完成无向图的最小生成树,并输出(Prim算法)



完成有向图的单源最短路径求解(迪杰斯特拉算法)

(五)码云仓库地址

我的码云仓库地址

三、心得体会

  • 在这次实验过程中,我遇到了许多问题,其中既有知识上的漏洞,也有不细心导致的马虎,这一切都补充,完善,丰富,扩展了我的计算机知识体系。在不断修复问题的过程中,我使用了很多方式去查询资料,例如:《数据结构与面向对象程序设计》,博客园平台,CSDN平台,码云平台,知乎app,等。进一步熟悉了Android studio这个平台的使用与运行方式,提高了自己自主学习的能力,为我接下来学习数据结构以及JAVA语言程序设计打下了坚实的基础,并在不断探索的过程中逐步提升了自己。

四、参考资料

posted @ 2020-12-30 17:46  20192307常万里  阅读(47)  评论(0编辑  收藏  举报