主要介紹有權圖、最小生成樹問題和切分定理、Prim算法、Krusk算法等ios
邊上都有本身的權值的帶權圖。 c++
![]()
// 邊
template<typename Weight>
class Edge{
private:
int a,b; // 邊的兩個端點
Weight weight; // 邊的權值
public:
// 構造函數
Edge(int a, int b, Weight weight){
this->a = a;
this->b = b;
this->weight = weight;
}
// 空的構造函數, 全部的成員變量都取默認值
Edge(){}
~Edge(){}
int v(){ return a;} // 返回第一個頂點
int w(){ return b;} // 返回第二個頂點
Weight wt(){ return weight;} // 返回權值
// 給定一個頂點, 返回另外一個頂點
int other(int x){
assert( x == a || x == b );
return x == a ? b : a;
}
// 輸出邊的信息
friend ostream& operator<<(ostream &os, const Edge &e){
os<<e.a<<"-"<<e.b<<": "<<e.weight;
return os;
}
// 邊的大小比較, 是對邊的權值的大小比較
bool operator<(Edge<Weight>& e){
return weight < e.wt();
}
bool operator<=(Edge<Weight>& e){
return weight <= e.wt();
}
bool operator>(Edge<Weight>& e){
return weight > e.wt();
}
bool operator>=(Edge<Weight>& e){
return weight >= e.wt();
}
bool operator==(Edge<Weight>& e){
return weight == e.wt();
}
};
複製代碼
// 稠密圖 - 鄰接矩陣
template <typename Weight>
class DenseGraph{
private:
int n, m; // 節點數和邊數
bool directed; // 是否爲有向圖
vector<vector<Edge<Weight> *>> g; // 圖的具體數據
public:
// 構造函數
DenseGraph( int n , bool directed){
assert( n >= 0 );
this->n = n;
this->m = 0;
this->directed = directed;
// g初始化爲n*n的矩陣, 每個g[i][j]指向一個邊的信息, 初始化爲NULL
g = vector<vector<Edge<Weight> *>>(n, vector<Edge<Weight> *>(n, NULL));
}
// 析構函數
~DenseGraph(){
for( int i = 0 ; i < n ; i ++ )
for( int j = 0 ; j < n ; j ++ )
if( g[i][j] != NULL )
delete g[i][j];
}
int V(){ return n;} // 返回節點個數
int E(){ return m;} // 返回邊的個數
// 向圖中添加一個邊, 權值爲weight
void addEdge( int v, int w , Weight weight ){
assert( v >= 0 && v < n );
assert( w >= 0 && w < n );
// 若是從v到w已經有邊, 刪除這條邊
if( hasEdge( v , w ) ){
delete g[v][w];
if( v != w && !directed )
delete g[w][v];
m --;
}
g[v][w] = new Edge<Weight>(v, w, weight);
if( v != w && !directed )
g[w][v] = new Edge<Weight>(w, v, weight);
m ++;
}
// 驗證圖中是否有從v到w的邊
bool hasEdge( int v , int w ){
assert( v >= 0 && v < n );
assert( w >= 0 && w < n );
return g[v][w] != NULL;
}
// 顯示圖的信息
void show(){
for( int i = 0 ; i < n ; i ++ ){
for( int j = 0 ; j < n ; j ++ )
if( g[i][j] )
cout<<g[i][j]->wt()<<"\t";
else
cout<<"NULL\t";
cout<<endl;
}
}
// 鄰邊迭代器, 傳入一個圖和一個頂點,
// 迭代在這個圖中和這個頂點向連的全部邊
class adjIterator{
private:
DenseGraph &G; // 圖G的引用
int v;
int index;
public:
// 構造函數
adjIterator(DenseGraph &graph, int v): G(graph){
this->v = v;
this->index = -1; // 索引從-1開始, 由於每次遍歷都須要調用一次next()
}
~adjIterator(){}
// 返回圖G中與頂點v相鏈接的第一個邊
Edge<Weight>* begin(){
// 索引從-1開始, 由於每次遍歷都須要調用一次next()
index = -1;
return next();
}
// 返回圖G中與頂點v相鏈接的下一個邊
Edge<Weight>* next(){
// 從當前index開始向後搜索, 直到找到一個g[v][index]爲true
for( index += 1 ; index < G.V() ; index ++ )
if( G.g[v][index] )
return G.g[v][index];
// 若沒有頂點和v相鏈接, 則返回NULL
return NULL;
}
// 查看是否已經迭代完了圖G中與頂點v相鏈接的全部邊
bool end(){
return index >= G.V();
}
};
};
複製代碼
main.cpp:算法
// 測試有權圖和有權圖的讀取
int main() {
string filename = "testG1.txt";
int V = 8;
cout<<fixed<<setprecision(2);
// Test Weighted Dense Graph
DenseGraph<double> g1 = DenseGraph<double>(V, false);
ReadGraph<DenseGraph<double>,double> readGraph1(g1, filename);
g1.show();
cout<<endl;
// Test Weighted Sparse Graph
SparseGraph<double> g2 = SparseGraph<double>(V, false);
ReadGraph<SparseGraph<double>,double> readGraph2(g2, filename);
g2.show();
cout<<endl;
return 0;
}
複製代碼
// 稀疏圖 - 鄰接表
template<typename Weight>
class SparseGraph{
private:
int n, m; // 節點數和邊數
bool directed; // 是否爲有向圖
vector<vector<Edge<Weight> *> > g; // 圖的具體數據
public:
// 構造函數
SparseGraph( int n , bool directed){
assert(n >= 0);
this->n = n;
this->m = 0; // 初始化沒有任何邊
this->directed = directed;
// g初始化爲n個空的vector, 表示每個g[i]都爲空, 即沒有任和邊
g = vector<vector<Edge<Weight> *> >(n, vector<Edge<Weight> *>());
}
// 析構函數
~SparseGraph(){
for( int i = 0 ; i < n ; i ++ )
for( int j = 0 ; j < g[i].size() ; j ++ )
delete g[i][j];
}
int V(){ return n;} // 返回節點個數
int E(){ return m;} // 返回邊的個數
// 向圖中添加一個邊, 權值爲weight
void addEdge( int v, int w , Weight weight){
assert( v >= 0 && v < n );
assert( w >= 0 && w < n );
// 注意, 因爲在鄰接表的狀況, 查找是否有重邊須要遍歷整個鏈表
// 咱們的程序容許重邊的出現
g[v].push_back(new Edge<Weight>(v, w, weight));
if( v != w && !directed )
g[w].push_back(new Edge<Weight>(w, v, weight));
m ++;
}
// 驗證圖中是否有從v到w的邊
bool hasEdge( int v , int w ){
assert( v >= 0 && v < n );
assert( w >= 0 && w < n );
for( int i = 0 ; i < g[v].size() ; i ++ )
if( g[v][i]->other(v) == w )
return true;
return false;
}
// 顯示圖的信息
void show(){
for( int i = 0 ; i < n ; i ++ ){
cout<<"vertex "<<i<<":\t";
for( int j = 0 ; j < g[i].size() ; j ++ )
cout<<"( to:"<<g[i][j]->w()<<",wt:"<<g[i][j]->wt()<<")\t";
cout<<endl;
}
}
// 鄰邊迭代器, 傳入一個圖和一個頂點,
// 迭代在這個圖中和這個頂點向連的全部邊
class adjIterator{
private:
SparseGraph &G; // 圖G的引用
int v;
int index;
public:
// 構造函數
adjIterator(SparseGraph &graph, int v): G(graph){
this->v = v;
this->index = 0;
}
~adjIterator(){}
// 返回圖G中與頂點v相鏈接的第一個邊
Edge<Weight>* begin(){
index = 0;
if( G.g[v].size() )
return G.g[v][index];
// 若沒有頂點和v相鏈接, 則返回NULL
return NULL;
}
// 返回圖G中與頂點v相鏈接的下一個邊
Edge<Weight>* next(){
index += 1;
if( index < G.g[v].size() )
return G.g[v][index];
return NULL;
}
// 查看是否已經迭代完了圖G中與頂點v相鏈接的全部頂點
bool end(){
return index >= G.g[v].size();
}
};
};
複製代碼
運行結果:數組
NULL NULL 0.26 NULL 0.38 NULL 0.58 0.16
NULL NULL 0.36 0.29 NULL 0.32 NULL 0.19
0.26 0.36 NULL 0.17 NULL NULL 0.40 0.34
NULL 0.29 0.17 NULL NULL NULL 0.52 NULL
0.38 NULL NULL NULL NULL 0.35 0.93 0.37
NULL 0.32 NULL NULL 0.35 NULL NULL 0.28
0.58 NULL 0.40 0.52 0.93 NULL NULL NULL
0.16 0.19 0.34 NULL 0.37 0.28 NULL NULL
vertex 0: ( to:7,wt:0.16) ( to:4,wt:0.38) ( to:2,wt:0.26) ( to:6,wt:0.58)
vertex 1: ( to:5,wt:0.32) ( to:7,wt:0.19) ( to:2,wt:0.36) ( to:3,wt:0.29)
vertex 2: ( to:3,wt:0.17) ( to:0,wt:0.26) ( to:1,wt:0.36) ( to:7,wt:0.34) ( to:6,wt:0.40)
vertex 3: ( to:2,wt:0.17) ( to:1,wt:0.29) ( to:6,wt:0.52)
vertex 4: ( to:5,wt:0.35) ( to:7,wt:0.37) ( to:0,wt:0.38) ( to:6,wt:0.93)
vertex 5: ( to:4,wt:0.35) ( to:7,wt:0.28) ( to:1,wt:0.32)
vertex 6: ( to:2,wt:0.40) ( to:3,wt:0.52) ( to:0,wt:0.58) ( to:4,wt:0.93)
vertex 7: ( to:4,wt:0.37) ( to:5,wt:0.28) ( to:0,wt:0.16) ( to:1,wt:0.19) ( to:2,wt:0.34)
複製代碼
最小生成樹是一副連通加權無向圖中一棵權值最小的生成樹。 各個節點之間連通,連通總費用最小。bash
最小生成樹主要針對:微信
把圖中的節點分紅兩部分,成爲一個切分(cut)網絡
藍色和紅色的部分造成了一個切分。數據結構
若是一個邊的兩個端點,屬於切分(Cut)不一樣的兩邊,這個邊稱爲橫切邊(Crossing Edge)。函數
給定任意切分,橫切邊中權值最小的邊必然屬於最小生成樹。測試
// 使用Prim算法求圖的最小生成樹
template<typename Graph, typename Weight>
class LazyPrimMST{
private:
Graph &G; // 圖的引用
MinHeap<Edge<Weight>> pq; // 最小堆, 算法輔助數據結構
bool *marked; // 標記數組, 在算法運行過程當中標記節點i是否被訪問
vector<Edge<Weight>> mst; // 最小生成樹所包含的全部邊
Weight mstWeight; // 最小生成樹的權值
// 訪問節點v
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() )
if( !marked[e->other(v)] )
pq.insert(*e);
}
public:
// 構造函數, 使用Prim算法求圖的最小生成樹
LazyPrimMST(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.clear();
// Lazy Prim
visit(0);
while( !pq.isEmpty() ){
// 使用最小堆找出已經訪問的邊中權值最小的邊
Edge<Weight> e = pq.extractMin();
// 若是這條邊的兩端都已經訪問過了, 則扔掉這條邊
if( marked[e.v()] == marked[e.w()] )
continue;
// 不然, 這條邊則應該存在在最小生成樹中
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();
}
// 析構函數
~LazyPrimMST(){
delete[] marked;
}
// 返回最小生成樹的全部邊
vector<Edge<Weight>> mstEdges(){
return mst;
};
// 返回最小生成樹的權值
Weight result(){
return mstWeight;
};
};
複製代碼
main函數
int main() {
string filename = "testG1.txt";
int V = 8;
SparseGraph<double> g = SparseGraph<double>(V, false);
ReadGraph<SparseGraph<double>, double> readGraph(g, filename);
// Test Lazy Prim MST
cout<<"Test Lazy Prim MST:"<<endl;
LazyPrimMST<SparseGraph<double>, double> lazyPrimMST(g);
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;
// Test Prim MST
cout<<"Test Prim MST:"<<endl;
PrimMST<SparseGraph<double>, double> primMST(g);
mst = primMST.mstEdges();
for( int i = 0 ; i < mst.size() ; i ++ )
cout<<mst[i]<<endl;
cout<<"The MST weight is: "<<primMST.result()<<endl;
cout<<endl;
return 0;
}
複製代碼
結果
Test Lazy Prim MST:
0-7: 0.16
7-1: 0.19
0-2: 0.26
2-3: 0.17
7-5: 0.28
5-4: 0.35
2-6: 0.4
The MST weight is: 1.81
複製代碼
O(ElogE)
複製代碼
###Prim算法優化
Lazy Prim存在的問題
維護數據結構:
和每一個節點相連的最短橫切邊。
//
// Created by liuyubobobo on 9/26/16.
//
#ifndef INC_05_IMPLEMENTATION_OF_OPTIMIZED_PRIM_ALGORITHM_PRIMMST_H
#define INC_05_IMPLEMENTATION_OF_OPTIMIZED_PRIM_ALGORITHM_PRIMMST_H
#include <iostream>
#include <vector>
#include <cassert>
#include "Edge.h"
#include "IndexMinHeap.h"
using namespace std;
template<typename Graph, typename Weight>
class PrimMST{
private:
Graph &G;
vector<Edge<Weight>> mst;
bool* marked;
IndexMinHeap<Weight> ipq;
vector<Edge<Weight>*> edgeTo; //存儲節點的最短橫切邊。
Weight mstWeight;
void visit(int v){
assert( !marked[v] );
marked[v] = true;
typename Graph::adjIterator adj(G,v);
for( Edge<Weight>* e = adj.begin() ; !adj.end() ; e = adj.next() ){
int w = e->other(v);
if( !marked[w] ){
if( !edgeTo[w] ){
edgeTo[w] = e;
ipq.insert(w, e->wt());
}
else if( e->wt() < edgeTo[w]->wt() ){
edgeTo[w] = e;
ipq.change(w, e->wt());//最小索引堆纔有change操做
}
}
}
}
public:
// assume graph is connected
PrimMST(Graph &graph):G(graph), ipq(IndexMinHeap<double>(graph.V())){
assert( graph.E() >= 1 );
marked = new bool[G.V()];
for( int i = 0 ; i < G.V() ; i ++ ){
marked[i] = false;
edgeTo.push_back(NULL);
}
mst.clear();
visit(0);
while( !ipq.isEmpty() ){
int v = ipq.extractMinIndex();
assert( edgeTo[v] );
mst.push_back( *edgeTo[v] );
visit( v );
}
mstWeight = mst[0].wt();
for( int i = 1 ; i < mst.size() ; i ++ )
mstWeight += mst[i].wt();
}
~PrimMST(){
delete[] marked;
}
vector<Edge<Weight>> mstEdges(){
return mst;
};
Weight result(){
return mstWeight;
};
};
#endif //INC_05_IMPLEMENTATION_OF_OPTIMIZED_PRIM_ALGORITHM_PRIMMST_H
複製代碼
Kruskal算法是一種用來查找最小生成樹的算法,由Joseph Kruskal在1956年發表。用來解決一樣問題的還有Prim算法和Boruvka算法等。三種算法都是貪心算法的應用。和Boruvka算法不一樣的地方是,Kruskal算法在圖中存在相同權值的邊時也有效。
// Kruskal算法
template <typename Graph, typename Weight>
class KruskalMST{
private:
vector<Edge<Weight>> mst; // 最小生成樹所包含的全部邊
Weight mstWeight; // 最小生成樹的權值
public:
// 構造函數, 使用Kruskal算法計算graph的最小生成樹
KruskalMST(Graph &graph){
// 將圖中的全部邊存放到一個最小堆中
MinHeap<Edge<Weight>> pq( graph.E() );
for( int i = 0 ; i < graph.V() ; i ++ ){
typename Graph::adjIterator adj(graph,i);
for( Edge<Weight> *e = adj.begin() ; !adj.end() ; e = adj.next() )
//只存一條邊中的一個節點
if( e->v() < e->w() )
pq.insert(*e);
}
// 建立一個並查集, 來查看已經訪問的節點的聯通狀況
UnionFind uf = UnionFind(graph.V());
while( !pq.isEmpty() && mst.size() < graph.V() - 1 ){
// 從最小堆中依次從小到大取出全部的邊
Edge<Weight> e = pq.extractMin();
// 若是該邊的兩個端點是聯通的, 說明加入這條邊將產生環, 扔掉這條邊
if( uf.isConnected( e.v() , e.w() ) )
continue;
// 不然, 將這條邊添加進最小生成樹, 同時標記邊的兩個端點聯通
mst.push_back( e );
uf.unionElements( e.v() , e.w() );
}
mstWeight = mst[0].wt();
for( int i = 1 ; i < mst.size() ; i ++ )
mstWeight += mst[i].wt();
}
~KruskalMST(){ }
// 返回最小生成樹的全部邊
vector<Edge<Weight>> mstEdges(){
return mst;
};
// 返回最小生成樹的權值
Weight result(){
return mstWeight;
};
};
// 測試Kruskal算法
int main() {
string filename = "testG1.txt";
int V = 8;
SparseGraph<double> g = SparseGraph<double>(V, false);
ReadGraph<SparseGraph<double>, double> readGraph(g, filename);
// Test Lazy Prim MST
cout<<"Test Lazy Prim MST:"<<endl;
LazyPrimMST<SparseGraph<double>, double> lazyPrimMST(g);
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;
// Test Prim MST
cout<<"Test Prim MST:"<<endl;
PrimMST<SparseGraph<double>, double> primMST(g);
mst = primMST.mstEdges();
for( int i = 0 ; i < mst.size() ; i ++ )
cout<<mst[i]<<endl;
cout<<"The MST weight is: "<<primMST.result()<<endl;
cout<<endl;
// Test Kruskal MST
cout<<"Test Kruskal MST:"<<endl;
KruskalMST<SparseGraph<double>, double> kruskalMST(g);
mst = kruskalMST.mstEdges();
for( int i = 0 ; i < mst.size() ; i ++ )
cout<<mst[i]<<endl;
cout<<"The MST weight is: "<<kruskalMST.result()<<endl;
return 0;
}
複製代碼
-------------------------華麗的分割線--------------------
看完的朋友能夠點個喜歡/關注,您的支持是對我最大的鼓勵。
想了解更多,歡迎關注個人微信公衆號:番茄技術小棧