最小生成树

个连通图的生成树是一个极小的连通子图,它含有图中全部的顶点,但只有足以构成一棵树的n−1条边,若砍去它的一条边,则会使生成树变成非连通图;若给它增加一条边,则会形成图中的一条回路。对于一个带权连通无向图G=(V,E),生成树不同,其中边的权值之和最小的那棵生成树(构造连通网的最小代价生成树),称为G的最小生成树(Minimum-Spanning-Tree, MST)。

构造最小生成树有多种算法,但大多数算法都利用了最小生成树的下列性质:假设G=(V,E)是一个带权连通无向图,U是顶点集V的一个非空子集。若(u,v)是一条具有最小权值的边,其中u∈U,v∈V−U,则必存在一棵包含边(u,v)的最小生成树。

基于该性质的最小生成树算法主要有Prim算法和Kruskal算法,它们都基于贪心算法的策略。

通用的最小生成树算法:

GENERIC_MST(G){
    T=NULL;
    while T 未形成一棵生成树;
        do 找到一条最小代价边(u, v)并且加入T后不会产生回路;
            T=T U (u, v);
}

通用算法每次加入一条边以逐渐形成一棵生成树

如果是稠密图,用Prim算法;如果是稀疏图,用Kruskal算法。

1.prim算法

Prim算法构造最小生成树的过程如下图所示。初始时从图中任取一顶点(如顶点加入树T,此时树中只含有一个顶点,之后选择一个与当前T中顶点集合距离最近的顶点,=并将该顶点和相应的边加入T,每次操作后T中的顶点数和边数都增1。以此类推,直至图中所有的顶点都并入T,得到的T就是最小生成树。此时T中必然有n-1条边。

通俗点说就是:从一个顶点出发,在保证不形成回路的前提下,每找到并添加一条最短的边,就把当前形成的连通分量当做一个整体或者一个点看待,然后重复“找最短的边并添加”的操作。

Prim算法的步骤如下:

假设G={V,E}是连通图,其最小生成树T=(U,ET),ET是最小生成树中边的集合。

初始化:向空树T=(U,ET)中添加图G=(V,E)的任一顶点u0,使U={u0},ET=NULL

循环(重复下列操作直至U=V):从图G中选择满足{(u,v)∣u∈U,v∈V−U}且具有最小权值的边(u,v),加入树T,置U=U∪{v},ET=ETU{(u,v)}

,我们先构造一个邻接矩阵,如下图的下图所示。

void MiniSpanTree_Prim(AMGraph G,char u){ //最小生成树(Prim算法) 
    cout<<endl<<"最小生成树:"; 
    int k = LocateVex(G,u);
    int min;
    for(int j = 0; j < G.vexnum; j++)  //初始化(u与其他顶点的权值,不直接连接的为极大值) 
        if(j != k){
            closedge[j].adjvex = u;
            closedge[j].lowcost = G.arcs[k][j];//将v0顶点与之组成边的权值存入数组
        }
    closedge[k].lowcost = 0;    
    
    for(int i = 1; i < G.vexnum; i++){
        min = MaxInt;//初始化最下权值为∞,通常设置一个不可能的很大的数字
        for(int j = 0; j < G.vexnum; j++)
            if(closedge[j].lowcost != 0 && closedge[j].lowcost < min){//如果权值不为0且权值小于min
                min = closedge[j].lowcost;/则让当前权值成为最小值
                k = j;//将当前最小值的下标存入k
            }
        cout<<closedge[k].adjvex<<G.vexs[k]<<" "; //打印当前顶点边中权值的最小边
        closedge[k].lowcost = 0;
        for(int j = 0;j < G.vexnum; j++)
            if(G.arcs[k][j] < closedge[j].lowcost){
                closedge[j].adjvex = G.vexs[k];//将较小的权值存入 closedge
                closedge[j].lowcost = G.arcs[k][j];
            }    
    }        
} 

由算法代码中的循环嵌套可得知此算法的时间复杂度为O(n^2)

Prim算法的精髓是根据点的不同,最小生成树也可能不同。根据起始点,找到与该点相关联的其他顶点,选择其中边权值最小的纳入集合,以此类推,最终将所有顶点都纳入集合中,这个就是Prim算法的最小生成树。

完整代码

#include<iostream>
using namespace std;

#define MaxInt 32767  //表示极大值,用于初始化无向网 
#define MAXNUM  100

char visited1[MAXNUM];

typedef struct{
    char vexs[MAXNUM];  //顶点 
    int arcs[MAXNUM][MAXNUM];//边 
    int vexnum,arcnum;
}AMGraph; //邻接矩阵的数据类型 

struct{
    char adjvex;//最小边上的已选择顶点 
    int lowcost;//最小边上的权值 
}closedge[MAXNUM];//辅助数组 


int LocateVex(AMGraph G,char v){
    for(int i = 0; i < G.vexnum; i++){
        if(G.vexs[i] == v)return i;
    }
    return -1;
}

int CreateUNG(AMGraph &G){ //创建无向图 
    char v1,v2;
    int w;//权值 
    cout<<"请输入顶点数和边数:"; 
    cin>>G.vexnum>>G.arcnum;
    
    cout<<"请依次输入顶点:";
    for(int i = 0; i < G.vexnum; i++)cin>>G.vexs[i];
    
    for(int j = 0; j < G.vexnum; j++)
        for(int i = 0; i < G.vexnum; i++)
            G.arcs[j][i] = MaxInt;         //初始化邻接矩阵
    cout<<"请依次输入邻接边和权值:"<<endl;     
    for(int k = 0; k < G.arcnum; k++){
        cin>>v1>>v2>>w;
        int i = LocateVex(G,v1);
        int j = LocateVex(G,v2);
        G.arcs[i][j] = w;
        G.arcs[j][i] = w;
    }        
    return 1;
}


void MiniSpanTree_Prim(AMGraph G,char u){ //最小生成树(Prim算法) 
    cout<<endl<<"最小生成树:"; 
    int k = LocateVex(G,u);
    int min;
    for(int j = 0; j < G.vexnum; j++)  //初始化(u与其他顶点的权值,不直接连接的为极大值) 
        if(j != k){
            closedge[j].adjvex = u;
            closedge[j].lowcost = G.arcs[k][j];
        }
    closedge[k].lowcost = 0;    
    
    for(int i = 1; i < G.vexnum; i++){
        min = MaxInt;
        for(int j = 0; j < G.vexnum; j++)
            if(closedge[j].lowcost != 0 && closedge[j].lowcost < min){
                min = closedge[j].lowcost;
                k = j;
            }
        cout<<closedge[k].adjvex<<G.vexs[k]<<" "; 
        closedge[k].lowcost = 0;
        for(int j = 0;j < G.vexnum; j++)
            if(G.arcs[k][j] < closedge[j].lowcost){
                closedge[j].adjvex = G.vexs[k];
                closedge[j].lowcost = G.arcs[k][j];
            }    
    }        
} 


int main(){
    AMGraph G;
    CreateUNG(G);
    MiniSpanTree_Prim(G,'a');    
    return 0;
}

样例

6 10

a b 6

a c 1

a d 5

b c 5

c d 5

b e 3

c e 6

c f 4

d f 2

e f 6

2.Kruskal算法

Kruskal算法构造最小生成树的过程如下图所示。初始时为只有n个顶点而无边的非连通图 T=V,每个顶点自成一个连通分量,然后按照边的权值由小到大的顺序,不断选取当前未被选取过且权值最小的边,若该边依附的顶点落在T中不同的连通分量上,则将此边加入T,否则舍弃此边而选择下一条权值最小的边。以此类推,直至T中所有顶点都在一个连通分量上。

时间复杂度为O(loge)

struct node
{
    VerTexType Head;
    VerTexType Tail;
    ArcType lowcost;
}Edge[1000];
int Vexset[MVNum];
bool cmp(node a,node b)
{
    return a.lowcost<b.lowcost;
}
void MiniSpanTree_Kruskal(AMGraph G)
{
    int k=0;
    for (int i=0;i<G.vexnum;i++)
    {
        for (int j=0;j<G.vexnum;j++)
        {
            if (G.arcs[i][j]!=MaxInt)
            {
                Edge[k].Head=G.vexs[i];
                Edge[k].Tail=G.vexs[j];
                Edge[k].lowcost=G.arcs[i][j];
                k++;
            }
        }
    }
    sort(Edge,Edge+k,cmp);
    for (int i=0;i<k;i++)
        Vexset[i]=i;
    int wpl=0;
    for (int i=0;i<k;i++)
    {
        int v1=LocateVexAMG(G,Edge[i].Head);
        int v2=LocateVexAMG(G,Edge[i].Tail);
        int vs1=Vexset[v1];
        int vs2=Vexset[v2];
        if (vs1!=vs2)
        {
            cout<<Edge[i].Head<<" "<<Edge[i].Tail<<" "<<Edge[i].lowcost<<endl;
            wpl+=Edge[i].lowcost;
            for (int j=0;j<G.vexnum;j++)
            {
                if (Vexset[j]==vs2)
                    Vexset[j]=vs1;
            }
        }
    }
    cout<<"最小生成树总权值为:"<<wpl<<endl;
}

完整代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
int parent[10];
int n,m;
int i,j;

struct edge{
    int u,v,w; //边的顶点,权值
}edges[10];

//初始化并查集
void UFset(){
    for(i=1; i<=n; i++) parent[i] = -1;
}

//查找i的根
int find(int i){
    int temp;
    //查找位置
    for(temp = i; parent[temp] >= 0; temp = parent[temp]);
    //压缩路径
    while(temp != i){   //表明没找到根节点,因为根节点的父节点是-1 
        int t = parent[i];
        parent[i] = temp;
        i = t;
    }
    return temp;
}
//合并两个元素a,b
void merge(int a,int b){
    int r1 = find(a);
    int r2 = find(b);
    int tmp = parent[r1] + parent[r2]; //两个集合节点数的和
    if(parent[r1] > parent[r2]){
        parent[r1] = r2;
        parent[r2] = tmp;
    }else{
        parent[r2] = r1;
        parent[r1] = tmp;
    }
}

void kruskal(){
    int sumWeight = 0;
    int num = 0;
    int u,v;
    UFset();
    for(int i=0; i<m; i++)
    {
        u = edges[i].u;
        v = edges[i].v;

        if(find(u) != find(v)){ //u和v不在一个集合,两者的根不同 
            printf("加入边:%d %d,权值: %d\n", u,v,edges[i].w);
            num ++;
            merge(u, v); //把这两个边加入一个集合。
            sumWeight+=edges[i].w; 
        }
    }
    printf("最小生成树的权值之和为:%d \n", sumWeight);
}

//比较函数,用户排序
int cmp(const void * a, const void * b){
    edge * e1 = (edge *)a;
    edge * e2 = (edge *)b;
    return e1->w - e2->w;
}

int main() {

    scanf("%d %d", &n, &m);
    for(i=0; i<m; i++){
        scanf("%d %d %d", &edges[i].u,  &edges[i].v,  &edges[i].w);
    }
    qsort(edges, m, sizeof(edge), cmp);

    kruskal();

system("pause");
    return 0;
}
/*
测试数据:
6 10
1 2 6 
1 3 1
1 4 5
2 3 5
2 5 3
3 4 5
3 5 6
3 6 4
4 6 2
5 6 6
 */
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值