圖的最小生成樹算法(Prim和Kruskal)

圖的鄰接矩陣表示法可參考:http://www.lxweimin.com/p/9f27288f6749
測試圖如圖所示:

測試圖.png

普里姆(Prim)算法

思想:先選取一個頂點加入最小生成樹,再選取與該頂點相連的邊中的最小權值對應的頂點加入生成樹,將這兩個頂點作為一棵新的最小生成樹,繼續判斷與該樹相連的邊的最小權值對應的頂點,并將其加入最小生成樹,直到所有頂點均加入生成樹為止。

    //最小生成樹--Prim算法
    //每次都是從lowcost數組中選擇權值最小的邊加入生成樹
    public void MiniSpanTree_Prim(MyGraph graph) {
        int min, i, j, k;
        //保存相關頂點下標
        int[] adjvex = new int[graph.getNumOfVertex()];
        //保存相關頂點間邊的權值
        int[] lowcost = new int[graph.getNumOfVertex()];
        /**
         * lowcost值為0時表示此下標的頂點已經加入最小生成樹,也就是將v0將入生成樹
         * 因為該程序默認從0號下標頂點開始建立最小生成樹,所以將lowcost初始化為0
         * 但最小生成樹的建立并不一定需要從0號下標頂點開始
        **/
        lowcost[0] = 0;
        //初始化第一個頂點的下標為0
        adjvex[0] = 0;
        for(i = 1; i < graph.getNumOfVertex(); i++) {
            //將與v0頂點有邊的權值存入數組
            lowcost[i] = edges[0][i];
            //初始化都為v0的下標
            adjvex[i] = 0;
        }
        for(i = 1; i < graph.getNumOfVertex(); i++) {
            //初始化最小權值,一般設置為一個不可能的大數字,此處是65535
            min = INF;
            //j用來作為頂點下標的循環變量;k用來存儲最小權值頂點的下標
            j = 1; k = 0;
            while(j < graph.getNumOfVertex()) {
                //如果權值不為0且權值小于min
                if (lowcost[j] != 0 && lowcost[j] < min) {
                    //讓當前權值稱為最小值
                    min = lowcost[j];
                    //將當前最小值的下標存入k
                    k = j;
                }
                j++;
            }
            //輸出當前頂點邊中權值最小的邊
            System.out.println("("+adjvex[k]+","+k+")");
            //將當前頂點的權值設置為0,表示此頂點已經加入生成樹
            lowcost[k] = 0;
            for(j =1; j < graph.getNumOfVertex(); j++) {
                //若下標為k的頂點各邊權值小于此前這些頂點未被加入生成樹權值
                if (lowcost[j] != 0 && edges[k][j] < lowcost[j]) {
                    //將較小權值存入lowcost
                    lowcost[j] = edges[k][j];
                    //將下標為k的頂點存入adjvex
                    adjvex[j] = k;
                }
            }
        }
    }

測試程序

        int n = 9;
        String[] vertex = {"v0","v1","v2","v3","v4","v5","v6","v7","v8"};
        MyGraph graph = new MyGraph(n);
        for (String string : vertex) {
            graph.insertVertex(string);
        }
        
        
        graph.insertEdge(0, 1, 10);
        graph.insertEdge(0, 5, 11);
        graph.insertEdge(1, 2, 18);     
        graph.insertEdge(1, 6, 16);
        graph.insertEdge(1, 8, 12);
        graph.insertEdge(2, 8, 8);
        graph.insertEdge(2, 3, 22);
        graph.insertEdge(3, 8, 21);
        graph.insertEdge(3, 6, 24);
        graph.insertEdge(3, 4, 20);
        graph.insertEdge(3, 7, 16);
        graph.insertEdge(4, 5, 26);
        graph.insertEdge(4, 7, 7);
        graph.insertEdge(5, 6, 17);
        graph.insertEdge(6, 7, 19);
        
        graph.MiniSpanTree_Prim(graph);

測試結果:


普里姆算法測試結果圖.png

克魯斯卡爾算法(Kruskal)

思想:將圖的存儲結構使用邊集數組的形式表示,并將邊集數組按權值從小到大排序,遍歷邊集數組,每次選取一條邊并判斷是否構成環路,不會構成環路則將其加入最小生成樹,最終只會包含n-1條邊(n為無向圖的頂點數)。

邊集數組的結構如圖所示:


邊集數組結構圖.png

邊集數組類

public class Edge implements Comparable<Edge> {
        int begin;
        int end;
        int weight;
        public int getBegin() {
            return begin;
        }
        public void setBegin(int begin) {
            this.begin = begin;
        }
        public int getEnd() {
            return end;
        }
        public void setEnd(int end) {
            this.end = end;
        }
        public int getWeight() {
            return weight;
        }
        public void setWeight(int weight) {
            this.weight = weight;
        }
        @Override
        public int compareTo(Edge o) {
            // TODO Auto-generated method stub
            //按權值升序排列
            return this.weight - o.weight;
        }
        
}

圖類

public class Graph {
    private ArrayList<Object> vertexList;       //存放頂點的數組
    Edge[] edges;                       //邊集數組
    int[][] arr;                                //鄰接矩陣

