并查集 ---------------- OpenCV代码阅读

系统 2122 0

功力不够只能那别人的代码研究,不知怎么的我怎么会翻到这个东东的.

首先把代码贴出来把,分析的时候肯定是支离破碎的.

      
        //
      
      
         This function splits the input sequence or set into one or more equivalence classes and


      
      
        //
      
      
         returns the vector of labels - 0-based class indexes for each element.


      
      
        //
      
      
         predicate(a,b) returns true if the two sequence elements certainly belong to the same class.


      
      
        //
      
      
        //
      
      
         The algorithm is described in "Introduction to Algorithms"


      
      
        //
      
      
         by Cormen, Leiserson and Rivest, the chapter "Data structures for disjoint sets"
      
      

template<typename _Tp, 
      
        class
      
       _EqPredicate> 
      
        int
      
      
        

partition( 
      
      
        const
      
       vector<_Tp>& _vec, vector<
      
        int
      
      >&
      
         labels,

           _EqPredicate predicate
      
      =
      
        _EqPredicate())

{

    
      
      
        int
      
       i, j, N = (
      
        int
      
      
        )_vec.size();

    
      
      
        const
      
       _Tp* vec = &_vec[
      
        0
      
      
        ];



    
      
      
        const
      
      
        int
      
       PARENT=
      
        0
      
      
        ;

    
      
      
        const
      
      
        int
      
       RANK=
      
        1
      
      
        ;



    vector
      
      <
      
        int
      
      > _nodes(N*
      
        2
      
      
        );

    
      
      
        int
      
       (*nodes)[
      
        2
      
      ] = (
      
        int
      
      (*)[
      
        2
      
      ])&_nodes[
      
        0
      
      
        ];



    
      
      
        //
      
      
         The first O(N) pass: create N single-vertex trees
      
      
        for
      
      (i = 
      
        0
      
      ; i < N; i++
      
        )

    {

        nodes[i][PARENT]
      
      =-
      
        1
      
      
        ;

        nodes[i][RANK] 
      
      = 
      
        0
      
      
        ;

    }



    
      
      
        //
      
      
         The main O(N^2) pass: merge connected components
      
      
        for
      
      ( i = 
      
        0
      
      ; i < N; i++
      
         )

    {

        
      
      
        int
      
       root =
      
         i;



        
      
      
        //
      
      
         find root
      
      
        while
      
      ( nodes[root][PARENT] >= 
      
        0
      
      
         )

            root 
      
      =
      
         nodes[root][PARENT];



        
      
      
        for
      
      ( j = 
      
        0
      
      ; j < N; j++
      
         )

        {

            
      
      
        if
      
      ( i == j || !
      
        predicate(vec[i], vec[j]))

                
      
      
        continue
      
      
        ;

            
      
      
        int
      
       root2 =
      
         j;



            
      
      
        while
      
      ( nodes[root2][PARENT] >= 
      
        0
      
      
         )

                root2 
      
      =
      
         nodes[root2][PARENT];



            
      
      
        if
      
      ( root2 !=
      
         root )

            {

                
      
      
        //
      
      
         unite both trees
      
      
        int
      
       rank = nodes[root][RANK], rank2 =
      
         nodes[root2][RANK];

                
      
      
        if
      
      ( rank >
      
         rank2 )

                    nodes[root2][PARENT] 
      
      =
      
         root;

                
      
      
        else
      
      
        

                {

                    nodes[root][PARENT] 
      
      =
      
         root2;

                    nodes[root2][RANK] 
      
      += rank ==
      
         rank2;

                    root 
      
      =
      
         root2;

                }

                assert( nodes[root][PARENT] 
      
      < 
      
        0
      
      
         );



                
      
      
        int
      
       k =
      
         j, parent;



                
      
      
        //
      
      
         compress the path from node2 to root
      
      
        while
      
      ( (parent = nodes[k][PARENT]) >= 
      
        0
      
      
         )

                {

                    nodes[k][PARENT] 
      
      =
      
         root;

                    k 
      
      =
      
         parent;

                }



                
      
      
        //
      
      
         compress the path from node to root
      
      

                k =
      
         i;

                
      
      
        while
      
      ( (parent = nodes[k][PARENT]) >= 
      
        0
      
      
         )

                {

                    nodes[k][PARENT] 
      
      =
      
         root;

                    k 
      
      =
      
         parent;

                }

            }

        }

    }



    
      
      
        //
      
      
         Final O(N) pass: enumerate classes
      
      
            labels.resize(N);

    
      
      
        int
      
       nclasses = 
      
        0
      
      
        ;



    
      
      
        for
      
      ( i = 
      
        0
      
      ; i < N; i++
      
         )

    {

        
      
      
        int
      
       root =
      
         i;

        
      
      
        while
      
      ( nodes[root][PARENT] >= 
      
        0
      
      
         )

            root 
      
      =
      
         nodes[root][PARENT];

        
      
      
        //
      
      
         re-use the rank as the class label
      
      
        if
      
      ( nodes[root][RANK] >= 
      
        0
      
      
         )

            nodes[root][RANK] 
      
      = ~nclasses++
      
        ;

        labels[i] 
      
      = ~
      
        nodes[root][RANK];

    }



    
      
      
        return
      
      
         nclasses;

}
      
    

