最小生成樹-Prim算法(Java實現(xiàn))

概念
  • 圖的生成樹是圖的子圖是尔,并且不形成環(huán)路
  • 最小生成樹是帶權圖中所有生成樹里邊權值總和最小的一個解惊奇,可能不唯一
算法思路
  1. 以圖的一個節(jié)點開始
  2. 找出所有已被訪問的節(jié)點的鄰接節(jié)點中未訪問且邊權值最小的一個節(jié)點以及對應邊互躬,加入樹中
  3. 只要樹的邊數(shù)小于節(jié)點數(shù) - 1就執(zhí)行第二步

算法實現(xiàn)

圖的實現(xiàn)
  • 此實現(xiàn)方法沒有節(jié)點類
  • 采用鄰接矩陣和頂點索引
  • 邊類有兩個成員變量,用于記錄兩個端點的索引int A赊时,int B
  • 使用枚舉來定義節(jié)點的狀態(tài)enum Status { UNDISCOVERD, VISITED }
  • 枚舉數(shù)組Status[] statuses記錄每個節(jié)點的狀態(tài)
  • 鄰接矩陣int[][] matrix(鄰接矩陣無需設置為沿對角線對稱)
    • matrix[i][j]表示從索引i的節(jié)點指向索引j的節(jié)點的權值
    • 權值為0表示兩點不連接或者自身與自身不連接
enum Status {  // 節(jié)點對象的狀態(tài)
    // 未被發(fā)現(xiàn), 已被遍歷
    UNDISCOVERD, VISITED
}
public class Graph<T> {
    private int N; // N個節(jié)點
    public int[][] matrix;  // 鄰接矩陣
    private Status[] statuses;  // 保存每個節(jié)點的狀態(tài)
    private T[] datas;  // 保存每個節(jié)點的數(shù)據(jù)
    class Edge {
        int A;  // 頂點索引
        int B;  // 頂點索引

        public Edge(int a, int b) {
            A = a;
            B = b;
        }

        @Override
        public String toString() {
            return "<" +
                    datas[A] +
                    "-" + matrix[A][B] + "-" + datas[B] +
                    '>';
        }
    }
}
核心步驟
  • 由于最小生成樹的邊數(shù) = 圖節(jié)點數(shù) - 1吨铸,所以只要最終結果的邊集合長度小于節(jié)點數(shù) - 1result.size() < N - 1,就循環(huán)執(zhí)行找出權值最小的節(jié)點以及邊
  • 每次循環(huán)都需要記錄最小權值minWeight祖秒,最小權值的邊minEdge
    • 內(nèi)層循環(huán)遍歷所有已訪問節(jié)點for (int index : visited)
      • 最內(nèi)層循環(huán),先確定當前要找后繼節(jié)點的節(jié)點索引舟奠,也就是for循環(huán)的每一個index竭缝,然后遍歷鄰接矩陣中indexi列,也就是圖中索引index的節(jié)點指向索引i節(jié)點的邊沼瘫,記錄權值weight = matrix[index][i]
        • 如果權值大于0weight > 0 并且后繼節(jié)點未被訪問statuses[i] == Status.UNDISCOVERD并且權重小于當前記錄的最小權值weight < minWeight抬纸,則記錄這條邊minEdge = new Edge(index, i),更新最小權值minWeight = weight
        • 由于Prim算法是基于節(jié)點的耿戚,所以不用像基于邊的Kruskal算法
          一樣判斷是否生成環(huán)湿故,只要選擇未被訪問的節(jié)點,就不會生成環(huán)
    • 每次內(nèi)層循環(huán)就找到了一條最小權值邊膜蛔,以及另一個端點坛猪,于是把最小邊加入集合result.add(minEdge),該端點加入已訪問的節(jié)點集合visited.add(minEdge.B)皂股,設置該點狀態(tài)為已訪問statuses[minEdge.B] = Status.VISITED
        while (result.size() < N - 1) {
            // 記錄最小權值
            int minWeight = Integer.MAX_VALUE;
            // 記錄最小權值對應的邊
            Edge minEdge = null;
            // 遍歷已經(jīng)被訪問過的所有節(jié)點, 查找他們的鄰接節(jié)點
            for (int index : visited) {
                // 循環(huán)遍歷鄰接矩陣中index行j列, 查看權重
                for (int i = 0; i < N; i++) {
                    // 如果當前節(jié)點指向索引j節(jié)點有路徑, 并且索引j節(jié)點未被訪問
                    int weight = matrix[index][i];
                    if (weight > 0 && statuses[i] == Status.UNDISCOVERD && weight < minWeight) {
                        // 如果這個權重比最小權重還小
                        // 記錄這條邊
                        minEdge = new Edge(index, i);
                        // 更新最小權值
                        minWeight = weight;
                    }                }
            }
            // 最小邊加入集合
            result.add(minEdge);
            // 最小邊另一個端點得加入visited
            visited.add(minEdge.B);
            // 設置邊的端點為已訪問
            statuses[minEdge.B] = Status.VISITED;
        }
