最小生成树(1)--Kruskal算法

图的最小生成树

图的最小生成树,是指用最小的边让图连通,让任意两点之间可以互相到达。图如果有n个顶点,则应该有n-1条边。此时连通无向图没有回路,就是一颗树,所以称为最小生成树。

最小生成树是让边的总长度之和最短,其中一种方法是可以选择最短的边,然后依次选择次短的边,直到选择了n-1条边为止。所以可以先将所有的边按权值从小到大进行排序(为了排序方便可以用结构体存储图的边值数据),然后再选择,选择n-1条边让整个图连通。当然有的权值小的边是不能选择的,那些会形成回路的边不能选,只能跳过这条边。

在这个过程中,最难实现的是判断两个顶点有没有连通。可以使用深度优先搜索或者广度优先搜索。不过有更方便的方法,判断顶点是否连通,可以用并查集查顶点是否有同一个祖先,如果有同一个祖先,就是连通的,如果不是同样的祖先,就表示没有连通,就可以选择这两个顶点的边。

举个栗子:
比如下面的图,用kruskal算法找最小生成树的方法如下。
原图:
最小生成树(1)--Kruskal算法_第1张图片
选择就先找权值最小的边,很明显是顶点1和2的权值只有1,而且两个顶点现在没有连通,则选择这条边将1和2连通。接下来的选择顶点1和3的边,权值次小为2,而且此时1和3没有连通。所以可以选这条边。如此选择下去,直到选满n-1条边,也就是5条结束。

注意在最后一条边,也就是下图的标号5的边,是1、2、3连通,4、5、6连通时,本来按照从小到大应该玄子顶点4和5的边为7,但是此时顶点4和5已经通过顶点6连通了,就不需要再选择这条边了,也就是我们之前说的通过并查集看是否连通要跳过边的情况。
最小生成树(1)--Kruskal算法_第2张图片

找完所有边之后,形成的最小生成树:
最小生成树(1)--Kruskal算法_第3张图片

接下来小小的略微说一说并查集:

并查集

并查集就像是一片森林,不断的合并成一棵大树。要判断两个结点是否是同一棵树,要注意必须求两个结点的根源,也就是两个结点的根结点是同一个则在同一个集合中。

所以我们用一个数组表示结点,用数组的值记录它的根结点可以不断更新。首先,我们将结点都当成独立的,也就是将数组值初始化为它的下标,用之后按要求将结点合并。合并也就是建立并查集的过程。

怎么实现呢?
两个结点用递归先搜索到它的根结点,比较两个根结点是否相同。如果相同,就不用处理,若不相同,我们就规定靠左,也就是把数组下标小的作为大的根,就是把数组右边的集合,作为左边集合的子集合。用递归的好处还有,在每一次函数返回的时如果根结点有变,则顺带将之前同一棵树下的结点都改成同一个根。

实现代码:

//递归寻求结点的祖先
int getf(int v)
{
    if(f[v]==v)
        return v;
    else
    {
        f[v]=getf(f[v]);
        return f[v];
    }
}

//合并并查集两子集
int merge(int u, int v)
{
    int comp1,comp2;
    comp1=getf(u);
    comp2=getf(v);

    if(comp1==comp2)
        return 0;
    else
    {
        f[comp1]=comp2;
        return 1;
    }
    return 0;
}

讲好了并查集,我们就可以用并查集来做最小生成树了。在这里我们统计一下最小生成树的边权值总和。下面贴出实例代码:

#include 
#include 

int n,m,sum,count;
struct edge
{
    int u;
    int v;
    int w;
};
struct edge e[10];
int f[7]={0},book[10];

//快速排序
void quickSorted(int left, int right)
{
    int i,j;
    struct edge t;
    if(left>right)
        return;

    i = left;
    j = right;
    while(i!=j)
    {
        //一定要先从右往左找
        while(e[j].w>=e[left].w && iwhile(e[i].w<=e[left].w &&i//交换两数的位置
        if(i//将基准数归位
    t=e[left];
    e[left]=e[i];
    e[i]=t;
    quickSorted(left,i-1);//递归继续处理左边的序列
    quickSorted(i+1,right);//递归继续处理右边的序列
    return;
}

//递归寻求结点的祖先
int getf(int v)
{
    if(f[v]==v)
        return v;
    else
    {
        f[v]=getf(f[v]);
        return f[v];
    }
}

//合并并查集两子集
int merge(int u, int v)
{
    int comp1,comp2;
    comp1=getf(u);
    comp2=getf(v);

    if(comp1==comp2)
        return 0;
    else
    {
        f[comp1]=comp2;
        return 1;
    }
    return 0;
}

//主程序
int main(void)
{
    int i;
    //读入n,m表示顶点个数和边的条数
    printf("Please input the numbers of Graph' vertex and edge divided by a space:\n");
    scanf("%d %d",&n,&m);

    //输入边,存储边的关系
    printf("Please input the two vertexes and their edge divided by space.\n");
    for(i=1;i<=m;i++)
        scanf("%d %d %d",&e[i].u,&e[i].v,&e[i].w);

    quickSorted(1,m);//将边进行排序

    //并查集初始化
    for(i=1;i<=n;i++)
        f[i]=i;

    //Kruskal算法
    for(i=1;i<=m;i++)
    {
        //判断边的两个顶点是否连通
        if(merge(e[i].u,e[i].v))
        {
            book[i]=1;//记录哪些边构成了最小生成树
            count++;
            sum=sum+e[i].w;
        }
        if(count == n-1)
            break;
    }

    //输出最小生成树的边
    printf("Print the smallest tree:\n");
    for(i=1;i<=m;i++)
    {
        if(book[i]==1)
            printf("%d %d %d\n",e[i].u,e[i].v,e[i].w);
        else
            continue;
     }

    printf("The wight of all: %d.\n",sum);
    return 0;
}

结果:
最小生成树(1)--Kruskal算法_第4张图片

以上就是最小生成树的kruskal算法的内容啦,之后还有最小生成树的另一种方法Prim算法,这个和最短路径的Dijkstra算法很相似。待续…

你可能感兴趣的:(算法)