功力不夠只能那別人的代碼研究,不知怎么的我怎么會翻到這個東東的.
首先把代碼貼出來把,分析的時候肯定是支離破碎的.
// 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()'可以作為參數傳給它的.
并查集的基本結構可以是這樣的.
(截圖來自<數據結構與算法分析-C語言版>)
每個元素有一個指向父親指針,如果2個元素相同就可以把其中一個元素的父親指向另一個.如下
當然由于元素的結構簡單,可以直接使用數組代替,數組的位置為元素的value,數組的值為它爹的地址.如
{0,1,2,3,3,4} ?可以認為0,1,2,3是單元素,第4個它爹是3,第5個元素它爹是4 可以看成{0,1,2,3<-4<-5}.所以上面的圖可以寫成
當然圖中指向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.
after.
部分代碼如下.
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:
after:
部分代碼如下:
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/ .
?
?
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