完整步驟
  • 聲明變量
    • List<Edge> result記錄最小生成樹的邊
    • List<Integer> visited記錄當前已被訪問的節(jié)點索引
  • 將第一個節(jié)點狀態(tài)設置為已被訪問statuses[0] = Status.VISITED
  • 將第一個節(jié)點添加到visited
  • 只要result元素數(shù)量小于節(jié)點數(shù) - 1result.size() < N - 1則循環(huán)執(zhí)行
    • 聲明變量最小權值int minWeight墅茉,最小權值的邊Edge minEdge
    • 遍歷已經(jīng)被訪問過的所有節(jié)點, 查找他們的鄰接節(jié)點
    • 記錄找到的最小權值的節(jié)點,對應邊呜呐,設置節(jié)點狀態(tài)
  • 循環(huán)結束后聲明一個樹的鄰接矩陣就斤,遍歷每條邊for (Edge edge : result)并把樹的鄰接矩陣設置好treeMatrix[edge.A][edge.B] = matrix[edge.A][edge.B]
  • 輸出查看最小生成樹的邊集合result,樹鄰接矩陣treeMatrix
    /**
     * 普利姆算法-最小生成樹
     *
     * @return void
     */
    public void PrimTree() {
        // 記錄最小生成樹的邊的集合
        List<Edge> result = new ArrayList<>();
        // 記錄當前已被訪問的節(jié)點索引(只需要N - 1個)
        List<Integer> visited = new ArrayList<>();
        // 從一個節(jié)點開始
        // 找出與該節(jié)點鄰接的所有節(jié)點中路徑權值最小的
        // 將這兩個節(jié)點的邊記錄下來, 并且節(jié)點設置為已訪問
        statuses[0] = Status.VISITED;
        visited.add(0);
        // 最終結果有N - 1條邊, 不滿足則循環(huán)執(zhí)行
        while (result.size() < N - 1) {
            // 記錄最小權值
            int minWeight = Integer.MAX_VALUE;
            // 記錄最小權值對應的邊
            Edge minEdge = null;
            // 遍歷已經(jīng)被訪問過的所有節(jié)點, 查找他們的鄰接節(jié)點
            for (int index : visited) {
                // 循環(huán)遍歷鄰接矩陣中index行j列, 查看權重
                for (int i = 0; i < N; i++) {
                    // 如果當前節(jié)點指向索引j節(jié)點有路徑, 并且索引j節(jié)點未被訪問
                    int weight = matrix[index][i];
                    if (weight > 0 && statuses[i] == Status.UNDISCOVERD) {
                        // 如果這個權重比最小權重還小
                        if (weight < minWeight) {
                            // 記錄這條邊
                            minEdge = new Edge(index, i);
                            // 更新最小權值
                            minWeight = weight;
                        }
                    }
                }
            }
            // 最小邊加入集合
            result.add(minEdge);
            // 最小邊另一個端點得加入visited
            visited.add(minEdge.B);
            // 設置邊的端點為已訪問
            statuses[minEdge.B] = Status.VISITED;
        }
        // 循環(huán)結束后, 打印最小生成樹鄰接矩陣
        int[][] treeMatrix = new int[N][N];
        for (Edge edge : result) {
            treeMatrix[edge.A][edge.B] = matrix[edge.A][edge.B];
        }
        System.out.println(result);
        System.out.println("最小生成樹的鄰接矩陣: ");
        for (int[] nums : treeMatrix) {
            System.out.println(Arrays.toString(nums));
        }
    }
