圖論之帶權圖「最小生成樹之Prim」

ice_moss發表於2021-05-26

一、prim演算法

最小生成樹prim版,是由prim提出的
在prim的實現中,需要使用輔助資料結構——最小堆,將所有的橫切邊放入最小堆中,經過一系列操作後取堆頂元素,就可得到最小生成樹。
其思想是基於切分定理,其過程如下:
如下圖,初始時,全為藍色,當某個節點被訪問到時,就將其標記(實現中用marked陣列標記,標記後即為紅色):
圖論之帶權圖「最小生成樹之Prim」

0開始:
圖論之帶權圖「最小生成樹之Prim」

將對應的橫切邊放入堆中
圖論之帶權圖「最小生成樹之Prim」

此時從最小堆中取堆頂元素0.16出來,0.16就屬於最小生成樹中
圖論之帶權圖「最小生成樹之Prim」

然後將最小生成樹中的邊0.16相連的未被訪問節點加入紅色
圖論之帶權圖「最小生成樹之Prim」

再將新產生的橫切邊放入最小堆中:
圖論之帶權圖「最小生成樹之Prim」

此時再次將堆頂元素0.19取出,加入最小生成樹中:

圖論之帶權圖「最小生成樹之Prim」

然後再將最小生成樹中的邊0.19相連的未被訪問節點加入紅色:
圖論之帶權圖「最小生成樹之Prim」

有一次將新產生的橫切邊加入最小堆中:

圖論之帶權圖「最小生成樹之Prim」

再一次取出堆頂元素0.26:

圖論之帶權圖「最小生成樹之Prim」

然後將最小生成樹中的邊0。26相連的未被訪問節點加入紅色,並將新產生的橫切邊放入最小堆中:

圖論之帶權圖「最小生成樹之Prim」

再一次取出堆頂元素0.17,加入最小生成樹中:

圖論之帶權圖「最小生成樹之Prim」

然後將最小生成樹中的邊0.17相連的未被訪問節點加入紅色,並將新產生的橫切邊放入最小堆中:

圖論之帶權圖「最小生成樹之Prim」

再一次取出堆頂元素0.28,加入最小生成樹中:

圖論之帶權圖「最小生成樹之Prim」

然後將最小生成樹中的邊0.28相連的未被訪問節點加入紅色,並將新產生的橫切邊放入最小堆中:

圖論之帶權圖「最小生成樹之Prim」

再一次取出堆頂元素0.29,(注意:此時邊0.29它已經不滿足橫切邊了(0.29的兩端都為紅色陣營),但是邊0.29仍然在最小堆中且為堆頂元素,此時在程式碼中需要加入判斷語句:if( marked[1] != marked[3] ),取出的邊判斷是不是橫切邊,不是則扔掉,是則加入最小生成樹中。)0.29直接扔掉。進入下一輪:

取出0.32,直接扔掉:

圖論之帶權圖「最小生成樹之Prim」

取出0.34,直接扔掉:

圖論之帶權圖「最小生成樹之Prim」

取出0.35,加入最小生成樹中,並將最小生成樹中的邊0.35相連的未被訪問節點加入紅色,再將新產生的橫切邊放入最小堆中:

圖論之帶權圖「最小生成樹之Prim」

然後依次取出0.36 、0.37,0.38直接扔掉;再取出最小堆堆頂元素0.40,加入最小生成樹中;並將最小生成樹中的邊0.40相連的未被訪問節點加入紅色,再將新產生的橫切邊放入最小堆中:

圖論之帶權圖「最小生成樹之Prim」
然後在依次取出堆頂元素0.52、0.58、0.93,直到最小堆為空。

二、prim最小生成樹的實現

需要一個輔助資料結構——最小堆

#include <algorithm>
#include <cassert>

using namespace std;

//最小堆
template <typename Item>
class MinHeap{

private:
    Item *data; //原始資料對應的陣列
    int count; //資料對應的索引
    int capacity;  //堆中容量

    void shiftUp(int k){
          while(k > 1 && data[k] < data[k/2]){
              swap(data[k]), data[k/2];
              k /= 2;
          }
      }

