最小生成樹:Kruskal算法 和 Prim算法(第23章)

武俠: 飛雪連天射白鹿,笑書神俠倚碧鴛。 ——金庸十四著作
飛狐外傳 、雪山飛狐 、連城訣 、天龍八部 、射鵰英雄傳 、白馬嘯西風 、鹿鼎記 、笑傲江湖 、書劍恩仇錄 、神鵰俠侶 、俠客島 、倚天屠龍記 、碧血劍 、鴛鴦刀 (除此之外還缺少越女劍)。

聲明:本文參考了華山大師兄博客最小生成樹-Prim算法和Kruskal算法。結合自己學習《算法導論》的認識形成的筆記。感謝網友的總結分享。

1. 最小生成樹的生成

  一個帶權的無向連通圖,如何選取一棵生成樹,使樹上所有邊上權的總和爲最小,這叫最小生成樹。Kruskal算法 和 Prim算法都是使用貪心策略來解決最小生成樹的問題。

//無相連通圖G(V,E)和權重函數w:E->R.
Generic-MST(G,w){
    A;//Φ表示空集
    while(A does not from a spinning tree){
        find a edge(u,v) that is safe for A;
        A=A∪{(u,v)}
    }
    return A;
}

2. 克魯斯卡爾算法

算法思想:遍歷根據權重從小到大排序的邊,依據約束,合併森林成爲目標樹。(自己理解的大白話)
1).記Graph中有v個頂點,e個邊。
2).新建圖Graphnew,Graphnew中擁有原圖中相同的v個頂點,但沒有邊。
3).將原圖Graph中所有e個邊按權值從小到大排序
4).循環:從權值最小的邊開始遍歷每條邊 直至圖Graph中所有的節點都在同一個連通分量中
if 這條邊連接的兩個節點於圖Graphnew中不在同一個連通分量中
添加這條邊到圖Graphnew中

3. 普里姆算法

算法思想:從一點出發,先添加連通的最近的結點,一直到所有的幾點都包含在目標樹中。(自己理解的大白話)
1).輸入:一個加權連通圖,其中頂點集合爲V,邊集合爲E;
2).初始化:Vnew = {x},其中x爲集合V中的任一節點(起始點),Enew = {},爲空;
3).重複下列操作,直到Vnew = V:
  a.在集合E中選取權值最小的邊

4. 算法Java實現**

4.1 圖的存儲和表示

package lbz.ch23.mst;

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

/** 
 * @author LbZhang
 * @version 創建時間:2016年3月16日 下午9:11:24 
 * @description 圖
 */
public class Graph {    


    public final static int NODECOUNT = 9;
    public final static String[] VERTEXS={"a","b","c","d","e","f","g","h","i"};
    private final int[][] EDGEVALUE = { 
            { 0, 4, 0, 0, 0, 0 , 0 ,8, 0},
            { 4, 0, 8, 0, 0, 0 , 0 ,11, 0},
            { 0, 8, 0, 7, 0, 4 , 0 ,0, 2},
            { 0, 0, 7, 0, 9, 14, 0 ,0, 0},
            { 0, 0, 0, 9, 0, 10, 0 ,0, 0},
            { 0, 0, 4, 14, 10, 0, 2 ,0, 0},
            { 0, 0, 0, 0, 0, 2, 0 ,1, 6},
            { 8, 11, 0, 0, 0, 0, 1 ,0, 7},
            { 0, 0, 2, 0, 0, 0, 6 ,7, 0}

            };


    public int count;
    public String[] vertexstr;
    public int[][] edgesValue;





    public Graph() {
        this.count=NODECOUNT;
        this.edgesValue=EDGEVALUE;
        this.vertexstr=VERTEXS;
    }
    /**
     * 獲取當前圖中的所有的邊集合
     * @return
     */
    public List<FromTo> getSortedEdgeFromTo(){
        List<FromTo> fts = new ArrayList<FromTo>();     
        for(int i=0;i<NODECOUNT;i++){
            for(int j=0;j<=i;j++){
                if(this.edgesValue[i][j]!=0){
                    FromTo ft = new FromTo();
                    ft.setFrom(new TreeNode(this.vertexstr[i]));
                    ft.setTo(new TreeNode(this.vertexstr[j]));
                    ft.setWeight(this.edgesValue[i][j]);
                    fts.add(ft);
                }
            }
        }
        ComparatorFromTo cft = new ComparatorFromTo();
        Collections.sort(fts, cft);
//      for(int i=0;i<fts.size();i++){
//          System.out.print(fts.get(i).weight+"  ");
//      }
//      System.out.println();
        return fts;
    }



