Kruskal的MST算法的代码如何工作?

时间:2017-04-16 20:20:09

标签: c++ algorithm minimum-spanning-tree kruskals-algorithm

下面是 Kruskal算法的C ++代码,用于查找由我的教师给出的图的最小成本生成树。

我对代码不太了解。我想确切地知道代码的哪一部分正在检查在包含边缘的森林中形成一个循环。

我也想知道parent[]数组的目的究竟是什么。

此外,它是否比使用 DFS (深度优先搜索)检查周期更好?

以下是代码:

#include<stdio.h>
#include<stdlib.h>

int i, j, k, a, b, u, v, n, ne = 1;
int min, mincost = 0, cost[9][9], parent[9];

int find(int);
int uni(int, int);

int main()
{
    printf("\n\tImplementation of Kruskal's algorithm\n");
    printf("\nEnter the no. of vertices:");
    scanf("%d",&n);
    printf("\nEnter the cost adjacency matrix:\n");

    for (i = 1; i <= n; i++)
    {
        for(j = 1; j <= n; j++)
        {
            scanf("%d",&cost[i][j]);
            if(cost[i][j] == 0)
                cost[i][j] = 999;
        }
    }

    printf("The edges of Minimum Cost Spanning Tree are\n");

    while(ne < n)
    {
        for(i = 1, min = 999; i <= n; i++)
        {
            for(j = 1; j <= n; j++)
            {
                if(cost[i][j] < min)
                {
                    min = cost[i][j];
                    a = u = i;
                    b = v = j;
                }
            }
        }
        u = find(u);
        v = find(v);
        if(uni(u,v))
        {
            printf("%d edge (%d, %d) =%d\n", ne++, a, b, min);
            mincost += min;
        }
        cost[a][b] = 999;
    }
    printf("\n\tMinimum cost = %d\n",mincost);
}

int find(int i)
{
    while(parent[i])
    i = parent[i];
    return i;
}

int uni(int i,int j)
{
    if(i!=j)
    {
        parent[j]=i;
        return 1;
    }
    return 0;
}

注意:

我知道代码搞砸了,如果用户输入的值超过9,用户输入将导致失败,但我不想专注于那部分了解它是如何工作的。我只知道它选择最小成本边缘,检查它是否形成周期,然后将其值设置为无穷大(此处为999)。我不知道检查循环形成的方式和位置。请解释一下。

3 个答案:

答案 0 :(得分:3)

whilemain循环内的代码找到了尚未考虑的最轻边。该边缘位于节点uv之间。只有当uv已经属于同一棵树时,边缘才会形成一个周期

此:

u=find(u);
v=find(v);

查找uv所属的树的根。然后main将这两个根传递给uni

if(uni(u,v))
  ...

int uni(int i,int j)
  {
    if(i!=j)
      {
        parent[j]=i;
        return 1;
      }
    return 0;
  }

如果两个根相同,则代码不执行任何操作,不使用边。

答案 1 :(得分:3)

  

我想确切地知道代码的哪一部分正在检查在包含边缘的森林中形成一个循环。我也想知道parent []数组的目的究竟是什么。

