1.最小生成樹問題
最小生成樹問題:一個圖有V個頂點,找到V-1條邊連接這V個頂點,使得總權值最小
最小生成樹問題通常是針對帶權無向圖、連通圖
如果圖不是連通圖:分別在每個連通分量上計算最小生成樹---最小生成森林
應用:電纜佈線設計、網絡設計、電路設計
最小生成樹爲我們提供讓所有點都連通,並且連通的點的權值最小的一個方案
2.切分定理
3.Prim算法
用於解決稠密圖的最小生成樹問題。
計算下圖的最小生成樹
算法流程:
首先將0結點爲一個切分,剩下的結點爲一個切分,那麼0-2、0-4、0-6、0-7就是橫切邊
根據切分定理,0-7的權值最小,那麼這時0-7這條邊一定是最小生成樹的一邊。將7也加入到切分中
橫切邊1-7的權值最小,將1加入切分中
橫切邊0-2的權值最小,將2加入切分中
橫切邊2-3的權值最小,將3加入切分中
橫切邊5-7的權值最小,將7加入切分中
1-3的權值最小,但是此時1-3已經不是橫切邊了,因爲1、3結點已經在切分中了
一直取最小的邊,如果不是橫切邊則扔掉,直到取到一個橫切邊
橫切邊4-5的權值最小,將4加入切分中
橫切邊2-6的權值最小,將6加入切分中
所有的節點已經遍歷完了,程序結束
Lazy Prim以邊爲空作爲結束條件,即:取到一條邊,如果不是橫切邊,則扔掉
直到不再有候選邊爲止
代碼實現:
1.數據定義
使用鄰接矩陣來表示圖
vector<Edge> mst數組存儲最小生成樹的邊 (邊包括起點,終點,邊的權值)
vector<bool> marked(n,false);//這個數組代表結點是否被加入切分中
定義一個最小堆(使用優先隊列),存儲當前需要處理的邊,權值小的在隊首
priority_queue<Edge,vector<Edge>,greater<Edge> > pq;
這邊注意需要對Edge結構體的>運算符做重載
2.對單個結點操作的函數
要將某個節點加入到切分中,要做兩件事
1.將該節點的marked設爲true,代表將該節點加入切分中
2.遍歷這個節點的所有鄰邊,如果鄰邊對應的那個結點還不在切分中,說明是橫切邊,將這個邊加入到優先隊列中
3.主函數
1.首先對結點0進行操作,即marked[0]=true,將結點0的所有鄰邊加入優先隊列中
2.從隊列中不斷取出當前待選邊中,權值最小的邊(隊頭),判斷這條邊是否是橫切邊,如果是,將這條邊加入到mst數組中,並且對橫切邊未加入切分的那個點進行操作
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
//prim算法
//使用鄰接矩陣來表示圖
class DenseGraph{
public:
int n;//n代表結點數
vector<vector<double> > Graph;//鄰接矩陣
bool directed;//是否是有向圖
DenseGraph(int n,bool directed){
this->n = n;
this->directed = directed;
for(int i=0; i<n; i++)
Graph.push_back(vector<double>(n,0));
}
//有權圖:給圖添加一條邊
void addEdge(int v,int m,double w)
{
Graph[v][m] = w;
if(!directed) //無向圖
Graph[m][v] = w;
}
void print()
{
for(int i=0; i<n; i++)
{
for(int j=0; j<n; j++)
cout<<Graph[i][j]<<" " ;
cout<<endl;
}
}
};
//最小生成樹的邊
struct Edge{
int v;
int w;
double weight;
Edge(int v,int w, double weight){
this->v =v ;
this->w =w ;
this->weight =weight ;
}
bool operator >(const Edge &n) const
{
return weight>n.weight; //最小堆
}
};
//有權無向圖
int n=8;
DenseGraph g2(n,false);
vector<Edge> mst;//最小生成樹的邊
vector<bool> marked(n,false);//代表結點是否被加入切分中
double mstWeight;//最小生成樹的權值
//定義一個最小堆,存儲當前需要處理的邊,權值小的在對首
priority_queue<Edge,vector<Edge>,greater<Edge> > pq;
//訪問結點v
//1.將結點v加入切分
//2.訪問結點v的所有鄰接邊,
//如果鄰接邊對應的那個點還沒被訪問過,
//說明是橫切邊,將這條邊加入到優先隊列中
void visit(int v)
{
marked[v] = true;
for(int i=0; i<g2.n; i++)
{
//v-i存在邊,並且點i還沒有在切分中
if(g2.Graph[v][i]!=0 && !marked[i])
{
Edge edge(v,i,g2.Graph[v][i]);
pq.push(edge);//將這條邊加入到優先隊列中
}
}
}
int main()
{
g2.addEdge(0,2,0.26);
g2.addEdge(0,4,0.38);
g2.addEdge(0,6,0.58);
g2.addEdge(0,7,0.16);
g2.addEdge(1,2,0.36);
g2.addEdge(1,3,0.29);
g2.addEdge(1,5,0.32);
g2.addEdge(1,7,0.19);
g2.addEdge(2,3,0.17);
g2.addEdge(2,6,0.40);
g2.addEdge(2,7,0.34);
g2.addEdge(3,6,0.52);
g2.addEdge(4,5,0.35);
g2.addEdge(4,6,0.93);
g2.addEdge(4,7,0.37);
g2.addEdge(5,7,0.28);
g2.print();
//Prim算法
visit(0);//先訪問結點0
while(!pq.empty())
{
Edge e = pq.top();//取出最小橫切邊
pq.pop();
//判斷這條邊是否是橫切邊
if(marked[e.v] == marked[e.w])
continue;
mst.push_back(e);//將這條邊放入最小生成樹中
//哪個結點不在切分中就訪問哪個結點
if(!marked[e.v])
visit(e.v);
else
visit(e.w);
}
//計算最小權值
mstWeight=0.0;
for(int i=0; i<mst.size(); i++)
mstWeight+=mst[i].weight;
cout<<"Prim Mst:"<<endl;
for(int i=0; i<mst.size(); i++)
cout<<mst[i].v<<"-"<<mst[i].w<<":"<<mst[i].weight<<endl;
cout<<"The Mst weight is:"<<mstWeight<<endl;
return 0;
}
Prim的時間複雜度是O(ElogE)
4.Kruskal算法
算法流程:
每次都找最小邊,只要加入的邊不使得圖構成一個環就可以
1.將圖中所有的邊按照權值進行一次排序
2.每次取出還沒考慮的所有邊中的最短邊
1-3、1-5、2-7加入後會形成環,所以我們不考慮這條邊
1-2、4-7、0-4加入後會形成環,所以我們不考慮這條邊
已經有了V-1條邊,算法結束
怎麼判斷將一條邊加入到當前邊後會形成一個環?
使用並查集
比如想要將1-3加入進來
則判斷1的根節點和3的根節點是否相同,如果相同,說明1,3已經連接在一起了,如果我們再將1-3加入,就形成了環
代碼實現:
1.數據定義
跟Prim算法一樣
使用鄰接矩陣來表示圖
vector<Edge> mst數組存儲最小生成樹的邊 (邊包括起點,終點,邊的權值)
vector<bool> marked(n,false);//這個數組代表結點是否被加入切分中
定義一個最小堆(使用優先隊列),存儲當前需要處理的邊,權值小的在隊首
priority_queue<Edge,vector<Edge>,greater<Edge> > pq;
這邊注意需要對Edge結構體的>運算符做重載
2.並查集定義
使用一個parent數組存儲,每個節點的父親節點
寫查找一個點的根節點的函數getParent,使用路徑壓縮優化
關於並查集,可以參考我的另外一篇博客:https://blog.csdn.net/qq_31965925/article/details/106265355
3.將所有的邊存入最小堆中
針對無向圖,存1-2 不存2-1,否則重複
4.從最小堆中取出當前的最短權值邊
判斷當前取出的邊兩個點是否連通,連通則不處理
不連通的話,將邊加入結果中,並且將兩個點使用並查集連接起來
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
//每個結點用一個結構體來表示
struct node{//新增結構體,表示與某個節點相連的節點號與權值
int num;
double weight;
};
//使用鄰接表來表示有權圖
class SparseGraph{
public:
int n;//n代表結點數
vector<vector<node> > Graph;//鄰接表
bool directed;//是否是有向圖
SparseGraph(int n,bool directed){
this->n = n;
this->directed = directed;
for(int i=0; i<n; i++)
Graph.push_back(vector<node>());
}
//給圖添加一條邊
void addEdge(int v,int w,double weight)
{
node node;
node.num=w;
node.weight = weight;
Graph[v].push_back(node);
if(!directed) //無向圖
{
node.num=v;
Graph[w].push_back(node);
}
}
void print()
{
for(int i=0; i<n; i++)
{
cout<<i<<":";
for(int j=0; j<Graph[i].size(); j++)
cout<<Graph[i][j].num<<"("<<Graph[i][j].weight<<")"<<" " ;
cout<<endl;
}
}
};
//最小生成樹的邊
struct Edge{
int v;
int w;
double weight;
Edge(int v,int w, double weight){
this->v =v ;
this->w =w ;
this->weight =weight ;
}
bool operator >(const Edge &n) const
{
return weight>n.weight; //最小堆
}
};
//有權有向圖
int n=8;
SparseGraph g2(n,false);
vector<Edge> mst;//最小生成樹的邊
double mstWeight;//最小生成樹的權值
vector<int> parent(n,-1);//並查集
//並查集獲取根節點
int getParent(int p)
{
if(p!=parent[p])
parent[p]=getParent(parent[p]) ;
return parent[p];
}
int main()
{
g2.addEdge(0,2,0.26);
g2.addEdge(0,4,0.38);
g2.addEdge(0,6,0.58);
g2.addEdge(0,7,0.16);
g2.addEdge(1,2,0.36);
g2.addEdge(1,3,0.29);
g2.addEdge(1,5,0.32);
g2.addEdge(1,7,0.19);
g2.addEdge(2,3,0.17);
g2.addEdge(2,6,0.40);
g2.addEdge(2,7,0.34);
g2.addEdge(3,6,0.52);
g2.addEdge(4,5,0.35);
g2.addEdge(4,6,0.93);
g2.addEdge(4,7,0.37);
g2.addEdge(5,7,0.28);
g2.print();
//kruskal算法
//定義一個最小堆,存儲所有邊
//針對無向圖,存1-2 不存2-1,否則重複
priority_queue<Edge,vector<Edge>,greater<Edge> > pq;
// 遍歷每個結點的鄰接邊 ,將邊加入最小堆中
for(int i=0; i<n; i++)
{
for(int j=0; j<g2.Graph[i].size(); j++)
{
node v = g2.Graph[i][j];
if(i<v.num)
{
Edge edge(i,v.num,v.weight);
pq.push(edge);
}
}
}
cout<<pq.size()<<endl;
for(int i=0; i<n; i++)
parent[i]=i;
while(!pq.empty() && mst.size()<n-1)
{
Edge edge = pq.top();
pq.pop();
//這條邊的兩個結點是相連的,會構成一個環,所以丟掉
if(getParent(edge.v)==getParent(edge.w))
continue;
mst.push_back(edge);
//將這兩個節點加入同一集合中,連接起來
int vRoot = getParent(edge.v);
int wRoot = getParent(edge.w);
parent[vRoot] = wRoot;//將一個結點的根節點連在另外一個結點下面
}
//計算最小權值
mstWeight=0.0;
for(int i=0; i<mst.size(); i++)
mstWeight+=mst[i].weight;
cout<<"Kruskal:"<<endl;
for(int i=0; i<mst.size(); i++)
cout<<mst[i].v<<"-"<<mst[i].w<<":"<<mst[i].weight<<endl;
cout<<"The Kruskal weight is:"<<mstWeight<<endl;
return 0;
return 0;
}