    public int getCount() {
        return count;
    }



    public void setCount(int count) {
        this.count = count;
    }



    public String[] getVertexstr() {
        return vertexstr;
    }



    public void setVertexstr(String[] vertexstr) {
        this.vertexstr = vertexstr;
    }



    public int[][] getEdgesValue() {
        return edgesValue;
    }



    public void setEdgesValue(int[][] edgesValue) {
        this.edgesValue = edgesValue;
    }



    /**
     * 圖的鏈接矩陣表示的輸出打印
     */
    public void printMGraph() {
        System.out.print("  ");//這裏的間隙使用的是tab 製表鍵
        for(int i=0;i<this.count;i++){
            System.out.print(this.vertexstr[i] + "  ");
        }
        System.out.println();
        for (int i = 0; i < this.edgesValue[0].length; i++) {
            System.out.print(this.vertexstr[i] + "  ");
            for (int j = 0; j < this.edgesValue[0].length; j++) {
                System.out.print(this.edgesValue[i][j] + "  ");
            }
            System.out.println();
        }

        System.out.println("------------圖的鏈接矩陣表示的輸出打印結束------------");
    }

    /**
     * 類內測試函數
     * @param args
     */
    public static void main(String[] args) {        
        Graph g = new Graph();
        g.printMGraph();
    }


}

4.2 樹的存儲和表示

package lbz.ch23.mst;

import java.util.Comparator;

import sun.misc.Compare;

/** 
 * @author LbZhang
 * @version 創建時間:2016年3月17日 上午5:41:52 
 * @description 邊類
 */
public class FromTo{

    public int weight;
    public TreeNode from;
    public TreeNode to;


    @Override
    public String toString() {

        return this.from.getVetex()+"->"+this.getTo().getVetex()+":"+this.weight+"";
    }


    public FromTo() {
        super();
    }


    public FromTo(int weight, TreeNode from, TreeNode to) {
        super();
        this.weight = weight;
        this.from = from;
        this.to = to;
    }


    public int getWeight() {
        return weight;
    }


    public void setWeight(int weight) {
        this.weight = weight;
    }


    public TreeNode getFrom() {
        return from;
    }


    public void setFrom(TreeNode from) {
        this.from = from;
    }


    public TreeNode getTo() {
        return to;
    }


    public void setTo(TreeNode to) {
        this.to = to;
    }
}
package lbz.ch23.mst;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/** 
 * @author LbZhang
 * @version 創建時間:2016年3月17日 下午3:48:34 
 * @description 比較類
 */
public class ComparatorFromTo implements Comparator{

    @Override
    public int compare(Object o1, Object o2) {
        FromTo ft1 = (FromTo)o1;
        FromTo ft2 = (FromTo)o2;
        int flag = 0;
        if(ft1.getWeight()>=ft2.getWeight()){
            flag=1;
        }else{
            flag=-1;
        }

        return flag;
    }

    @SuppressWarnings("unchecked")
    public static void main(String[] args) {
        System.out.println("Test !@");
        List<FromTo> fts = new ArrayList<FromTo>();

        fts.add(new FromTo(3,new TreeNode("A"),new TreeNode("B")));
        fts.add(new FromTo(1,new TreeNode("A"),new TreeNode("B")));
        fts.add(new FromTo(5,new TreeNode("A"),new TreeNode("B")));
        fts.add(new FromTo(2,new TreeNode("A"),new TreeNode("B")));
        ComparatorFromTo cft = new ComparatorFromTo();
        Collections.sort(fts, cft);
        for(int i=0;i<fts.size();i++){
            System.out.println(fts.get(i).weight);
        }

    }

}
package lbz.ch23.mst;

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

/**
 * @author LbZhang
 * @version 創建時間:2016年3月17日 上午5:14:25
 * @description 樹結點類
 */
public class TreeNode {
    public String vetex;
    public List<FromTo> from = new ArrayList<FromTo>();
    public List<FromTo> to = new ArrayList<FromTo>();

    public TreeNode(String vetex) {
        super();
        this.vetex = vetex;
    }