首先说下并查集,主要功能是就是将相似的元素分为一类,有点像聚类,但是聚类没有具体的相似测试.如果我们有{1,1,3,3,4,4,4,4,4,5,5,6,6},直接可以看出来这个可以分成{{1,1},{3,3},{4,4,4,4,4},{5,5},{6,6}}.当然这个前提是两个元素相似等于相等.普通的可以这样做,先排序再相连的两个元素比较,如相等,再往后移动,具体可以看 http://www.haskell.org/haskellwiki/99_questions/1_to_10 第9题.

这个是在一维的情况下,但是如果到了二维呢,(x1,y1)是不可以排序的的,就像实数点是可以排序,但是复数却不可以一样.这个时候可以定义`相似`,可以这样仍为如果(x1,y1),(x2,y2)的欧式距离小于某个值就认为相似.这个时候再分类就可以了.

相似是两个元素的比较操作,在代码中体现为 '_EqPredicate predicate=_EqPredicate()'可以作为参数传给它的.

并查集的基本结构可以是这样的.

并查集 ---------------- OpenCV代码阅读

(截图来自<数据结构与算法分析-C语言版>)

每个元素有一个指向父亲指针,如果2个元素相同就可以把其中一个元素的父亲指向另一个.如下

并查集 ---------------- OpenCV代码阅读

并查集 ---------------- OpenCV代码阅读

当然由于元素的结构简单,可以直接使用数组代替,数组的位置为元素的value,数组的值为它爹的地址.如

{0,1,2,3,3,4}  可以认为0,1,2,3是单元素,第4个它爹是3,第5个元素它爹是4 可以看成{0,1,2,3<-4<-5}.所以上面的图可以写成

并查集 ---------------- OpenCV代码阅读

当然图中指向0认为是单个元素.

在OpenCV代码中表现为

    
      for
    
    (i = 
    
      0
    
    ; i < N; i++
    
      )

    {

        nodes[i][PARENT]
    
    =-
    
      1
    
    
      ;

        nodes[i][RANK] 
    
    = 
    
      0
    
    
      ;

    }
    
    
OpenCV中使用的指向-1.其中的RANK等会儿再说.
如何合并两个值呢,简单的情况是两个元素都没有爹,但是如果这两个元素都有爹呢,如果爹一样,pass,已经在同一个类别里了,如果不一样呢,还要继续判断.其实这里可以联想到<编程之美>上的一道题目' 3.6 编程判断两个链表是否相交 ',其实解法很简单,先分别找爹的爹的爹....的爹..如果它们的老祖宗相等就可以认为是相等的,pass.如果不等,对它们的祖宗合并.
找祖宗的代码如下..
    
      //
    
    
       find root
    
    
      while
    
    ( nodes[root][PARENT] >= 
    
      0
    
    
       )

            root 
    
    =
    
       nodes[root][PARENT];
      
    
      while
    
    ( nodes[root2][PARENT] >= 
    
      0
    
    
       )

                root2 
    
    =
    
       nodes[root2][PARENT];
    
    
合并祖宗可以简单的如合并单个元素一样的,单个的点指向另一个点,但是这种情况下,最坏的情况如下,{1<-2<-3<-4<-5...<-n-1<n}.如果比较n-1,和n元素.这时的情况就是.需要遍历2n-1次元素,如果在合并是有一种情况.
1
/ | \
2 3 ..n 这种情况就是特好的了,反正都是同一类,谁当爹都一样(这话有问题的),这种情况下只需要2次就可以找到了.
当然这个术语叫 路径压缩 ,代码如下.
    
      //
    
    
       compress the path from node2 to root
    
    
      while
    
    ( (parent = nodes[k][PARENT]) >= 
    
      0
    
    
       )

                {

                    nodes[k][PARENT] 
    
    =
    
       root;

                    k 
    
    =
    
       parent;

                }



                
    
    
      //
    
    
       compress the path from node to root
    
    

                k =
    
       i;

                
    
    
      while
    
    ( (parent = nodes[k][PARENT]) >= 
    
      0
    
    
       )

                {

                    nodes[k][PARENT] 
    
    =
    
       root;

                    k 
    
    =
    
       parent;

                }
    
    