您似乎理解Kruskal算法的一般概念,但不是一些更重要的细节。特别是,我必须假设您不理解此算法中"disjoint set"(a.k.a。&#34; set union&#34;和其他名称)数据结构的核心和必要用法。如果你这样做,你肯定会认识到在你的代码中,parent数组所服务的角色。即使您没有从名称中猜出,find()uni()函数也是一个绝对的赠品。

代码使用不相交的集合结构来跟踪到目前为止添加到图形中的边缘连接的顶点组。 find()函数确定给定顶点属于哪个集合,如果两个顶点属于同一集合,则拒绝候选边缘。当两个子图通过接受边连接时,uni()函数将两个集合成一个。

  

此外,它是否比使用DFS(深度优先搜索)检查周期更好?

性能细节在某种程度上取决于不相交集的实现。这里的一个特别简单,但更复杂的可以降低搜索的摊销成本,从而在整体算法上获得比使用DFS更好的性能限制。

答案 2 :(得分:3)

好的。在继续进行解释之前,请随意阅读并阅读this关于HackerEarth的Kruskal算法的精彩编写的教程,以便大致了解要查找的内容。

现在算法:

注意:首先忽略前三行,只需查看main中的代码,并假设所有变量都是在手工声明之前声明的。

现在让我们开始吧:

printf("\n\tImplementation of Kruskal's algorithm\n");
printf("\nEnter the no. of vertices:");
scanf("%d",&n);
printf("\nEnter the cost adjacency matrix:\n");
for(i=1;i<=n;i++)
{
    for(j=1;j<=n;j++)
    {
        scanf("%d",&cost[i][j]);
        if(cost[i][j]==0)
            cost[i][j]=999;
    }
}

这些第一行询问顶点的数量和邻接矩阵,其中每个顶点的成本为任何其他顶点。看起来当没有任何边连接2个顶点时,成本设置为999,这样当设置为0时它不会使代码出错。

这是邻接矩阵的样子。

假设您的图表看起来像Simple weighted directed graph 邻接矩阵如下:

   1  2  3
  _________
1| 0  0  11
2| 0  0  0
3| 11 6  0

表示1连接到3,成本为11. 2未连接任何顶点,3连接到1,成本为11,2连接到成本6.上面的代码将上面的矩阵更改为:

   1    2    3
  _____________
1| 999  999  11
2| 999  999  999
3| 11   6    999

因此算法不会选择0作为最低成本。并避免选择非连接顶点。

之后我们有:

printf("The edges of Minimum Cost Spanning Tree are\n");
while(ne < n)
{
    for(i=1,min=999;i<=n;i++)
    {
        for(j=1;j <= n;j++)
        {
            if(cost[i][j] < min)
            {
                min=cost[i][j];
                a=u=i;
                b=v=j;
            }
        }
    }
    u=find(u);
    v=find(v);
    if(uni(u,v))
    {
        printf("%d edge (%d,%d) =%d\n",ne++,a,b,min);
        mincost +=min;
    }
    cost[a][b]=999;
}
printf("\n\tMinimum cost = %d\n",mincost);

首先,您必须知道Kruskal的算法使用Connected Components来确定2个顶点是否连接(这也是kruskal算法不创建圆圈的原因)。让我们来看代码的作用。

for(i=1,min=999;i<=n;i++)
{
    for(j=1;j <= n;j++)
    {
        if(cost[i][j] < min)
        {
            min=cost[i][j];
            a=u=i;
            b=v=j;
        }
    }
}

这有点直截了当。它做的是通过矩阵并找到其中的最小值。所以我给它的例子首先会找到6。 所以min = 6,u = 3(起始顶点),v = 2(结束顶点)。因此,现在为了理解将要遵循的内容,您必须阅读有关不相交集和连接组件的内容。幸运的是,在HackerEarth上再次有一个10分钟的阅读教程,它将帮助您了解Connected Components的工作原理。你可以找到它here

所以这是正在发生的事情。该算法表示目前最小的成本是3> 2,成本为6.我们将其添加到我们在后台使用连接组件构建的图表中,并将成本设置为999,因此我们不会重新考虑它。所以这里:u=find(u);

它转到父数组并检查父母的位置3(arr[3])?答案是3,因为我们还没有将它连接到任何其他组件。接下来它对2(arr[2])做同样的事情,因为我们没有连接它也保持不变。其他任何事情。然后将它们统一到一个。这就是数组现在变成:

[1, 2, 3] -> [1, 3, 3] (minCost is now equal to 6)

然后它将min添加到minCost这是答案。并将成本从3> 2更改为999,因此我们不会重新考虑它。

重复这个过程,以便我们:

    // min=6, u=3, v=2
    [1, 2, 3] -> [1, 3, 3] // minCost = 6
    // min=11, u=1, v=3
    [1, 3, 3] -> [1, 3, 1] // minCost = 17
    // min=11, u=3, v=1 !!! CAREFUL NOW
    Moving over to 
    parent of parent[3] == parent[1] meaning that they have the same parent so they are CONNECTED. 
    if(uni(u,v)) <-- This won't run since uni(3, 1) will return 0 meaning that they are connected so the min won't be added to minCost this time.

这就是算法结束的地方。它打印的最终成本为17,你就完成了。 ne变量只是作为一个计数器,使打印更容易理解。

我希望这会对你有所帮助。请务必阅读我链接的教程,因为精彩的Kruskal算法会帮助您理解逻辑。

上述链接:

Kruskal的算法:https://www.hackerearth.com/practice/algorithms/graphs/minimum-spanning-tree/tutorial/

已连接的组件:https://www.hackerearth.com/practice/data-structures/disjoint-data-strutures/basics-of-disjoint-data-structures/tutorial/