測試
  • 創(chuàng)建7個節(jié)點的圖new Graph<>(7)
  • 設置圖節(jié)點保存的數(shù)據(jù)為"ABCDEFG"七個字母
  • 初始化鄰接矩陣graph.setMatrix(matrix)
  • 將圖變成無向圖也就是鄰接矩陣沿左對角線對稱graph.makeUndirected()
  • 執(zhí)行Prim算法graph.PrimTree()
    public static void main(String[] args) {
        Graph<String> graph = new Graph<>(7);
        graph.setDatas(new String[]{"A", "B", "C", "D", "E", "F", "G"});
        int[][] matrix = {
                {0, 7, 3, 2, 2, 0, 0},
                {0, 0, 1, 0, 0, 0, 0},
                {0, 0, 0, 0, 4, 3, 0},
                {0, 0, 0, 0, 1, 10, 2},
                {0, 0, 0, 0, 0, 4, 2},
                {0, 0, 0, 0, 0, 0, 7},
                {0, 0, 0, 0, 0, 0, 0}};
        graph.setMatrix(matrix);
        graph.makeUndirected();
        graph.PrimTree();
    }
給定的圖

輸出結果
[<A-2-D>, <D-1-E>, <D-2-G>, <A-3-C>, <C-1-B>, <C-3-F>]
最小生成樹的鄰接矩陣:
[0, 0, 3, 2, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0]
[0, 1, 0, 0, 0, 3, 0]
[0, 0, 0, 0, 1, 0, 2]
[0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0]

最小生成樹
完整代碼
enum Status {  // 節(jié)點對象的狀態(tài)
    // 未被發(fā)現(xiàn), 已被遍歷
    UNDISCOVERD, VISITED
}
public class Graph<T> {
    private int N; // 節(jié)點個數(shù)
    public int[][] matrix;  // 鄰接矩陣
    private T[] datas;  // 保存每個節(jié)點的數(shù)據(jù)
    public List<Edge> edges = new ArrayList<>();  // 邊集合

    class Edge {
        int A;  // 頂點索引
        int B;  // 頂點索引

        public Edge(int a, int b) {
            A = a;
            B = b;
        }

        // 重寫toString()方法方便查看結果
        @Override
        public String toString() {
            return "<" +
                    datas[A] +
                    "-" + matrix[A][B] + "-" + datas[B] +
                    '>';
        }
    }

    public Graph(int N) {
        this.N = N;
        matrix = new int[N][N];
        statuses = new Status[N];
        datas = (T[]) new Object[N];  // 泛型數(shù)組實例化
    }

    /**
     * 鄰接矩陣保存的信息是從一個節(jié)點指向另一個節(jié)點的信息
     *
     * @param from   從這個節(jié)點
     * @param to     指向這個節(jié)點
     * @param weight 路徑權重
     * @return void
     */
    public void setMatrix(int from, int to, int weight) {
        matrix[from][to] = weight;
    }

    /**
     * 重載方法: 用傳進來的矩陣初始化圖的鄰接矩陣
     * @param matrix 傳進來用于初始化鄰接矩陣的矩陣
     * @return void
     */
    public void setMatrix(int[][] matrix) {
        this.matrix = matrix;
    }

