【问题标题】:Increasing efficiency of union-find提高 union-find 的效率
【发布时间】:2017-12-06 06:22:21
【问题描述】:

我正在尝试优化联合查找算法以查找图像中的连接组件。我的图像可以是 2d 或 3d 文件,由 0 和 1 组成。我在这个线程中找到了一个实现:Connected Component Labelling,用户 Dukering 给出了答案。

我根据我的目的调整了该代码。代码有效,但执行时间很快变得过长。我不明白这个问题。

我的代码如下所示。我正在测试它的文件链接在这里:https://utexas.box.com/s/k12m17rg24fw1yh1p21hytxwq5q8959u 那是一个 2223x2223 大小的文件(在下面的程序中定义)。

正如原用户所说,这是 union-find 的基本实现,可以提高效率。我不明白怎么做。另外,我在Matlab中测试过这张图,Matlab的速度要快很多。例如,上面链接的图像在我的计算机上大约需要 1.5 分钟,但 Matlab 使用 bwlabel 只需一秒钟。我检查了 bwlabel 使用的算法,它似乎是 union-find 的一些变体,这就是我首先开始这项工作的原因。如何让我的代码尽可能快地工作?我还应该提到,我希望在更大的图像(大至 1000^3)上运行我的代码。我当前的版本无法做到这一点。

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

    #define w 2223
    #define h 2223

    void writeArrayInt(int *data, int dims[], char *filename)
    {
     FILE *fp;

     fp = fopen(filename,"w"); 

     /* write grid dimensions */
     fwrite(dims, sizeof(int), 3, fp); 

      /* write data array */
      fwrite(data, sizeof(int), w*h, fp);

      fclose(fp);
      }

      void readArrayInt(int *data, int dims[], char *filename)
      {
       FILE *fp;

       fp = fopen(filename,"r"); 

       /* read grid dimensions */
       fread(dims, sizeof(int), 3, fp); 

       /* read data array */
       fread(data, sizeof(int), w*h, fp);

       fclose(fp);
       }

       void doUnion(int a, int b, int *component)
       {
        // get the root component of a and b, and set the one's parent to the other
       while (component[a] != a)
         a = component[a];
       while (component[b] != b)
         b = component[b];
       component[b] = a;
       }

       void unionCoords(int x, int y, int x2, int y2, int *component, int *input)
       {
        int ind1 = x*h + y;
        int ind2 = x2*h + y2;
        if (y2 < h && x2 < w && input[ind1] && input[ind2] && y2 >= 0 && x2 >= 0)
    doUnion(ind1, ind2, component);
        }

       int main()
       {
       int i, j;
       int *input = (int *)malloc((w*h)*sizeof(int));
       int *output = (int *)malloc((w*h)*sizeof(int));
       int dims[3];

       char fname[256];
       sprintf(fname, "phi_w_bin");
       readArrayInt(input, dims, fname); 

       int *component = (int *)malloc((w*h)*sizeof(int));

       for (i = 0; i < w*h; i++)
         component[i] = i;

 for (int x = 0; x < w; x++)
    for (int y = 0; y < h; y++)
    {
        unionCoords(x, y, x+1, y, component, input);
        unionCoords(x, y, x, y+1, component, input);
        unionCoords(x, y, x-1, y, component, input);
        unionCoords(x, y, x, y-1, component, input);
        unionCoords(x, y, x+1, y+1, component, input);
        unionCoords(x, y, x-1, y+1, component, input);
        unionCoords(x, y, x+1, y-1, component, input);
        unionCoords(x, y, x-1, y-1, component, input);
    }

for (int x = 0; x < w; x++)
{
    for (int y = 0; y < h; y++)
    {
        int c = x*h + y;
        if (input[c] == 0)
        {
            output[c] = input[c];
            continue;
        }
        while (component[c] != c) c = component[c];

        int c1 = x*h + y;
        output[c1] = component[c];
    }
}

sprintf(fname, "outputImage2d");
writeArrayInt(output, dims, fname);  

free(input);
free(output);
free(component);  
}

【问题讨论】:

  • 如果您的代码正在运行并且您希望获得有关如何提高其性能的建议,那么Code Review 是这个问题的更合适的地方。
  • 请缩进您的代码。如果你使用 tab 键缩进,现在是时候去寻找如何告诉你的 IDE 插入空格了......
  • 您的缩进确实需要帮助:修复它会帮助您获得帮助。

标签: c algorithm matlab


【解决方案1】:

我建议对您的联合查找结构进行两项改进:

  • 实际上实现 union 和 find! 如果您有一个有效的 find 方法,实现 union 会变得更加简单,因为您不需要 while (component[c] != c) 类型的行。作为参考,请查看有关 union-find 数据结构的信息 Wikipedia entry
  • 实现一些常见的加速启发式方法,例如路径压缩(将 find(x) 返回的值存储在 component[x] 中,从而减少第二次调用 find(x) 所需的时间)和 union-by-rank 或 union -by-size(使较大的集成为较小集的父集)

编辑:由于似乎需要对另一个答案进行一些澄清,我将自己添加一个最小的实现:

typedef struct {
    int* parent;
    int size;
} union_find;

