【问题标题】:How does this code for Kruskal's MST algorithm work? [closed]Kruskal 的 MST 算法的代码是如何工作的? [关闭]
【发布时间】:2017-04-16 20:20:08
【问题描述】:

下面是 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)。我不知道它是如何以及在哪里检查循环形成的。请帮忙解释一下。

【问题讨论】:

  • 大家的时间有限。如果代码不是很短且呈现良好,您将很难找到有人为您解释您的代码。 int i,j,k,a,b,u,v,n,ne=1; 到底是什么?我们应该如何解释它们各自的含义?
  • 与流行的看法相反,没有“C/C++”语言。您必须确定您使用的是 C 还是 C++,并相应地编辑您的问题。
  • 我知道没有这样的 lang。而且我认为不存在如此流行的信念。我只想说,上面的代码同时适用于 C 编译器和 C++ 编译器。这不是我担心的代码,而是它用于检查循环是否存在的逻辑。
  • @50calrevolver,我们这些经常使用 SO 的人都知道,许多在这里提出问题的人确实似乎没有认识到 C 和 C++ 是不同的语言,既不是其他语言的子集。如果在两者的共同子集中编写确实对您很重要——这不是一项微不足道的任务——那么就这么说吧。否则,选择一个。
  • 我不会对任何在理解包含int i,j,k,a,b,u,v,n,ne=1; 之类的程序有困难的人提出异议。

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


【解决方案1】:

main 中的while 循环内的代码找到尚未考虑的最轻边。该边位于节点u 和v 之间。只有当u 和v 已经属于同一棵树时,边才能形成循环。

这个:

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

查找u 和v 所属的树的根。然后main 将这两个根传递给uni:

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

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

如果两个根相同,则代码什么也不做,不使用边。

【讨论】:

  • 感谢您的回答。还有 uni(u, v) 函数到底是做什么的?
  • @50calrevolver:如果根相同,什么都不做,返回0;如果根不同,则通过将一个根作为另一根的父级来合并两棵树,并返回 1。如果返回 1,则调用代码报告 MST 包含边,并更新树的成本。
  • 谢谢,这真的很有帮助。
【解决方案2】:

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

您似乎了解 Kruskal 算法的一般概念,但不了解一些更重要的细节。特别是,我必须假设您不了解该算法中"disjoint set"(又名“集合联合”和其他名称)数据结构的核心和基本用途。因为如果你这样做了,你肯定会在你的代码中认识到,这就是 parent 数组所扮演的角色。即使您没有从名称中猜到,find() 和 uni() 函数也是一个绝品。

代码使用不相交集结构来跟踪哪些顶点组是由迄今为止添加到图中的边连接的。 find() 函数确定给定顶点属于哪个集合,如果两个顶点属于同一集合,则候选边将被拒绝。 uni() 函数在通过接受一条边连接两个子图时将两个集合合二为一。

另外,它是否比使用 DFS(深度优先搜索)检查循环更好?

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

【讨论】:

  • 我承认我没有任何此类数据结构的先验知识,因此我无法弄清楚甚至猜测它的用法以及代码的逻辑。感谢您的有用解释。
【解决方案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 时不会出错。

这是邻接矩阵的样子。

假设您的图表如下所示 邻接矩阵如下:

   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);

首先你要知道克鲁斯卡尔算法使用连通分量来判断两个顶点是否连通(这也是克鲁斯卡尔算法不创建圆的原因)。那么让我们来看看代码的作用吧。

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 变量只是作为一个计数器,使打印更容易理解。

我希望这对你有帮助。请务必阅读我链接的教程,它们将真正帮助您理解奇妙的克鲁斯卡尔算法的逻辑。

上面提到的链接:

克鲁斯卡尔算法: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/

【讨论】:

    猜你喜欢
    • 2012-06-01
    • 1970-01-01
    • 2011-05-16
    • 2012-09-14
    • 1970-01-01
    • 1970-01-01
    • 2012-09-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多