    public TreeNode(String vetex, List<FromTo> from, List<FromTo> to) {
        super();
        this.vetex = vetex;
        this.from = from;
        this.to = to;
    }

    public TreeNode() {
        super();
    }

    public String getVetex() {
        return vetex;
    }

    public void setVetex(String vetex) {
        this.vetex = vetex;
    }

    public List<FromTo> getFrom() {
        return from;
    }

    public void setFrom(List<FromTo> from) {
        this.from = from;
    }

    public List<FromTo> getTo() {
        return to;
    }

    public void setTo(List<FromTo> to) {
        this.to = to;
    }

}
package lbz.ch23.mst;

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

/** 
 * @author LbZhang
 * @version 創建時間:2016年3月17日 下午2:56:36 
 * @description 最小生成樹結構類
 */
public class MSTree {

    public TreeNode root;//樹的根節點
    public List<TreeNode> tns = new ArrayList<TreeNode>();
    public List<FromTo> fts=new ArrayList<FromTo>();
    public MSTree() {
        super();
    }

    public MSTree(TreeNode root) {
        super();
        this.root = root;
        this.tns.add(root);
    }

    public MSTree(TreeNode r, List<TreeNode> tns, List<FromTo> fts) {
        super();
        this.root=r;
        this.tns = tns;
        this.fts = fts;
    }
    public TreeNode getRoot() {
        return root;
    }
    public void setRoot(TreeNode root) {
        this.root = root;
    }
    public List<TreeNode> getTns() {
        return tns;
    }
    public void setTns(List<TreeNode> tns) {
        this.tns = tns;
    }
    public List<FromTo> getFts() {
        return fts;
    }
    public void setFts(List<FromTo> fts) {
        this.fts = fts;
    }

    /**
     * 判斷兩個頂點是否在當前的最小生成樹中
     * @param from
     * @param to
     * @return
     */
    public boolean containsTwoNodes(TreeNode from, TreeNode to) {
        boolean flag = false;
        int count = 0;
//      System.out.println(tns.size());
//      for(int i=0;i<tns.size();i++){
//          System.out.print(":"+tns.get(i).getVetex());
//      }
//      System.out.println();
        for(int i=0;i<tns.size();i++){
            //判定如果在一個最小生成樹中有當前的需要比對的節點count+1
            if((tns.get(i).getVetex().equals(from.getVetex()))||(tns.get(i).getVetex().equals(to.getVetex()))){
                count++;
            }


            if(count>=2){
                flag=true;
                break;
            }
        }
        return flag;
    }

    /**
     * 當前樹中包含當前結點
     * @param from
     * @return
     */
    public boolean containsNode(TreeNode tn) {
        boolean flag = false;
        for(int i=0;i<tns.size();i++){
            if((tns.get(i).getVetex().equals(tn.getVetex()))){
                flag=true;
                break;
            }
        }
        return flag;
    }



}

4.3 克魯斯卡爾(Krusckal)算法的實現

package lbz.ch23.mst;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

/** 
 * @author LbZhang
 * @version 創建時間:2016年3月16日 下午9:09:05 
 * @description   MST Kruskal算法實現類
 * 無向連通圖——最小生成樹算法的設計實現和測試
 * 
 * MinimumSpanningTree
 */
public class KruskalMST {

    public static void main(String[] args) {
        System.out.println("My Kruskal MST TEST! ");
        Graph g = new Graph();
        g.printMGraph();
        System.out.println("----無向連通圖構建完畢----");
        //克魯斯卡爾算法的最小生成樹
        Set<FromTo> frset = MstKruskal(g);
        Iterator<FromTo> it = frset.iterator();
        System.out.println("----最小生成樹的結果---");
        while(it.hasNext()){
            FromTo ft = it.next();
            System.out.println(ft.getFrom().getVetex()+"->"+ft.getTo().getVetex()+": "+ ft.getWeight());
        }

    }

