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

最小生成树

最小生成树定义引用来源

所谓最小生成树,就是在一个具有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
*/
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值