    void shiftDown(int k){
        while(2*k <= count){
            int j = 2*k;
            //右孩子存在
            if(j+1 < count && data[j] > data[j+1]) {
                j++;
            }
            //右孩子不存在
            if(data[k] <= data[j]){
                break;
            }
            swap(data[k], data[j]);
            k = j;
   }
 }

 public:
    //建構函式,構造一個空堆可容納capacity個元素
     MinHeap(int capacity ){

          data = new Item[capacity + 1]; //開capacity+1的空間,對應第一個元素從索引值1開始
          count = 0;
          this->capacity = capacity;

  }
         //建構函式,透過給定一個定陣列建立一個最小堆
     MinHeap(Item arr[], int n){
          data = new Item[n+1];
          capacity = n;

          for(int i = 0; i < n; i++){
              data[i+1] = arr[i];
          }
          count = n;
 }

    //解構函式
  ~MinHeap(){
        delete[] data;
  }

  //返回堆中的元素個數
  int size(){
      return count;
  }

  //返回一個布林值,表示堆是否為空
  bool isEmpty(){
      return count == 0;
  }

  //向最小堆中,插入一個新元素item
  Item insert(Item item){
      assert(count + 1 <= capacity);
      data[count+1] = item;
      shiftUp(count+1);
      count ++;

}
  // 從最小堆中取出堆頂元素, 即堆中所儲存的最小資料
  Item extracitMin(){
      assert(count > 0);
      Item ret = data[1];
      swap(data[1], data[count]);

      count --;
      shiftDown(1);
      return ret;
  }

  //檢視堆頂元素
  Item getMin(){
      assert(count > 0);
      return data[1];
 }
};
prim最小生成樹

引入標頭檔案:

#include <iostream>
#include <cassert>
#include <vector>
#include "Edge.h"
#include "MinHeap.h"
//使用LazePrim演算法求最小生成樹
using namespace std;

//使用prim演算法求圖的最小生成數
template <typename Graph, typename Weight>

class LazePrimMST {
private:
    Graph &G;   //圖G的引用
    MinHeap<Edge<Weight>> pq;  //最小堆,將橫切邊放入最小堆中
    bool *marked;              //標記陣列,在演算法執行過程中標記節點i是否被訪問
    vector<Edge<Weight>> mst;  // 最小生成樹所包含的所有邊
    Weight mstWeight;           // 最小生成樹的權值

在private中寫visit()方法用來將橫切邊加入最小堆中:

//visit
void visit(int v) {
    assert(!marked[v]);
    marked[v] = true;

  // 使用圖的迭代器,將和節點v相連線的所有未訪問的邊放入最小堆中
   typename Graph::adjIterator adj(G, v);
   for (Edge<Weight> *e = adj.begin(); !adj.end(); e = adj.next()) {
        //判斷v節點的相鄰節點是否被訪問
        if ( !marked[e->other(v)]) {
              pq.insert(*e);
        }
    }
}
public:
    LazePrimMST(Graph &graph) : G(graph), pq(MinHeap<Edge<Weight>>(graph.E())) {

        //初始化演算法
      marked = new bool[G.V()];
      for (int i = 0; i < G.v(); i++) {
            marked[i] = false;
      }
        //將mst清空
      mst.clrar;

      // Prim
      visit(0);
      while (!pq.isEmpty()) {
          // 使用最小堆找出已經訪問的邊中權值最小的邊
          Edge<Weight> e = pq.extracitMin();
          // 如果這條邊的兩端都已經訪問過了, 則扔掉這條邊
          if (marked[e.v()] == marked[e.w]) {
                continue;
          }
         //否則e就為最小生成樹的邊
          mst.push_back(e);

         // 訪問和這條邊連線的還沒有被訪問過的節點
         if (!marked[e.v()]) {
              visit(e.v());
         } 
        else {
              visit(e.w);
        }
}

  //計算最小生成樹的權值
       mstWeight = mst[0].wt;
       for (int i = 1; i < mst.size(); i++) {
             mstWeight += mst[i].wt;
       }
}

 //解構函式
  ~LazePrimMST(){
        delete[] marked;
  }

    // 返回最小生成樹的所有邊
  vector<Edge<Weight>> mstEdge(){
        return mst;
  }

    // 返回最小生成樹的權值
  Weight result(){
        return mstWeight;
  }

};











本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章