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;
}