图论 C++实现 笔记3-最小生成树
最小生成树 Minimum Span Tree
应用场景
- 电缆布线
- 网络布线
注意点:最小生成树主要是针对有权无向的连通图而言的
切分定理
把图中的节点分为两个部分,称为一个切分.
如果一个边的两个顶点属于切分的不同部分,这个边称为横切边.
切分定理:给定任意切分,横切边中权值最小的边必然属于最小生成树.
Lazy Prim
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;
}