优化查找的另一种方法就是合并的时候,不直接简单的将一个元素认为是爹,当爹的条件首选是资历老{Rank}必须要大,最后的效果就是树尽量平衡.避免单链表的情况,当然术语叫 Rank合并 代码如下:
    
      //
    
    
       unite both trees
    
    
      int
    
     rank = nodes[root][RANK], rank2 =
    
       nodes[root2][RANK];

                
    
    
      if
    
    ( rank >
    
       rank2 )

                    nodes[root2][PARENT] 
    
    =
    
       root;

                
    
    
      else
    
    
      

                {

                    nodes[root][PARENT] 
    
    =
    
       root2;

                    nodes[root2][RANK] 
    
    += rank ==
    
       rank2;

                    root 
    
    =
    
       root2;

                }
    
    
最后就是标出每个元素所在的类别了.从第一个元素开始,直接访问它的祖宗,设置类别.此时OpenCV作者有一次体现了功力深厚的时候,直接在RANK上填写类别,反正树已经建好了,RANK没用了,RANK上的值都是非负的,就用~来区分,设置元素时在用~改回去,使用~而不用-的原因估计是位操作比较快吧.

最后说下应用.
1.聚集顶点. 相似条件欧式距离<10.
before.

并查集 ---------------- OpenCV代码阅读

    after.
  

并查集 ---------------- OpenCV代码阅读

部分代码如下.

      
         1
      
      
        struct
      
      
         PointLike{


      
      
         2
      
               PointLike(
      
        int
      
      
         thresh){


      
      
         3
      
      
        this
      
      ->thresh =
      
         thresh;


      
      
         4
      
      
                }


      
      
         5
      
      
        bool
      
      
        operator
      
      
        ()(cv::Point p1,cv::Point p2){


      
      
         6
      
      
        int
      
       x = p1.x -
      
         p2.x;


      
      
         7
      
      
        int
      
       y = p1.y -
      
         p2.y;


      
      
         8
      
      
        return
      
       x*x+y*y <=thresh*
      
        thresh;


      
      
         9
      
      
                }


      
      
        10
      
      
        int
      
      
         thresh;


      
      
        11
      
      
        };


      
      
        12
      
      
        13
      
        PointLike plike(
      
        20
      
      
        );


      
      
        14
      
               std::vector<
      
        int
      
      >
      
         labels;


      
      
        15
      
      
        int
      
      
         count;


      
      
        16
      
               count =
      
         cv::partition(pts_v,labels,plike);


      
      
        17
      
      
        18
      
      
        for
      
      (size_t i = 
      
        0
      
      ;i<pts_v.size();i++
      
        )


      
      
        19
      
      
                {


      
      
        20
      
                       cv::circle(after,pts_v[i],
      
        2
      
      
        ,colorTab[labels[i]]);


      
      
        21
      
               }
    

2.合并重叠的矩形.(人脸识别里用的)

相似条件(重叠面积占小矩形面基的75%)

before:

并查集 ---------------- OpenCV代码阅读

after:

并查集 ---------------- OpenCV代码阅读

部分代码如下:

      
         1
      
      
        struct
      
      
         RectLike{


      
      
         2
      
               RectLike(
      
        double
      
      
         p){


      
      
         3
      
      
        this
      
      ->p =
      
         p;


      
      
         4
      
      
                }


      
      
         5
      
      
        bool
      
      
        operator
      
      
        ()(cv::Rect r1,cv::Rect r2){


      
      
         6
      
      
        int
      
       area1 =
      
         r1.area();


      
      
         7
      
      
        int
      
       area2 =
      
         r2.area();


      
      
         8
      
      
        //
      
      
        相交Rect面积
      
      
         9
      
      
        int
      
       area =
      
         overlap_rect_area(r1,r2);


      
      
        10
      
      
        return
      
       area1<area2?area>=p*area1:area>=p*
      
        area2;                                


      
      
        11
      
      
                }


      
      
        12
      
      
        double
      
      
         p;


      
      
        13
      
      
        };


      
      
        14
      
      
        15
      
       RectLike rLike(
      
        0.75
      
      
        );


      
      
        16
      
       std::vector<
      
        int
      
      >
      
         labels;


      
      
        17
      
      
        int
      
      
         count;


      
      
        18
      
       count =
      
         cv::partition(rects_v,labels,rLike);


      
      
        19
      
      
        20
      
      
        for
      
      (size_t i = 
      
        0
      
      ;i<rects_v.size();i++
      
        )


      
      
        21
      
      
                {


      
      
        22
      
      
                        cv::rectangle(after,rects_v[i],colorTab[labels[i]]);


      
      
        23
      
               }
    

好了总算写结束了.

其实还有很多的应用的.

推荐 http://mindlee.net/2011/10/21/disjoint-sets/ .

    

  

 

 

并查集 ---------------- OpenCV代码阅读


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论