    /**
     * 使圖變成無向圖(把鄰接矩陣鏡像化)
     *
     * @return void
     */
    public void makeUndirected() {
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++) {
                if (matrix[i][j] > 0 && matrix[i][j] != matrix[j][i]) {
                    matrix[j][i] = matrix[i][j];
                }
            }
        }
    }

    public void setDatas(T[] datas) {
        this.datas = datas;
    }

    /**
     * 初始化狀態(tài)數(shù)組
     *
     * @return void
     */
    private void initStatuses() {
        for (int i = 0; i < N; i++) {
            statuses[i] = Status.UNDISCOVERD;
        }
    }

    /**
     * 普利姆算法-最小生成樹
     *
     * @return void
     */
    public void PrimTree() {
        // 記錄最小生成樹的邊的集合
        List<Edge> result = new ArrayList<>();
        // 記錄當前已被訪問的節(jié)點索引(只需要N - 1個)
        List<Integer> visited = new ArrayList<>();
        // 從一個節(jié)點開始
        // 找出與該節(jié)點鄰接的所有節(jié)點中路徑權值最小的
        // 將這兩個節(jié)點的邊記錄下來, 并且節(jié)點設置為已訪問
        statuses[0] = Status.VISITED;
        visited.add(0);
        // 最終結果有N - 1條邊, 不滿足則循環(huán)執(zhí)行
        while (result.size() < N - 1) {
            // 記錄最小權值
            int minWeight = Integer.MAX_VALUE;
            // 記錄最小權值對應的邊
            Edge minEdge = null;
            // 遍歷已經(jīng)被訪問過的所有節(jié)點, 查找他們的鄰接節(jié)點
            for (int index : visited) {
                // 循環(huán)遍歷鄰接矩陣中index行j列, 查看權重
                for (int i = 0; i < N; i++) {
                    // 如果當前節(jié)點指向索引j節(jié)點有路徑, 并且索引j節(jié)點未被訪問
                    int weight = matrix[index][i];
                    if (weight > 0 && statuses[i] == Status.UNDISCOVERD && weight < minWeight) {
                        // 如果這個權重比最小權重還小
                        // 記錄這條邊
                        minEdge = new Edge(index, i);
                        // 更新最小權值
                        minWeight = weight;
                    }
                }
            }
            // 最小邊加入集合
            result.add(minEdge);
            // 最小邊另一個端點得加入visited
            visited.add(minEdge.B);
            // 設置邊的端點為已訪問
            statuses[minEdge.B] = Status.VISITED;
        }
        // 循環(huán)結束后, 打印最小生成樹鄰接矩陣
        int[][] treeMatrix = new int[N][N];
        for (Edge edge : result) {
            treeMatrix[edge.A][edge.B] = matrix[edge.A][edge.B];
        }
        System.out.println(result);
        System.out.println("最小生成樹的鄰接矩陣: ");
        for (int[] nums : treeMatrix) {
            System.out.println(Arrays.toString(nums));
        }
    }

    public static void main(String[] args) {
        Graph<String> graph = new Graph<>(7);
        graph.setDatas(new String[]{"A", "B", "C", "D", "E", "F", "G"});
        int[][] matrix = {
                {0, 7, 3, 2, 2, 0, 0},
                {0, 0, 1, 0, 0, 0, 0},
                {0, 0, 0, 0, 4, 3, 0},
                {0, 0, 0, 0, 1, 10, 2},
                {0, 0, 0, 0, 0, 4, 2},
                {0, 0, 0, 0, 0, 0, 7},
                {0, 0, 0, 0, 0, 0, 0}};
        graph.setMatrix(matrix);
        graph.makeUndirected();
        graph.PrimTree();
    }
}
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蘑辑,一起剝皮案震驚了整個濱河市洋机,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌洋魂,老刑警劉巖绷旗,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件喜鼓,死亡現(xiàn)場離奇詭異,居然都是意外死亡刁标,警方通過查閱死者的電腦和手機颠通,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來膀懈,“玉大人顿锰,你說我怎么就攤上這事∑袈В” “怎么了硼控?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長胳赌。 經(jīng)常有香客問我牢撼,道長,這世上最難降的妖魔是什么疑苫? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任熏版,我火速辦了婚禮,結果婚禮上捍掺,老公的妹妹穿的比我還像新娘撼短。我一直安慰自己,他們只是感情好挺勿,可當我...
    茶點故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布曲横。 她就那樣靜靜地躺著,像睡著了一般不瓶。 火紅的嫁衣襯著肌膚如雪禾嫉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天蚊丐,我揣著相機與錄音熙参,去河邊找鬼。 笑死吠撮,一個胖子當著我的面吹牛尊惰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播泥兰,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼弄屡,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了鞋诗?” 一聲冷哼從身側響起膀捷,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎削彬,沒想到半個月后全庸,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體秀仲,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年壶笼,在試婚紗的時候發(fā)現(xiàn)自己被綠了神僵。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡覆劈,死狀恐怖保礼,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情责语,我是刑警寧澤炮障,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布,位于F島的核電站坤候,受9級特大地震影響胁赢,放射性物質發(fā)生泄漏。R本人自食惡果不足惜白筹,卻給世界環(huán)境...
    茶點故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一智末、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧徒河,春花似錦吹害、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽螺男。三九已至棒厘,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間下隧,已是汗流浹背奢人。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留淆院,地道東北人何乎。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像土辩,于是被迫代替她去往敵國和親支救。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,724評論 2 351

推薦閱讀更多精彩內(nèi)容