您的位置:首页 > 其它

第七章 图(最小生成树之prime算法和 kruskal算法)

2017-12-09 15:06 537 查看

最小生成树

最小生成树定义引用来源

所谓最小生成树,就是在一个具有N个顶点的带权连通图G中,如果存在某个子图G’,其包含了图G中的所有顶点和一部分边,且不形成回路,并且子图G’的各边权值之和最小,则称G’为图G的最小生成树。

由定义我们可得知最小生成树的三个性质:

•最小生成树不能有回路。

•最小生成树可能是一个,也可能是多个。

•最小生成树边的个数等于顶点的个数减一。

prime算法

[prime算法演示实例]

(http://blog.csdn.net/lqcsp/article/details/14118871)









以下面的图为例,实现prime算法



#include<iostream>
#include<cstring>
using namespace std;

const int inf=10000;

int graph[4][4]={
{inf,6,1,inf},
{6,inf,5,3},
{1,5,inf,4},
{inf,3,4,inf}
};

bool vis[4];
int m=4;
int dis[100];

int prime(int cur)
{
int index;
int sum = 0;
memset(vis, false, sizeof(vis));
vis[cur] = true;//最小生成树的起点标记访问过
for(int i = 0; i < m; i ++){
dis[i] = graph[cur][i];
}
//cur点已经访问过,剩下的结点数目是m-1
for(int i = 1; i < m; i ++){
int minmum=inf;
for(int j = 0; j < m; j ++){
if(!vis[j] && dis[j] < minmum){
minmum = dis[j];
index = j;
}
}
vis[index] = true;
sum += minmum;//求最小生成树的最小边权之和
//更新dis数组,对index点来说,与之相连的边和对应的以前的边
//比较,选择较短的边。比如开始和点2相连的所有边是6,inf,5,3
//index更新为点4后,与4相连的所有边是inf,3,4,inf,原来的dis
//是6,inf,5,3更新成6,3,4,3
for(int j = 0; j < m; j ++){
if(!vis[j] && dis[j] > graph[index][j]){
dis[j] = graph[index][j];//更新dis数组
}
}
}
return sum;
}

int main(){
cout<<prime(1)<<endl;
return 0;
}


kruskal算法

讲kruskal算法之前,先讲并查集

并查集

并查集根据其名字干两件事:

1.把元素a所在的集合和元素b所在的集合合并为一个集合。

2.查元素a和元素b是否属于同一个集合。

下面给出代码(含有解释),该代码合并集合的方法是根据树的深度,把深度小的树(集合)合并到深度大的(集合)上,如下示例。



/*
并查集思路。
并查集算法的思想,结合了树的思想。
刚开始给出n个元素,这n个元素可以看成n棵树。对于其中一个元素a,
如果另一个元素b和a属于同一个集合,那么我们可以把b当做a的父母,
或者把a当做b的父母(起初,每个元素的父母是他自己),如果元素c
和a,b仍然属于同一个集合,那么我们可以把a,b中任一个当做c的父母,
或者把c当做a或b的父母,以此类推。这样集合用一棵树表示了。不同
的集合表示成了不同棵树。如果我们想把不同集合合并,我们只需要将
一个集合中任一个元素当做另一个集合的父母即可(即在两棵树直接添
加了一条边,把两棵树连在了一起)。如果我们想查两个元素d,e是否
属于同一个集合,我们只需要一直递归向上查d,e的父母(即他们分别
所属的树根)(请记得起初的时候每个元素的父母是他自己,递归到最
后一定能查出一个值),如果他们的父母相同,则他们属于同一个集合,
否则,不属于同一个集合。
上面的描述中,还有一点补充,给每一个元素定个级别,刚开始每个元
素的级别是1,表明以该元素为根的树深为1。
*/

/*
本程序例子是,包含6个结点,分别是1,2,3,4,5,6。7其中1,2,
3,7属于一个集合,4,5属于一个集合。6属于一个集合
*/

#include <iostream>
#include <cstring>
#include<algorithm>
using namespace std;

const int maxn = 10000;
int par[maxn];     //结点的父母
int r[maxn];    //每个结点的等级,即以该结点为根的树的深度

//初始化n个元素,父母是他自己,等级(树深)初始化为1
void init(int n)
{
for (int i = 1; i <=n; i++) {
par[i] = i;
r[i] = 1;
}
}

//查询树根
int find_root(int x) {
if (par[x] == x) {
return x;
}
else {
return find_root(par[x]);
}
}

//合并x和y所属集合
void united(int x, int y) {
x = find_root(x);
y = find_root(y);
if (x == y) return;//属于同一棵树,无需进行合并
//深度浅的树合并到深度大的树上
if (r[x]<r[y]) {
par[x] = y;r[y]=max(r[x]+1,r[y]);
} else {
par[y] = x;r[x]=max(r[x],r[y]+1);
}
}

//判断x和y是否属于同一个集合
bool same_set(int x, int y) {
return find_root(x) == find_root(y);
}

int main()
{
int n,m;
cout<<"请输入元素的总数: ";cin>>n;init(n);
cout<<"请输入集合的总数: ";cin>>m;
cout<<"输入每个集合中的任意两个元素(最多情况数为该集合元素数减1):"<<endl;
//n个元素构成m个集合,最多只需输入n-m对元素即可完成所有关系创建
//比如1,2,3,7为一个集合,4,5为一个集合,6为一个集合,7个元素
//形成3个集合,只需要输入1、2,1、3,4、5这三对(<=7-3)即可
int e,p;
for(int i=1;i<=n-m;i++){
cin>>e>>p;
p=find_root(p);
par[e]=p;   //路径压缩,e的父母直接变为p的树根
r[p]=2; //由于建立的树只有两层,孩子层深度为1,父母层深度为2
//这样建立起来的树只有两层,一个父母,众多个孩子
}

cout << "输入两个元素,把这两个元素所在的集合合并: ";
int e1,e2;
cin>>e1>>e2;
united(e1, e2);//把e1,e2所在的两个集合合并

cout << "输入两个元素,查询是否属于一个集合: ";
cin >> e1 >> e2;
if(same_set(e1,e2)) cout<<"same"<<endl;
else cout<<"different"<<endl;

return 0;
}

/*
本程序样例输入输出:

请输入元素的总数: 7
请输入集合的总数: 3
输入每个集合中的任意两个元素(最多情况数为该集合元素数减1):
1 2
2 3
4 5
6 6
输入两个元素,把这两个元素所在的集合合并: 2 5
输入两个元素,查询是否属于一个集合: 1 4
same

*/


kruskal算法过程

构造一个只含n个顶点,而边集为空的子图,若将该子图中各个顶点看成是各棵树的根节点,则它是一个含有n棵树的森林 。之后,从网的边集中选取一条权值最小的边,若该边的两个顶点分属不同的树 ,则将其加入子图,也就是这两个顶点分别所在的 两棵树合成一棵树;反之,若该边的两个顶点已落在同一棵树上,则不可取,而应该取下一条权值最小的边再试之。依次类推,直至森林只有一棵树。kruskal算法能够在并查集的基础很快的实现。

以下面的图为例,实现kruskal算法



#include<iostream>
#include<algorithm>
using namespace std;

const int maxn=10000;
int par[maxn];//结点的父母,用并查集
int r[maxn];//每个结点等级(以该结点为树根的树深),用并查集

typedef struct{
int st;//一条边的起始结点
int en;//一条边的结束结点
int w;//边权重
}edge_node;

edge_node edge[maxn/2*maxn];

//并查集算法的初始化
void init(int n){
for(int i=1;i<=n;i++){
par[i]=i;
r[i]=1;
}
}
//并查集算法的找树根
int find_root(int x){
if(x==par[x]) return x;
else return find_root(par[x]);
}
//并查集算法的合并集合
void united(int x,int y){
x=find_root(x);
y=find_root(y);
if(x==y) return;
if(r[x]<r[y]) {par[x]=y;r[y]=r[x]+1;}
else{par[y]=x;r[x]=r[y]+1;}
}

bool cmp(edge_node e1,edge_node e2){
return e1.w<e2.w;
}

int kruskal(int n,edge_node edge[]){
int sum=0;
init(n);
sort(edge+1,edge+n+1,cmp);
for(int i=1;i<=n;i++){
if(find_root(edge[i].st)!=(find_root(edge[i].en))){
united(edge[i].st,edge[i].en);
sum+=edge[i].w;
}
}
return sum;
}

int main(){
int n;
cout<<"请输入边的条数: ";cin>>n;
int x,y,w;
for(int i=1;i<=n;i++){
cout<<"请输入第"<<i<<"条边的两个顶点及权重: "<<endl;
cin>>x>>y>>w;
edge[i].st=x;edge[i].en=y;edge[i].w=w;
}
cout<<"最小生成树的边权之和: ";
cout<<kruskal(n,edge)<<endl;
return 0;
}

/*
输入输出样例:

请输入边的条数: 5
请输入第1条边的两个顶点及权重:
1 2 6
请输入第2条边的两个顶点及权重:
1 3 1
请输入第3条边的两个顶点及权重:
2 3 5
请输入第4条边的两个顶点及权重:
2 4 3
请输入第5条边的两个顶点及权重:
3 4 4
最小生成树的边权之和: 8
*/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