1. 程式人生 > 實用技巧 >20192307 2020-2021-1 《資料結構與面向物件程式設計》實驗九報告

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語言程式設計打下了堅實的基礎,並在不斷探索的過程中逐步提升了自己。

四、參考資料