    /**
     * Kruskal 算法的核心內容的實現
     * @param g
     * @return
     */
    private static Set<FromTo> MstKruskal(Graph g) {

        Set<FromTo> FRSet = new HashSet<FromTo>();
        //最小生成樹列表
        MSTree mst = new MSTree();
        int n=g.count;//結點的個數

        List<MSTree> mstList = new ArrayList<MSTree>();
        //初始化各個最小生成樹
        for(int i=0;i<n;i++){
            MSTree mt = new MSTree(new TreeNode(g.getVertexstr()[i]));
            mstList.add(mt);
        }
        //根據權重排序        
        List<FromTo> fts = g.getSortedEdgeFromTo();
        for(int i=0;i<fts.size();i++){
            System.out.println(fts.get(i).weight+"  "+fts.get(i).getFrom().getVetex()+"->"+fts.get(i).getTo().getVetex());
        }

        System.out.println("----根據權重排序   一共有:"+fts.size()+"條邊----");

        for(int i=0;i<fts.size();i++){
            FromTo ft = fts.get(i);
            if(ft.getWeight()==8){
                System.out.println();
            }
            if(!findInSameTree(ft.from,ft.to,mstList)){
                FRSet.add(ft);
                unionRelatedTree(ft.from,ft.to,ft,mstList);             
            }
        }
        return FRSet;

    }

    /**
     * 將通過邊連接起來的兩個最小生成樹合併
     * @param from
     * @param to
     * @param ft 
     * @param mstList
     */
    private static void unionRelatedTree(TreeNode from, TreeNode to,
            FromTo ft, List<MSTree> mstList) {
        MSTree mst1 = new MSTree(),mst2=new MSTree();
        int memo = 0;
        for(int i=0;i<mstList.size();i++){
            MSTree mst = mstList.get(i);
            if(mst.containsNode(from)){
                mst1=mst;
            }else if(mst.containsNode(to)){
                mst2=mst;
                memo=i;
            }
        }
        //將兩個鏈表合併爲一個
        mst1.getFts().addAll(mst2.getFts());
        mst1.getTns().addAll(mst2.getTns());
        mstList.remove(memo);
    }

    /**
     * 判斷當前的邊的兩個端點是否位於同一最小生成樹中
     * @param from
     * @param to
     * @param mstList 
     * @return
     */
    private static boolean findInSameTree(TreeNode from, TreeNode to, List<MSTree> mstList) {
        boolean flag = false;
        for(int i=0;i<mstList.size();i++){
            MSTree mst = mstList.get(i);
            if(mst.containsTwoNodes(from,to)){
                flag=true;
                break;
            }

        }
        return flag;
    }

}

4.4 普利姆(Prim)算法的實現

package lbz.ch23.mst;

import java.util.Iterator;
import java.util.List;
import java.util.Set;

/** 
 * @author LbZhang
 * @version 創建時間:2016年3月17日 下午8:21:45 
 * @description MST Prim算法實現類
 * 無向連通圖——最小生成樹算法的設計實現和測試
 */
public class PrimMST {

    public static void main(String[] args) {
        System.out.println("My Prim MST TEST! ");
        Graph g = new Graph();
        g.printMGraph();
        System.out.println("----無向連通圖構建完畢----");
        MSTree mst = new MSTree();
        TreeNode root = new TreeNode("a");
        mst=MstPrim(g,root);

        System.out.println("輸出Prim構造的最小生成樹的結果演示");
        for(int i=0;i<mst.getFts().size();i++){
            System.out.println(mst.getFts().get(i));
        }
        System.out.println();
    }

    private static MSTree MstPrim(Graph g, TreeNode root) {
        MSTree mst = new MSTree(root);

        //獲取當前圖中的邊從小到大的集合
        List<FromTo> fts = g.getSortedEdgeFromTo();

        while(mst.getTns().size()<=g.getCount()){
            FromTo ft = extractMin(fts,mst);//
            if(ft==null){//最後一個會傳出null的值
                break;
            }
            mst.getTns().add(ft.to);
            mst.getFts().add(ft);

        }
        return mst;
    }

    private static FromTo extractMin(List<FromTo> fts, MSTree mst) {
        FromTo  ft=null;
        TreeNode tnv = null;
        for(int i=0;i<fts.size();i++){
            ft = fts.get(i);
            if(mst.containsNode(ft.from)&&!mst.containsNode(ft.to)){
                tnv=ft.to;
                ft.to=ft.from;
                ft.from=tnv;                
                fts.remove(i);
                break;

            }else if(!mst.containsNode(ft.from)&&mst.containsNode(ft.to)){
                tnv=ft.from;
                ft.from=ft.to;
                ft.to=tnv;
                fts.remove(i);
                break;

            }else{
                ft=null;
            }

        }

        return ft;

    }

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章