2023-03-05:二维图像连通域计算方法

并查集

参考文章一
参考文章二
并查集(Disjoint-Set)是一种可以动态维护若干个不重叠的集合,并支持合并与查询两种操作的一种数据结构。
并查集有两个基本操作:

  1. Find: 查找元素所属子集;
  2. Union:合并两个子集为一个新的集合;
    在这里插入图片描述在这里插入图片描述根节点的父节点是其本身,若某个节点的父节点不是其本身,则继续find(父节点)直至找到父节点为本身的根节点。
#include <vector>
class DisjSet
{
private:
    std::vector<int> parent;

public:
    DisjSet(int max_size) : parent(std::vector<int>(max_size))
    {
        // 初始化每一个元素的根节点都为自身
        for (int i = 0; i < max_size; ++i)
            parent[i] = i;
    }
    int find(int x)
    {
        return parent[x] == x ? x : find(parent[x]);
    }
    //非递归版本
    int find(int p)
    {
	    while(parent[p] != p ) p = parent[p];
	    return p;
  	}
    void to_union(int x1, int x2)
    {
        parent[find(x1)] = find(x2);
    }
    // 判断两个元素是否属于同一个集合
    bool is_same(int e1, int e2)
    {
        return find(e1) == find(e2);
    }
};

并查集的优化

  • 按秩合并:实际上就是在合并两棵树时,将高度较小的树合并到高度较大的树上。这里我们使用“秩”(rank)代替高度,秩表示高度的上界,通常情况我们令只有一个节点的树的秩为0,严格来说,rank + 1才是高度的上界;两棵秩分别为r1、r2的树合并,如果秩不相等,我们将秩小的树合并到秩大的树上,这样就能保证新树秩不大于原来的任意一棵树。如果r1与r2相等,两棵树任意合并,并令新树的秩为r1 + 1。
  • 路径压缩:在执行Find的过程中,将路径上的所有节点都直接连接到根节点上。
#include <vector>
class DisjSet
{
  private:
    std::vector<int> parent;
    std::vector<int> rank; // 秩

  public:
    DisjSet(int max_size) : parent(std::vector<int>(max_size)),
                            rank(std::vector<int>(max_size, 0))
    {
        for (int i = 0; i < max_size; ++i)
            parent[i] = i;
    }
    int find(int x)
    {
    	//这里一直在不断更新父节点的值,将一条路径上所有的数的父节点都更新为根节点
        return x == parent[x] ? x : (parent[x] = find(parent[x]));
    }
    // 非递归版本
	int find(int x)
	{
	    int p = x, t;
	    while (uset[p] != p) p = uset[p];
	    while (x != p) { t = uset[x]; uset[x] = p; x = t; }
	    return x;
	}
    void to_union(int x1, int x2)
    {
        int f1 = find(x1);
        int f2 = find(x2);
        if (rank[f1] > rank[f2])
            parent[f2] = f1;
        else
        {
            parent[f1] = f2;
            if (rank[f1] == rank[f2])
                ++rank[f2];
        }
    }
    bool is_same(int e1, int e2)
    {
        return find(e1) == find(e2);
    }
};

两次遍历法计算连通域

#include <iostream>
using namespace std;

class DisjSet
{
private:
    int* parent;
    int* rank;

public:
    DisjSet(int maxsize) : parent(new int[maxsize]), rank(new int[maxsize])
    {
        for (int i = 0; i < maxsize; ++i)
        {
            parent[i] = i;
            rank[i] = 0;
        }
    }
    ~DisjSet() 
    {
        delete[] parent;
        delete[] rank;
    }

    int find(int x)
    {
        return x == parent[x] ? x : (parent[x] = find(parent[x]));
    }

    void to_union(int x1, int x2)
    {
        int f1 = find(x1);
        int f2 = find(x2);
        if (rank[f1] > rank[f2])
            parent[f2] = f1;
        else
        {
            parent[f1] = f2;
            if (rank[f1] == rank[f2])
                rank[f2] += 1;
        }
    }
    bool is_same(int e1, int e2)
    {
        return find(e1) == find(e2);
    }
};

void TwoPassLabel(int* imgsrc,int* imglabel,int w, int h)
{
    int label = 0;
    DisjSet equivalences(w * h);

    int left = -1;
    int up = -w;
    int loc = 0;
    int cur = 0;
    //第一次遍历
    for (int j = 0; j < h; j++)
    {
        for (int i = 0; i < w; i++)
        {
            loc = j * w + i;
            cur = imgsrc[loc];
            if (cur == 0)
            {
                continue;
            }
            if (i > 0 && imgsrc[loc + left])
            {
                imglabel[loc] = imglabel[loc + left];
                if (j > 0 && imgsrc[loc + up + left] == 0 && imgsrc[loc + up])
                {
                    equivalences.to_union(imglabel[loc], imglabel[loc + up]);
                }
            }
            else if (j > 0 && imgsrc[loc + up])
            {
                imglabel[loc] = imglabel[loc + up];
            }
            else
            {
                label++;
                imglabel[loc] = label;
            }   
        }
    }
    //对标签排序:因为可能出现1234 3的父节点为1,更新标签后为124
    int next_label = 1;
    int *renumber=new int[label+1]();
    for (int i = 1; i <= label; i++)
    {
        int temp = equivalences.find(i);
        if (renumber[temp]==0)
        {
			renumber[temp] = next_label;
            renumber[i] = next_label;
            next_label++;
        }
        else
        {
            renumber[i] = renumber[temp];
        }
    }

    //第二次遍历
    for (int j = 0; j < h; j++)
    {
        for (int i = 0; i < w; i++)
        {
            imglabel[j * w + i] = renumber[imglabel[j * w + i]];
        }
    }
    delete[]renumber;
}

int main()
{
    int img[30] = {
                    0, 0, 1, 0 ,1 ,
                    0, 1, 0, 1, 1 ,
                    1, 1, 1, 0, 1, 
                    0, 0, 0, 1 ,0 ,
                    0, 1, 0, 1, 1 ,
                    1, 1, 0, 0, 1, };
    int imglabel[30] = {0};
    TwoPassLabel(img, imglabel, 5, 6);
    for (int i = 0; i < 30; i++)
    {
        cout << imglabel[i] << endl;
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值