    public Graph(int v, int e) {
        //初始化結點數組
        vertexList = new ArrayList<>(v);
        //根據邊數初始化邊集數組
        edges = new Edge[e];
        //向邊集數組添加空對象
        for(int i = 0; i < e; i++){
            Edge edge = new Edge();
            edges[i] = edge;
        }
        //初始化鄰接矩陣
        arr = new int[v][v];
        for (int i = 0; i < v; i++) {
            for (int j = 0; j < v; j++) {
                if (i == j)
                    arr[i][j] = 0;
                else {
                    arr[j][i] = 65535;
                }
            }
        }
    }
    
    //插入結點
    public void insertVertex(Object vertex) {
        vertexList.add(vertexList.size(),vertex);
    }
    
    //插入無向邊以及設置權值
    public void insertEdge(int n1, int n2, int weight) {
        arr[n1][n2] = weight;
        //該圖為無向圖,所以矩陣關于對角線對稱
        arr[n2][n1] = weight;
    }
    
    //獲取頂點個數
    public int getNumOfVertex() {
        return vertexList.size();
    }
    
    //獲取頂點n的值
    public Object getValueByIndex(int n) {
        return vertexList.get(n);
    }
    
    //鄰接矩陣轉換為邊集數組
    public  void MatrixToEdgesArray() {
        int k = 0;
        //因為該圖為無向圖,所以該矩陣關于從左上到右下的對角線對稱
        //因此此處遍歷矩陣只需遍歷右上部分即可
        for (int i = 0; i < getNumOfVertex(); i++) {
            for (int j = i ; j < getNumOfVertex(); j++) {
                if (arr[i][j] < 65535 && arr[i][j] != 0) {
                    edges[k].begin = i; // 編號較小的結點為首
                    edges[k].end = j; // 編號較大的結點為尾
                    edges[k].weight = arr[i][j];
                    k++;
                }
            }
        }
        //將edges邊集數組按權值從小到大排序
        Arrays.sort(edges);
    }
    
    //最小生成樹--克魯斯卡爾算法
    public void MiniSpanTree_Kruskal(Graph graph) {
        int i, n, m;
        //將鄰接矩陣轉換為邊集數組
        MatrixToEdgesArray();
        int[]  parent = new int[edges.length];
        //初始化parent數組,用于判斷是否產生了環路
        for(i = 0; i < edges.length; i++) {
            parent[i] = 0;
        }
        for(i = 0;  i < edges.length; i++) {
            //按權值從小到大拿到每一條邊
            Edge edge = edges[i];
            n = Find(parent,edge.begin);
            m = Find(parent,edge.end);
            //n==m時表示構成了環路,不能納入最小生成樹中
            if (n != m) {
                System.out.println("(" + edge.begin + "," + edge.end + ")-->" + edge.weight);
                parent[n] = m;
            }
        }
    }
    
    public int Find(int[] parent, int f) {
        /**當i=7時,parent數組為{1,5,8,7,7,8,0,0,6}
        *               對應的下標為{0,1,2,3,4,5,6,7,8}
        *parent[0] = 1表示頂點0,1已經加入生成樹中
        *所以此時頂點0,1,2,5,8,6在一個邊集合中;頂點3,4,7在一個邊集合中
        **/
        while(parent[f] > 0) {
            f = parent[f];
        }
        return f;
    }
}

測試程序:

        int n = 9, e = 15;
        String[] vertex = {"v0","v1","v2","v3","v4","v5","v6","v7","v8"};
        Graph graph = new Graph(n,e);
        for (String string : vertex) {
            graph.insertVertex(string);
        }
                
        graph.insertEdge(0, 1, 10);
        graph.insertEdge(0, 5, 11);
        graph.insertEdge(1, 2, 18);     
        graph.insertEdge(1, 6, 16);
        graph.insertEdge(1, 8, 12);
        graph.insertEdge(2, 8, 8);
        graph.insertEdge(2, 3, 22);
        graph.insertEdge(3, 8, 21);
        graph.insertEdge(3, 6, 24);
        graph.insertEdge(3, 4, 20);
        graph.insertEdge(3, 7, 16);
        graph.insertEdge(4, 5, 26);
        graph.insertEdge(4, 7, 7);
        graph.insertEdge(5, 6, 17);
        graph.insertEdge(6, 7, 19);
        
        graph.MiniSpanTree_Kruskal(graph);

測試結果:


克魯斯卡爾算法測試結果圖.png

最小生成樹為:


最小生成樹.png

總結

普里姆算法針對頂點展開,通過不斷尋找與已構建的生成樹的最小邊來不斷構建新的生成樹。普里姆算法對于稠密圖,也就是邊數非常多的情況會更好一些,因為其是通過頂點來展開的。算法時間損耗主要來源于嵌套的for循環,所以時間復雜度為O(n^2)。

克魯斯卡爾算法針對邊展開,通過對邊集數組的遍歷來構建最小生成樹,但是過程中必須避免構成環路。克魯斯卡爾算法對于稀疏圖,也就是邊數較少的情況效率會很高。此算法的Find函數由邊數e決定,時間復雜度為O(loge),再加上外層for循環的e次,所以時間復雜度為O(eloge)。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,885評論 6 541
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,312評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,993評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,667評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,410評論 6 411
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,778評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,775評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,955評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,521評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,266評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,468評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,998評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,696評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,095評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,385評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,193評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,431評論 2 378

推薦閱讀更多精彩內容