1. 程式人生 > >貪心演算法(四)——最小代價生成樹

貪心演算法(四)——最小代價生成樹

這裡寫圖片描述

問題描述

n個村莊間架設通訊線路,每個村莊間的距離不同,如何架設最節省開銷?

這個問題中,村莊可以抽象成節點,村莊之間的距離抽象成帶權值的邊,要求最節約的架設方案其實就是求如何使用最少的邊、最小的權值和將圖中所有的節點連線起來。
這就是一個最小代價生成樹的問題,可以用Prim演算法或kruskal演算法解決。

  • PS1:無向連通圖的生成樹是一個極小連通子圖。
  • PS2:生成樹是圖的一個子圖,包括所有的頂點和最少的邊(n-1條邊)。
  • PS3:最小代價生成樹就是所有生成樹中權值之和最小的那個。

演算法思路

演算法的目標很明確,就是要在n個節點的圖中,找出n-1個節點,並且節點之間連線的權值是最小的。因此總體思路如下:

/**
 * @param a:圖的鄰接矩陣
 */
EdgeSet spanningTree(int[][] a){
    // 結果集(邊的集合)
    EdgeSet solution = new EdgeSet();
    // 選出n-1條邊
    int i = 0;
    while( i<n && 還有未檢查的邊 ){
        // 選出一條邊
        Edge edge = Select(a);
        // 判斷是否有迴路
        if ( !hasLoop(edge) ) {
            solution.add( edge );
        }
    }
    return
solution; }

上述為最小代價生成樹的總體思路,其中選邊方式(貪心準則)的不同,就產生不同的最小代價生成樹演算法。

圖的鄰接表示法

邊節點

title
一個邊節點有一條邊 和 一個終止節點組成。

/**
 * 邊節點(由一條邊和一個終止節點構成)
 */
class ENode{
    int id;// 終止節點的編號
    int weight;// 邊的權重
}

圖的鄰接表示

圖用一個Map< String,List>表示,其中String表示節點的編號,List中儲存以該節點為起點的所有邊節點。
title

Map<String,List<ENode>>

Prim演算法

貪心準則:將整個圖分成兩部分,一部分已選入生成樹,另一部分在生成樹之外。每次選的邊要求一頭在生成樹之內,一頭在生成樹之外,並保證當前邊是滿足上述條件中最短的一條。重複上述操作,直到選出n-1條邊為止。

資料結構

  • mark:

    Map<String,Boolean> mark = new HashMap<>();

    記錄指定節點是否已在生成樹中。
    key表示節點編號,value為boolean型,表示是否已選入生成樹中。

  • nearest:

    Map<String,String> nearest;

    用於記錄最小代價生成樹的那條路徑。
    key表示指定節點的編號;
    value表示在最小代價生成樹中,該節點的前驅節點的編號。

  • lowcost:

    Map<String,Integer> lowcost;

    記錄指定節點為終點的邊的最小權值。
    key表示指定節點的編號;
    value表示在最小代價生成樹中,以該節點為終點的邊的權值。

  • k節點:
    最新選入生成樹的節點。

演算法過程

第一步:
首先初始化陣列:
1. mark的值全為false
2. nearest的值全為-1
3. lowcost的值全為Integer.MAX_VALUE。

mark[1] mark[2] mark[3] mark[4] mark[5] mark[6]
false false false false false false
lowcost[1] lowcost[2] lowcost[3] lowcost[4] lowcost[5] lowcost[6]
MAX MAX MAX MAX MAX MAX
nearest[1] nearest[2] nearest[3] nearest[4] nearest[5] nearest[6]
-1 -1 -1 -1 -1 -1

第二步:
title

  1. 將節點1作為起點選入生成樹,記為k,mark[1]=true;
  2. 遍歷節點k的所有相鄰節點,更新lowcost陣列和nearest陣列:
    設j是節點k相鄰節點,並且如果< k,j>這條邊的權值小於lowcost[j],則更新lowcost[j]=w< k,j>、nearest[j]=k。
  3. 在lowcost陣列中找到那個權值最小,且不在生成樹中的邊的節點,將它加入生成樹中:
    3.1. 遍歷lowcost,找出最小值;
    3.2. 將該最小值對應的lowcost下標(節點編號)的mark設為true;
    3.3. 更新k;
mark[1] mark[2] mark[3] mark[4] mark[5] mark[6]
true false false false false false
lowcost[1] lowcost[2] lowcost[3] lowcost[4] lowcost[5] lowcost[6]
MAX 6 1 5 MAX MAX
nearest[1] nearest[2] nearest[3] nearest[4] nearest[5] nearest[6]
-1 1 1 1 0 0

第三步:
title

  1. 此時將節點3記為k;
  2. 依次遍歷與k節點相鄰的所有不在生成樹中的節點,並更新nearest陣列和lowcost陣列;
  3. 遍歷lowcost陣列,找出尚未選中的最短的邊,將該邊的終點設為true,並設為k,一直迴圈下去,直到選出n-1條邊為止。

程式碼實現

/**
 * prim演算法
 * @param graph:圖的鄰接矩陣
 */
void prim(Map<String,List<Edge>> graph){
    // 初始化
    Map<String,String> nearest = new HashMap<>();
    Map<String,Integer> lowcost = new HashMap<>();
    Map<String,Boolean> mark = new HashMap<>();
    String k = null;
    String end = null;// 記錄最後一個節點的id,用於從後向前輸出結果

    for( String id : graph.keySet() ){
        nearest.put( id, null );
        lowcost.put( id, Integer.MAX_VALUE );
        mark.put( id, false );
        k = id;
    }
    mark.put( id, true );

    // 尋找生成樹的n-1條邊
    for(int i=1; i<=graph.size()-1; i++){

        // 更新與k相鄰的nearest
        List<ENode> edges = graph.get( k );
        for( ENode edge : edges ){
            if ( !mark.get(edge.id) && edge.w < lowcost.get(edge.id) ) {
                lowcost.put( edge.id, edge.w );
                nearest.put( edge.id, k );
            }
        }

        // 尋找當前lowcost中最短的邊
        int min = Integer.MAX_VALUE;
        for( Map.Entry<String,Integer> entry : lowcost.entrySet() ){
            if ( entry.getValue() < min ) {
                min = entry.getValue();
                k = entry.getKey();
            }
        }
        mark.get( k, true );
        end = k;
    }

    // 輸出結果
    for ( int i=0; i<graph.size(); i++ ) {
        System.out.println( nearest.get(end)+"-"+end+"權值:"+lowcost.get(end) );
        end = nearest.get(end);
    }
}

時間複雜度

若圖中共有n個節點,那麼Prim演算法的時間複雜度為O(n^2)。

Kruskal演算法

貪心準則:將所有的邊按照權值遞增的順序排序,每次選一條權值最小的邊納入生成樹中,若沒有環路則選邊成功,若有環路,則選下一條次小的邊,直到選滿n-1條邊為止。

這裡寫圖片描述