union_find make_sets(int size) {
    union_find result;
    result.parent = malloc(sizeof(int) * size);
    result.size = size;
    for (int i = 0; i < size; ++i) {
        result.parent[i] = size;
    }

    return result;
}

int find(union_find uf, int i) {
    if (uf.parent[i] < uf.size)
        return uf.parent[i] = find(uf, uf.parent[i]);
    return i;
}

void do_union(union_find uf, int i, int j) {
    int pi = find(uf, i);
    int pj = find(uf, j);
    if (pi == pj) {
        return;
    }
    if (pi < pj) {
        // link the smaller group to the larger one
        uf.parent[pi] = pj;
    } else if (pi > pj) {
        // link the smaller group to the larger one
        uf.parent[pj] = pi;
    } else {
        // equal rank: link arbitrarily and increase rank
        uf.parent[pj] = pi;
        ++uf.parent[pi];  
    }
}

【讨论】:

  • 我有点困惑。是否只有一个父数组为每个人存储父母?所以当我初始化时,我只有一个 union_find 结构变量,uf?然后我所有的操作都是在uf上进行的?如果是这种情况,那么我最初是否只执行 make_sets(N) 其中 N 是数组的总大小?
  • 是的,在您的情况下,父数组与组件数组具有几乎相同的功能。唯一的区别是数组的初始化方式(parent[i] = size 而不是 parent[i] = i)以及如果 parent[i] >= size 时这些条目的含义。 make_sets(w*h) 在语义上与您的初始化相同 (for (i = 0; i &lt; w*h; i++) component[i] = i;)
  • 看起来这很好用,非常感谢 Tobias!我还有一个问题:如果我对 3d 做同样的事情,我将有 26 个连接(而不是我的示例代码中的 8 个连接),事情会变慢吗?从我对时间复杂度的理解来看,应该和二维的情况差不多吧?
  • union 和 find 的时间复杂度应该几乎与元素的数量无关,是的。但是由于您可能会有更多的像素来迭代每个像素的更多邻居,因此整体运行时间可能会更大一些
【解决方案2】:

如果正确实施,联合查找应该在恒定时间内工作。

这里有一些想法:

-- 修改find,这样每次上树直到到达根节点(根节点是具有NODE.up = NODE 属性的节点),更新所有节点的所有UP 节点你跟着上去。换句话说,当您查找 2 个节点的连接组件时,您会为该路径上的所有节点更新该组件(表示为其根节点的索引)。

--当你第二次找到一个节点的组件时,它不仅对于它自己而且对于中间节点来说都是常数时间。

-- 联合应该一直花时间array[node] = parent_node

【讨论】:

  • 只是吹毛求疵:union-find 几乎是常数时间,但对于所有实际问题它是:)
【解决方案3】:

使用按秩并集的不相交集的良好工作算法之一 路径压缩如下:

实现,使用struct Node component[]。其中包含所有元素的数组。

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

struct Node
{
    // Needed for union and find.
    int parent;
    int rank;
};

// Find implementation using path compression, NOTE: a is index of the element to be found.
int find (struct Node *component, int a)
{
    if (component[a].parent != a)
        return component[a].parent = find(component[a], component[a].parent)
    return a;
}

// Union implementation using rank. NOTE: a and b are index of the element
void union(struct Node *component, int a, int b)
{
    if (find(component, a) != find(component, b))
    {

        if (component[a].rank == component[b].rank)
            component[a].rank += 1;

        if (component[a].rank >= component[b].rank)
            component[b].parent = a;
        else
            component[a].parent = b;
    }    
}

您可以使用上述函数,在恒定时间内(摊销)进行 Union-Find。应该清楚的是,您可能必须修改结构,因为它适合您的数据。

您也可以使用模板在 C++ 中实现它。但由于问题被标记为 C,因此我提供了这个解决方案。

如果您想了解上述算法,此链接可能会有所帮助。Union-Find Algorithm

请发表评论以获得进一步的说明。

【讨论】:

  • Union-find 可以在不使用指针间接的情况下非常有效地实现,只使用一个大型 malloc 而不是每个节点一个。恕我直言,使用指针比仅使用存储每个元素的父索引的平面数组更加混乱。
  • @Tobias 为了完整起见,我已经添加了这两个实现。请查看最近的更改。
  • 我想我需要澄清一下我的意思:这种方式的实现本身更具可读性,但是如果你把它们混在一起,IMO 仍然在指针和索引之间跳转是没有帮助的。 find(...) 的 return 语句似乎返回了一个不正确的值(应该是父指针或类似的东西,但只是输入参数 a,在方法中根本没有改变) OP 的实现似乎已经基于索引,那么为什么要跳转到需要显式存储索引(val)的指针?
  • @Tobias 谢谢!现在我懂了。我觉得你的第二个意见是对的。我不认为您对 find 的看法是正确的。请仔细查看,它也以if 条件返回。我请求您查看我最近的编辑和评论。谢谢!
  • @Tobias 组件数组,必须同时有父索引和排名,这样才能发挥这两种启发式的好处。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-01-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-27
  • 2015-10-08
  • 2021-08-04
相关资源
最近更新 更多