图论 C++实现 笔记3-最小生成树 Prim算法

123 阅读3分钟

图论 C++实现 笔记3-最小生成树

最小生成树 Minimum Span Tree

image.png

应用场景

  • 电缆布线
  • 网络布线

注意点:最小生成树主要是针对有权无向的连通图而言的

切分定理

把图中的节点分为两个部分,称为一个切分.

image.png

如果一个边的两个顶点属于切分的不同部分,这个边称为横切边.

image.png

切分定理:给定任意切分,横切边中权值最小的边必然属于最小生成树.

image.png

image.png

Lazy Prim

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

min_heap.h

//
// Created by liuyubobobo on 9/13/16.
// https://github.com/liuyubobobo/Play-with-Algorithms/blob/master/08-Minimum-Span-Trees/Course%20Code%20(C%2B%2B)/03-Lazy-Prim/MinHeap.h
//

#ifndef MIN_HEAP_H
#define MIN_HEAP_H

#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/2] > data[k] ){
            swap( data[k/2], data[k] );
            k /= 2;
        }
    }

    void shiftDown(int k){
        while( 2*k <= count ){
            int j = 2*k;
            if( j+1 <= count && data[j+1] < data[j] ) j ++;
            if( data[k] <= data[j] ) break;
            swap( data[k] , data[j] );
            k = j;
        }
    }

public:

    // 构造函数, 构造一个空堆, 可容纳capacity个元素
    MinHeap(int capacity){
        data = new Item[capacity+1];
        count = 0;
        this->capacity = capacity;
    }

    // 构造函数, 通过一个给定数组创建一个最小堆
    // 该构造堆的过程, 时间复杂度为O(n)
    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;

        for( int i = count/2 ; i >= 1 ; i -- )
            shiftDown(i);
    }

    ~MinHeap(){
        delete[] data;
    }

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

    // 返回一个布尔值, 表示堆中是否为空
    bool isEmpty(){
        return count == 0;
    }

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

    // 从最小堆中取出堆顶元素, 即堆中所存储的最小数据
    Item extractMin(){
        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];
    }
};

#endif //MIN_HEAP_H

lazy_prim_mst.h

#ifndef LAZY_PRIM_MST_H
#define LAZY_PRIM_MST_H

#include <vector>
#include <cassert>
#include "edge.h"
#include "min_heap.h"


// 使用Prim算法求图的 最小生成树(mst)
template <typename Graph, typename Weight>
class LazyPrimMST
{
private:
    Graph &G;                       // 图的引用
    MinHeap<Edge<Weight> > min_heap; // 最小堆, 算法辅助数据结构
    bool *marked;                   // 标记数组, 在算法运行过程中标记节点i是否被访问
    vector<Edge<Weight> > mst;       // 最小生成树所包含的所有边
    Weight mstWeight;               // 最小生成树的权值
    void visit(int v);

public:
    // 构造函数,
    LazyPrimMST(Graph &graph);
    // 析构函数
    ~LazyPrimMST()
    {
        delete[] marked;
    }
    // 返回最小生成树的所有边
    vector<Edge<Weight> > mstEdges()
    {
        return mst;
    };
    // 返回最小生成树的权值
    Weight result()
    {
        return mstWeight;
    };
};

// 访问节点v
template <typename Graph, typename Weight>
void LazyPrimMST<Graph, Weight>::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())
        if (!marked[e->getOtherV(v)])
            min_heap.insert(*e);
}

template <typename Graph, typename Weight>
LazyPrimMST<Graph, Weight>::LazyPrimMST(Graph &graph) : G(graph), min_heap(MinHeap<Edge<Weight> >(graph.E()))
{
    marked = new bool[G.V()];
    for (int i = 0; i < G.V(); i++)
        marked[i] = false;
    mst.clear();

    // Lazy Prim
    visit(0);
    while (!min_heap.isEmpty())
    {
        Edge<Weight> e = min_heap.extractMin();       // 使用最小堆找出已经访问的边中权值最小的边
        if (marked[e.getFrom()] == marked[e.getTo()]) // 如果这条边的两端都已经访问过了, 则扔掉这条边
            continue;
        mst.push_back(e);         // 否则, 这条边则应该存在在最小生成树中
        if (!marked[e.getFrom()]) // 访问和这条边连接的还没有被访问过的节点
            visit(e.getFrom());
        else
            visit(e.getTo());
    }

    // 计算最小生成树的权值
    mstWeight = mst[0].getW();
    for (int i = 1; i < mst.size(); i++)
        mstWeight += mst[i].getW();
}

#endif // LAZY_PRIM_MST_H

lazy_prim_mst_test.cpp

#include "weight_dense_graph.h"
#include "weight_sparse_graph.h"
#include "weight_read_graph.h"
#include "lazy_prim_mst.h"

// g++ lazy_prim_mst_test.cpp && ./a.out
int main(){
    int V = 8;
    string file_name = "graph_data4.txt";
    WeightSparseGraph<double> wsg(8, false);
    WeightReadGraph<WeightSparseGraph<double>, double> readGraph(wsg, file_name);
    // wsg.show();

    cout<<"Test Lazy Prim MST:"<<endl;
    LazyPrimMST<WeightSparseGraph<double>, double> lazyPrimMST(wsg);
    vector<Edge<double> > mst = lazyPrimMST.mstEdges();
    for( int i = 0 ; i < mst.size() ; i ++ )
        cout<<mst[i]<<endl;
    cout<<"The MST weight is: "<<lazyPrimMST.result()<<endl;
    cout<<endl;

    return 0;
}