提高工会效率

时间:2017-07-03 04:28:44

标签: c algorithm matlab

我正在尝试优化联合查找算法,以便在图像中查找连接的组件。我的图像可以是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);  
}

3 个答案:

答案 0 :(得分:2)

我建议您对union-find结构进行两项改进:

  • 实际上实现了union 和find!如果你有一个有效的find方法,实现union会变得简单得多,因为你不需要while (component[c] != c)种行。有关参考,请查看有关union-find数据结构的信息Wikipedia entry
  • 实现一些常见的加速启发式方法,如路径压缩(存储find(x)component[x]中返回的值,从而减少第二次调用find(x)所需的时间)和联合-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];  
    }
}

答案 1 :(得分:1)

如果正确实施,Union-find应该在恒定时间内工作。

以下是一些想法:

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

- 第二次找到节点的组件时,它不仅是自身的常量时间,也是中间节点的常量时间。

- 工会应该一直花费时间array[node] = parent_node

答案 2 :(得分:-1)

使用按等级联合的不相交集的良好工作算法之一 和路径压缩如下:

使用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;
    }    
}

您可以使用上述功能,以恒定时间(分期)进行联合查找。应该很清楚,您可能必须修改结构,因为它适合您的数据。

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

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

请评论任何进一步的澄清。