Showing posts with label Union-Find - Weight. Show all posts
Showing posts with label Union-Find - Weight. Show all posts

POJ 1182 - 食物链


带权并查集

动物王国中有三类动物A,B,C,构成环形:A吃B,B吃C,C吃A。
现有N个动物,从1开始编号,每个动物都是A,B,C中的一种,但我们并不知道是哪一种。
用两种说法描述这N个动物的食物链关系:
  • 1 X Y 表示XY是同类
  • 2 X Y 表示XY
给出K句话,有些是真的,有些是假的,满足下列任一条件即为假话,否则是真话:
  • 当前的话与之前的某些真话冲突
  • 当前的话中XYN
  • 当前的话表示XX
输出假话的总数。

分析

这个题目是有关系的集合问题,可以利用带权并查集解决。
定义两个数组farankfa用来判断集合关系,rank用来描述其与根节点的关系。因为关系满足传递性,所以可以推导出给出条件下的当前关系,在判断与之前已有关系是否矛盾。
本题的解法巧妙地利用了模运算,rank数组用0表示同类,1表示当前点能吃根,2表示当前点被根吃。
传递性推导
结点A与根关系结点B与根关系A与B关系
000
012
021
101
110
122
202
211
220
首先用rank标记当前点与根的关系,然后利用传递性,得到任意两点的关系(rank[a] - rank[b]) % 3
关系1
结点A与根关系结点B与A关系B与根关系
000
011
022
101
112
120
202
210
221
并且利用路径压缩维护这种关系,rank[b] = (rank[b] + rank[a]) % 3,通过当前点到之前根的关系,加上之前根到当前根的关系,维护当前点到根的关系。
关系 2
每次添加新关系时,导致两个集合的连接,可以通过当前点的关系,反推出两个根的关系:rank[rb] = rank[a] - rank[b] + relation(b->a)
本题需要注意的是传入的relation恰为描述的种类号减一。
关系 3

代码

#include <iostream>
#include <cstring>
#include <cctype>
#include <cstdio>
#include <string>
#include <cmath>
#include <algorithm>
#define LL long long
using namespace std;

const int MAXN = 100005;
const int INF = 0x3F3F3F3F;
const double eps = 1e-6;
const int MOD = 1e9 + 7;

int fa[MAXN], _rank[MAXN];
int n, k, ans;

void init(int n)
{
    for(int i = 0; i <= n; i++)
    {
        fa[i] = i;
        _rank[i] = 0;
    }

    ans = 0;
}

int find(int x)
{
    if(fa[x] == x)
        return x;
    else
    {
        int temp = fa[x];
        fa[x] = find(fa[x]);
        _rank[x] = (_rank[x] + _rank[temp]) % 3;
        return fa[x];
    }
}

void merge(int r, int u, int v)
{
    int fu = find(u);
    int fv = find(v);

    if(fu != fv)
    {
        fa[fu] = fv;
        _rank[fu] = (_rank[v] - _rank[u] + r + 3) % 3;
    }
}

bool check(int r, int u, int v)
{
    if(u > n || v > n)
        return false;
    if(r == 1 && u == v) // 传入的序号已减一
        return false;

    int fu = find(u);
    int fv = find(v);

    if(fu == fv)
        return ((_rank[u] - _rank[v]) % 3 + 3 ) % 3 == r;
    else return true;    
}

int main()
{
    scanf("%d %d", &n, &k);

    init(n);

    int a, b, c;

    for(int i = 0; i < k; i++)
    {
        scanf("%d %d %d", &a, &b, &c);
        a --;
        if(check(a, b, c))
            merge(a, b, c);
        else
            ans ++;
    }

    printf("%d\n", ans);

    return 0;
}

hiho#1515 - 分数调查(带权并查集的经典应用)


http://www.voidcn.com/article/p-hlsdlkgy-bbz.html
小Hi的学校总共有N名学生,编号1-N。学校刚刚进行了一场全校的古诗文水平测验。
学校没有公布测验的成绩,所以小Hi只能得到一些小道消息,例如X号同学的分数比Y号同学的分数高S分。
小Hi想知道利用这些消息,能不能判断出某两位同学之间的分数高低?
输入
第一行包含三个整数N, M和Q。N表示学生总数,M表示小Hi知道消息的总数,Q表示小Hi想询问的数量。
以下M行每行三个整数,X, Y和S。表示X号同学的分数比Y号同学的分数高S分。
以下Q行每行两个整数,X和Y。表示小Hi想知道X号同学的分数比Y号同学的分数高几分。
对于50%的数据,1 <= N, M, Q <= 1000
对于100%的数据,1 <= N, M, Q<= 100000 1 <= X, Y <= N -1000 <= S <= 1000
数据保证没有矛盾。
输出
对于每个询问,如果不能判断出X比Y高几分输出-1。否则输出X比Y高的分数。
样例输入
10 5 3  
1 2 10  
2 3 10  
4 5 -10  
5 6 -10  
2 5 10  
1 10  
1 5  
3 5
样例输出
-1  
20  
0
很简单的带权并查集,
注意之所以可以在递归回溯过程中采用dis+=,是因为并查
集的路径压缩,不会导致某个点到根结点的权值重复叠加。union的过程很是巧妙。

  学生a比学生b的分数高,可视为 b 是 a的父亲,a的权值为 v , 同理,假如c比b分数高,则b是c的父亲。 如果此时d比c的分数高,但是d不在并查集内,就把d加入并查集,因为d分数比c高,则箭头应该d->c,但是c的父亲是b,则d->b,即d的父亲是b。
  如果两位同学不在同一并查集中,就不能判断。否则能判断,输出 a的权值和b的权值之差。

const int INF = 100000 + 4;
typedef long long LL;
LL dis[INF];
int n, m, q;
LL fa[INF];

void init() {
    for(int i = 1; i <= n; i++) {
        dis[i] = 0;
        fa[i] = i;
    }
}

LL Find(LL x) {
    if(fa[x] == x) {
        return x;
    }
    int t = fa[x];
    fa[x] = Find(fa[x]);
    dis[x] += dis[t];
    return fa[x];
}

void Union(LL x, LL y, LL tx, LL ty, LL d) {
    fa[ty] = tx;
    dis[ty] = dis[x] + d - dis[y];
}

int main() {
    while(~scanf("%d%d%d", &n, &m, &q)) {
            init();

        for(int i = 1, x, y, z; i <= m; i++) {
            scanf("%d%d%d", &x, &y, &z);
            LL tx = Find(x);
            LL ty = Find(y);
            if(tx != ty) {
                Union(x, y, tx, ty, z);
            }
        }

        for(int i = 0, x, y; i < q; i++) {
            scanf("%d%d", &x, &y);
            if(Find(x) == Find(y)) {
                printf("%d\n", dis[y] - dis[x]);
            } else {
                printf("-1\n");
            }

        }

    }
    return 0;
}
https://www.cnblogs.com/mjtcn/p/9755169.html
 2 如果把每个人抽象成一个点,之间的关系抽象成边。
 3 那么如果询问的两个人之间存在关系,说明,他们在图上上是联通的。
 4 所以并查集维护一下连通性。
 5 对于分数之间的关系,用到带权并查集。
 6 每个点里存一个val表示当前点的分数-根节点的分数。
 7 查询:
 8 xy不连通,输出-1,
 9 否则val[x]=fen[x]-fen[u],val[y]=fen[y]-fen[u],输出fen[x]-fen[y]=val[x]-val[y]
10 合并:
11 x的根节点为u,y的根节点为v,fa[u]=v;
12 更新后的val[u]=fen[u]-fen[v],
13 val[x]=fen[x]-fen[u]  ->  fen[u]=fen[x]-val[x]
14 val[y]=fen[y]-fen[v]  ->  fen[v]=fen[y]-val[y]
15 fen[u]-fen[v] = fen[x] - fen[y] + val[y] - val[x] = S + val[y] - val[x]
16 */
int par[maxn],N,M; int sum[maxn];//sum[i]表示i到i的父结点的距离,即i的父结点比i高的分数 void init() { for(int i=0;i<=N;i++) par[i]=i; memset(sum,0,sizeof(sum)); } int find(int x) { if(par[x]!=x) { int tmp=par[x]; par[x]=find(par[x]); sum[x]+=sum[tmp]; } return par[x]; } void unit(int x,int y,int v) { int fx=find(x),fy=find(y); if(fx==fy)//在一棵树上 { return ; } else { par[fx]=fy; sum[fx]=sum[y]-sum[x]-v; } return ; } bool same(int x,int y) { return find(x)==find(y); } int main() { int q; while(~scanf("%d%d%d",&N,&M,&q)) { init(); while(M--) { int x,y,v; scanf("%d%d%d",&x,&y,&v); unit(x,y,v); } while(q--) { int x,y; scanf("%d%d",&x,&y); printf("%d\n",same(x,y)?sum[y]-sum[x]:-1); } } return 0; }


同样使用带权并查集,用数组p记录同学们的权值(与根的分数差),那么路径压缩时,p[a] = p[a] + p],合并时,p[rb] = p[a] + S - p[b]

Union-Find Data Structure Summary


https://zhuanlan.zhihu.com/p/34929966
然后与传统的不同,带权并查集有一个新的数组叫做 v[i] ,他表示的意义是:从当前节点到根节点有向距离(或者在这题称为盈亏利润,这里定义A到B的有向距离为dis时,B到A的有向距离为-dis),而对于两个点对 (x,y) 的距离,如果他们不在一个集合里则肯定没有距离(显然),而他们在集合里的有向距离则为 v[y]-v[x] (相当于是y到根节点的距离根节点到x点的距离)。

X. 带权并查集
并查集的本质是一个森林,每棵树代表一个集合,树根为集合的代表元。支持两种操作:
  • 查询一个元素所处的集合
  • 合并两个集合
查询一个元素所处的集合,只需要不断寻找父节点,直到找到代表元。
合并两个集合时,先找到两个集合的代表元xy,然后令fa[x]=y即可。
优化
  • 路径压缩: 沿着树根的路径找到元素a所在集合的代表元b后,对这条路径上的所有元素执行fa[x]=b
  • rank启发式合并:为了避免退化,对于每个集合维护一个rank值,每次将较小的合并到较大的,相同时则rank=rank+1
void init(int n)
{
    for(int i = 0; i < n; i++)
    fa[i] = i;
}

int find(int v)
{
    return fa[v] = fa[v] == v ? v : find(fa[v]);
}

void merge(int x, int y)
{
    int a = find(x), b = find(y);
    if(rank[a] < rank[b])
        fa[a] = b;
    else {
        fa[b] = a;
        if(rank[a] == rank[b])
            rank[a]++;
    }
}

带权并查集

带权并查集即是结点存有权值信息的并查集。
当两个元素之间的关系可以量化,并且关系可以合并时,可以使用带权并查集来维护元素之间的关系。
带权并查集每个元素的权通常描述其与并查集中祖先的关系,这种关系如何合并,路径压缩时就如何压缩。
带权并查集可以推算集合内点的关系,而一般并查集只能判断属于某个集合。
Delete(a) – remove a from its containing set
Union links the root of the shallower tree to the root of the taller tree (by rank) O(1)

To increase the amortized efficiency of the find operation we perform path compression
Link all the nodes in the path directly to the root
- Delete operation in constant time
Its value grows rapidly, even for small inputs. For example, A(4, 2) is an integer of 19,729 decimal digits
Using both path compressionsplitting, or halving and union by rank or size ensures that the amortized time per operation is only ,[4][5] which is optimal,[6] where  is the inverse Ackermann function. This function has a value  for any value of n that can be written in this physical universe, so the disjoint-set operations take place in essentially constant time.

Since the function  f(n) = A(nn) considered above grows very rapidly, its inverse functionf−1, grows very slowly. This inverse Ackermann function f−1 is usually denoted by α. In fact, α(n) is less than 5 for any practical input size n, since A(4, 4) is on the order of .
This inverse appears in the time complexity of some algorithms, such as the disjoint-set data structure and Chazelle's algorithm for minimum spanning trees. Sometimes Ackermann's original function or other variations are used in these settings, but they all grow at similarly high rates. In particular, some modified functions simplify the expression by eliminating the −3 and similar terms.
I saw time complexity of union and find function depends on some conditions.
  1. without anything: O(N)
  2. with Union by Rank: O(logN)
  3. with Path Compression: O(α(N)) which is inverse Ackermann function.
The above operations can be optimized to O(Log n) in worst case. The idea is to always attach smaller depth tree under the root of the deeper tree. This technique is called union by rank. The term rank is preferred instead of height because if path compression technique (we have discussed it below) is used, then rank is not always equal to height. Also, size (in place of height) of trees can also be used as rank. Using size as rank also yields worst case time complexity as O(Logn) 

The second optimization to naive method is Path Compression. The idea is to flatten the tree when find() is called. When find() is called for an element x, root of the tree is returned. The find() operation traverses up from x to find root. The idea of path compression is to make the found root as parent of x so that we don’t have to traverse all intermediate nodes again. If x is root of a subtree, then path (to root) from all nodes under x also compresses.
// class to represent Subset
class subset
{
    int parent;
    int rank;
}
  
// A utility function to find 
// set of an element i (uses 
// path compression technique)
int find(subset [] subsets , int i)
{
if (subsets[i].parent != i)
    subsets[i].parent = find(subsets, 
                             subsets[i].parent);
    return subsets[i].parent;
}
  
// A function that does union
// of two sets of x and y
// (uses union by rank)
void Union(subset [] subsets, 
           int x , int y )
{
    int xroot = find(subsets, x);
        int yroot = find(subsets, y);
  
    if (subsets[xroot].rank < subsets[yroot].rank)
        subsets[xroot].parent = yroot;
    else if (subsets[yroot].rank < subsets[xroot].rank)
        subsets[yroot].parent = xroot;
    else
    {
        subsets[xroot].parent = yroot;
        subsets[yroot].rank++;
    }
}

disjoint-set data structure is a data structure that keeps track of a set of elements partitioned into a number of disjoint (non-overlapping) subsets. A union-find algorithm is an algorithm that performs two useful operations on such a data structure:
Find: Determine which subset a particular element is in. This can be used for determining if two elements are in the same subset.
Union: Join two subsets into a single subset.

It supports two useful operations:
  • Find: Determine which subset a particular element is in. Find typically returns an item from this set that serves as its "representative"; by comparing the result of two Find operations, one can determine whether two elements are in the same subset.
  • Union: Join two subsets into a single subset.
The other important operation, MakeSet, which makes a set containing only a given element (asingleton), is generally trivial.

Union-Find Algorithm can be used to check whether an undirected graph contains cycle or not. This method assumes that graph doesn’t contain any self-loops.
We can keeps track of the subsets in a 1D array, lets call it parent[].
For each edge, make subsets using both the vertices of the edge. If both the vertices are in the same subset, a cycle is found.
class Graph
{
    int V, E;    // V-> no. of vertices & E->no.of edges
    Edge edge[]; // /collection of all edges
    class Edge
    {
        int src, dest;
    };
    // Creates a graph with V vertices and E edges
    Graph(int v,int e)
    {
        V = v;
        E = e;
        edge = new Edge[E];
        for (int i=0; i<e; ++i)
            edge[i] = new Edge();
    }
    // A utility function to find the subset of an element i
    int find(int parent[], int i)
    {
        if (parent[i] == -1)
            return i;
        return find(parent, parent[i]);
    }
    // A utility function to do union of two subsets
    void Union(int parent[], int x, int y)
    {
        int xset = find(parent, x);
        int yset = find(parent, y);
        parent[xset] = yset;
    }
    // The main function to check whether a given graph
    // contains cycle or not
    int isCycle( Graph graph)
    {
        // Allocate memory for creating V subsets
        int parent[] = new int[graph.V];
        // Initialize all subsets as single element sets
        for (int i=0; i<graph.V; ++i)
            parent[i]=-1;
        // Iterate through all edges of graph, find subset of both
        // vertices of every edge, if both subsets are same, then
        // there is cycle in graph.
        for (int i = 0; i < graph.E; ++i)
        {
            int x = graph.find(parent, graph.edge[i].src);
            int y = graph.find(parent, graph.edge[i].dest);
            if (x == y)
                return 1;
            graph.Union(parent, x, y);
        }
        return 0;
    }
}
http://www.geeksforgeeks.org/union-find-algorithm-set-2-union-by-rank/
Note that the implementation of union() and find() is naive and takes O(n) time in worst case. These methods can be improved to O(Logn) using Union by Rank or Height.

The idea is to always attach smaller depth tree under the root of the deeper tree. This technique is called union by rank. The term rank is preferred instead of height because if path compression technique (we have discussed it below) is used, then rank is not always equal to height. Also, size (in place of height) of trees can also be used as rank. Using size as rank also yields worst case time complexity as O(Logn)

Let us see the above example with union by rank
Initially all elements are single element subsets.
0 1 2 3 

Do Union(0, 1)
   1   2   3  
  /
 0

Do Union(1, 2)
   1    3
 /  \
0    2

Do Union(2, 3)
    1    
 /  |  \
0   2   3
The second optimization to naive method is Path Compression. The idea is to flatten the tree when find() is called. When find() is called for an element x, root of the tree is returned. The find() operation traverses up from x to find root. The idea of path compression is to make the found root as parent of x so that we don’t have to traverse all intermediate nodes again. If x is root of a subtree, then path (to root) from all nodes under x also compresses.
Let the subset {0, 1, .. 9} be represented as below and find() is called
for element 3.
              9
         /    |    \  
        4     5      6
     /     \        /  \
    0        3     7    8
            /  \
           1    2  

When find() is called for 3, we traverse up and find 9 as representative
of this subset. With path compression, we also make 3 as child of 9 so 
that when find() is called next time for 1, 2 or 3, the path to root is 
reduced.

               9
         /    /  \    \
        4    5    6     3 
     /           /  \   /  \
    0           7    8  1   2   

// a structure to represent an edge in graph
struct Edge
{
    int src, dest;
}
// a structure to represent a graph
struct Graph
{
    // V-> Number of vertices, E-> Number of edges
    int V, E;
    // graph is represented as an array of edges
    struct Edge* edge;
};
struct subset
{
    int parent;
    int rank;
};
// Creates a graph with V vertices and E edges
struct Graph* createGraph(int V, int E)
{
    struct Graph* graph = (struct Graph*) malloc( sizeof(struct Graph) );
    graph->V = V;
    graph->E = E;
    graph->edge = (struct Edge*) malloc( graph->E * sizeof( struct Edge ) );
    return graph;
}
// A utility function to find set of an element i
// (uses path compression technique)
int find(struct subset subsets[], int i)
{
    // find root and make root as parent of i (path compression)
    if (subsets[i].parent != i)
        subsets[i].parent = find(subsets, subsets[i].parent);
    return subsets[i].parent;
}
// A function that does union of two sets of x and y
// (uses union by rank)
void Union(struct subset subsets[], int x, int y)
{
    int xroot = find(subsets, x);
    int yroot = find(subsets, y);
    // Attach smaller rank tree under root of high rank tree
    // (Union by Rank)
    if (subsets[xroot].rank < subsets[yroot].rank)
        subsets[xroot].parent = yroot;
    else if (subsets[xroot].rank > subsets[yroot].rank)
        subsets[yroot].parent = xroot;
    // If ranks are same, then make one as root and increment
    // its rank by one
    else
    {
        subsets[yroot].parent = xroot;
        subsets[xroot].rank++;
    }
}
// The main function to check whether a given graph contains cycle or not
int isCycle( struct Graph* graph )
{
    int V = graph->V;
    int E = graph->E;
    // Allocate memory for creating V sets
    struct subset *subsets =
        (struct subset*) malloc( V * sizeof(struct subset) );
    for (int v = 0; v < V; ++v)
    {
        subsets[v].parent = v;
        subsets[v].rank = 0;
    }
    // Iterate through all edges of graph, find sets of both
    // vertices of every edge, if sets are same, then there is
    // cycle in graph.
    for(int e = 0; e < E; ++e)
    {
        int x = find(subsets, graph->edge[e].src);
        int y = find(subsets, graph->edge[e].dest);
        if (x == y)
            return 1;
        Union(subsets, x, y);
    }
    return 0;
}

http://blog.csdn.net/dm_vincent/article/details/7655764
在对问题进行建模的时候,我们应该尽量想清楚需要解决的问题是什么。因为模型中选择的数据结构和算法显然会根据问题的不同而不同,就动态连通性这个场景而言,我们需要解决的问题可能是:
  • 给出两个节点,判断它们是否连通,如果连通,不需要给出具体的路径
  • 给出两个节点,判断它们是否连通,如果连通,需要给出具体的路径

就上面两种问题而言,虽然只有是否能够给出具体路径的区别,但是这个区别导致了选择算法的不同,本文主要介绍的是第一种情况,即不需要给出具体路径的Union-Find算法,而第二种情况可以使用基于DFS的算法。
Quick-Find 算法:
上述代码的find方法十分高效,因为仅仅需要一次数组读取操作就能够找到该节点的组号,但是问题随之而来,对于需要添加新路径的情况,就涉及到对于组号的修改,因为并不能确定哪些节点的组号需要被修改,因此就必须对整个数组进行遍历,找到需要修改的节点,逐一修改,这一下每次添加新路径带来的复杂度就是线性关系了,如果要添加的新路径的数量是M,节点数量是N,那么最后的时间复杂度就是MN,显然是一个平方阶的复杂度,对于大规模的数据而言,平方阶的算法是存在问题的,这种情况下,每次添加新路径就是“牵一发而动全身”,想要解决这个问题,关键就是要提高union方法的效率,让它不再需要遍历整个数组。
  1.     private int[] id; // access to component id (site indexed)  
  2.     private int count; // number of components  
  3.     public UF(int N)  
  4.     {  
  5.         // Initialize component id array.  
  6.         count = N;  
  7.         id = new int[N];  
  8.         for (int i = 0; i < N; i++)  
  9.             id[i] = i;  
  10.     }  
  11.     public int count()  
  12.     { return count; }  
  13.     public boolean connected(int p, int q)  
  14.     { return find(p) == find(q); }  
  15.     public int find(int p)  
  16.     { return id[p]; }  
  17.     public void union(int p, int q)  
  18.     {   
  19.         // 获得p和q的组号  
  20.         int pID = find(p);  
  21.         int qID = find(q);  
  22.         // 如果两个组号相等,直接返回  
  23.         if (pID == qID) return;  
  24.         // 遍历一次,改变组号使他们属于一个组  
  25.         for (int i = 0; i < id.length; i++)  
  26.             if (id[i] == pID) id[i] = qID;  
  27.         count--;  
  28.     } 

如果不改变底层数据结构,即不改变使用数组的表示方法的话。可以采用parent-link的方式将节点组织起来,举例而言,id[p]的值就是p节点的父节点的序号,如果p是树根的话,id[p]的值就是p,因此最后经过若干次查找,一个节点总是能够找到它的根节点,即满足id[root] = root的节点也就是组的根节点了,然后就可以使用根节点的序号来表示组号。所以在处理一个pair的时候,将首先找到pair中每一个节点的组号(即它们所在树的根节点的序号),如果属于不同的组的话,就将其中一个根节点的父节点设置为另外一个根节点,相当于将一颗独立的树编程另一颗独立的树的子树。直观的过程如下图所示。但是这个时候又引入了问题。


  1. private int find(int p)  
  2. {   
  3.     // 寻找p节点所在组的根节点,根节点具有性质id[root] = root  
  4.     while (p != id[p]) p = id[p];  
  5.     return p;  
  6. }  
  7. public void union(int p, int q)  
  8. {   
  9.     // Give p and q the same root.  
  10.     int pRoot = find(p);  
  11.     int qRoot = find(q);  
  12.     if (pRoot == qRoot)   
  13.         return;  
  14.     id[pRoot] = qRoot;    // 将一颗树(即一个组)变成另外一课树(即一个组)的子树  
  15.     count--;  
  16. }  
树这种数据结构容易出现极端情况,因为在建树的过程中,树的最终形态严重依赖于输入数据本身的性质,比如数据是否排序,是否随机分布等等。比如在输入数据是有序的情况下,构造的BST会退化成一个链表

Quick-Union算法中的任何操作,都不可避免的需要调用find方法,而该方法的执行效率依赖于树的高度。树的高度减小了,find方法的效率就增加了,从而也就增加了整个Quick-Union算法的效率。

find方法的执行过程中,不是需要进行一个while循环找到根节点嘛?如果保存所有路过的中间节点到一个数组中,然后在while循环结束之后,将这些中间节点的父节点指向根节点,不就行了么?但是这个方法也有问题,因为find操作的频繁性,会造成频繁生成中间节点数组,相应的分配销毁的时间自然就上升了。那么有没有更好的方法呢?还是有的,即将节点的父节点指向该节点的爷爷节点,这一点很巧妙,十分方便且有效,相当于在寻找根节点的同时,对路径进行了压缩,使整个树结构扁平化。相应的实现如下,实际上只需要添加一行代码:

[java] view plain copy
 print?
  1. private int find(int p)  
  2. {  
  3.     while (p != id[p])  
  4.     {  
  5.         // 将p节点的父节点设置为它的爷爷节点  
  6.         id[p] = id[id[p]];  
  7.         p = id[p];  
  8.     }  
  9.     return p;  
  10. }  
http://www.geeksforgeeks.org/union-find-algorithm-set-2-union-by-rank/
struct subset
{
    int parent;
    int rank;
};
// A utility function to find set of an element i
// (uses path compression technique)
int find(struct subset subsets[], int i)
{
    // find root and make root as parent of i (path compression)
    if (subsets[i].parent != i)
        subsets[i].parent = find(subsets, subsets[i].parent);
    return subsets[i].parent;
}
// A function that does union of two sets of x and y
// (uses union by rank)
void Union(struct subset subsets[], int x, int y)
{
    int xroot = find(subsets, x);
    int yroot = find(subsets, y);
    // Attach smaller rank tree under root of high rank tree
    // (Union by Rank)
    if (subsets[xroot].rank < subsets[yroot].rank)
        subsets[xroot].parent = yroot;
    else if (subsets[xroot].rank > subsets[yroot].rank)
        subsets[yroot].parent = xroot;
    // If ranks are same, then make one as root and increment
    // its rank by one
    else
    {
        subsets[yroot].parent = xroot;
        subsets[xroot].rank++;
    }
}
// The main function to check whether a given graph contains cycle or not
int isCycle( struct Graph* graph )
{
    int V = graph->V;
    int E = graph->E;
    // Allocate memory for creating V sets
    struct subset *subsets =
        (struct subset*) malloc( V * sizeof(struct subset) );
    for (int v = 0; v < V; ++v)
    {
        subsets[v].parent = v;
        subsets[v].rank = 0;
    }
    // Iterate through all edges of graph, find sets of both
    // vertices of every edge, if sets are same, then there is
    // cycle in graph.
    for(int e = 0; e < E; ++e)
    {
        int x = find(subsets, graph->edge[e].src);
        int y = find(subsets, graph->edge[e].dest);
        if (x == y)
            return 1;
        Union(subsets, x, y);
    }
    return 0;
}

http://algs4.cs.princeton.edu/15uf/WeightedQuickUnionUF.java.html
http://algs4.cs.princeton.edu/15uf/UF.java.html
http://vancexu.github.io/2015/07/13/intro-to-union-find-data-structure.html

http://hehejun.blogspot.com/2015/02/data-structureunion-find.html
1. union
最简单的union操作就是从当前 p 和 q 一直找到 pRoot 和 qRoot,然后把 pRoot 的 parent设置成qRoot,这样时间复杂度是 O (n),
如果连接的时候我们一直把 size 较小的那个 tree 连向 size 较大的 tree,可以优化到 O(log n)
2. find
size balancing优化后的复杂度是 O(log n),我们可以做的优化的是,在我们从 p 到 root 的过程中,把路径上所有节点指向 root,
通过一个简单的递归可以实现,从底部到 root,拿到 root 后再一层一层 return 回来, 同时把路径上的节点指向 root。
经过 path compression 的优化后,时间复杂度,可以达到 O(lg *n),十分接近常数时间复杂度。
3. connected

判断 p 和 q 的 root是不是相等即可
public class UnionFind {

    private int[] id; //parent
    private int[] sz; //rank
 
    public UnionFind(int size) {
        id = new int[size];
        sz = new int[size];
        for(int i = 0; i < size; i++) {
            id[i] = i;
            sz[i] = 1;
        }
    }
 
    public void union(int p, int q) {
        int pRoot = find(p);
        int qRoot = find(q);
        //size balancing
        if (sz[pRoot] <= sz[qRoot]) {
            id[pRoot] = qRoot;
            sz[qRoot] += sz[pRoot];
        }
        else {
            id[qRoot] = pRoot;
            sz[pRoot] += sz[qRoot];
        }
    }
 
    public int find(int i) {
        if (id[i] == i)
            return i;
        //path compression
        int parent = find(id[i]);
        id[i] = parent;
        return parent;
    }
 
    public boolean connected(int p, int q) {
        return find(p) == find(q);
    }
 
    public int size() {
        return id.length;
    }
}
http://www.cnblogs.com/googlerla/p/4831239.html
几个需要思考的问题:
这种算法通常应用于什么场景?怎么用?
如何将问题归约到union-find或者说union的目的是什么?
怎么union,即两个元素满足什么条件时可以union?
>>两个元素有关联
题目
union 什么
union目的
Graph Valid Tree
一条边的两个顶点
若union两个顶点时发现根一样,说明已经在同一棵树中,说明输入graph存在环,非tree;union结束后,计数有多少个不同的根,当且仅当存在一个根时事vaild tree
Subtree Sum
节点同其父节点
将以指定id节点为根的所有节点union在一起,对这个union中的元素值求和
Number of Islands
两个相邻的1元素
union后计数union集合数量(通过计数union数组中根节点数量)
Surrounded Regions
所有从边界可达的O元素union在一起
union完成后那些没有在边界可达O集合中的O是需要翻转的



Labels

LeetCode (1432) GeeksforGeeks (1122) LeetCode - Review (1067) Review (882) Algorithm (668) to-do (609) Classic Algorithm (270) Google Interview (237) Classic Interview (222) Dynamic Programming (220) DP (186) Bit Algorithms (145) POJ (141) Math (137) Tree (132) LeetCode - Phone (129) EPI (122) Cracking Coding Interview (119) DFS (115) Difficult Algorithm (115) Lintcode (115) Different Solutions (110) Smart Algorithm (104) Binary Search (96) BFS (91) HackerRank (90) Binary Tree (86) Hard (79) Two Pointers (78) Stack (76) Company-Facebook (75) BST (72) Graph Algorithm (72) Time Complexity (69) Greedy Algorithm (68) Interval (63) Company - Google (62) Geometry Algorithm (61) Interview Corner (61) LeetCode - Extended (61) Union-Find (60) Trie (58) Advanced Data Structure (56) List (56) Priority Queue (53) Codility (52) ComProGuide (50) LeetCode Hard (50) Matrix (50) Bisection (48) Segment Tree (48) Sliding Window (48) USACO (46) Space Optimization (45) Company-Airbnb (41) Greedy (41) Mathematical Algorithm (41) Tree - Post-Order (41) ACM-ICPC (40) Algorithm Interview (40) Data Structure Design (40) Graph (40) Backtracking (39) Data Structure (39) Jobdu (39) Random (39) Codeforces (38) Knapsack (38) LeetCode - DP (38) Recursive Algorithm (38) String Algorithm (38) TopCoder (38) Sort (37) Introduction to Algorithms (36) Pre-Sort (36) Beauty of Programming (35) Must Known (34) Binary Search Tree (33) Follow Up (33) prismoskills (33) Palindrome (32) Permutation (31) Array (30) Google Code Jam (30) HDU (30) Array O(N) (29) Logic Thinking (29) Monotonic Stack (29) Puzzles (29) Code - Detail (27) Company-Zenefits (27) Microsoft 100 - July (27) Queue (27) Binary Indexed Trees (26) TreeMap (26) to-do-must (26) 1point3acres (25) GeeksQuiz (25) Merge Sort (25) Reverse Thinking (25) hihocoder (25) Company - LinkedIn (24) Hash (24) High Frequency (24) Summary (24) Divide and Conquer (23) Proof (23) Game Theory (22) Topological Sort (22) Lintcode - Review (21) Tree - Modification (21) Algorithm Game (20) CareerCup (20) Company - Twitter (20) DFS + Review (20) DP - Relation (20) Brain Teaser (19) DP - Tree (19) Left and Right Array (19) O(N) (19) Sweep Line (19) UVA (19) DP - Bit Masking (18) LeetCode - Thinking (18) KMP (17) LeetCode - TODO (17) Probabilities (17) Simulation (17) String Search (17) Codercareer (16) Company-Uber (16) Iterator (16) Number (16) O(1) Space (16) Shortest Path (16) itint5 (16) DFS+Cache (15) Dijkstra (15) Euclidean GCD (15) Heap (15) LeetCode - Hard (15) Majority (15) Number Theory (15) Rolling Hash (15) Tree Traversal (15) Brute Force (14) Bucket Sort (14) DP - Knapsack (14) DP - Probability (14) Difficult (14) Fast Power Algorithm (14) Pattern (14) Prefix Sum (14) TreeSet (14) Algorithm Videos (13) Amazon Interview (13) Basic Algorithm (13) Codechef (13) Combination (13) Computational Geometry (13) DP - Digit (13) LCA (13) LeetCode - DFS (13) Linked List (13) Long Increasing Sequence(LIS) (13) Math-Divisible (13) Reservoir Sampling (13) mitbbs (13) Algorithm - How To (12) Company - Microsoft (12) DP - Interval (12) DP - Multiple Relation (12) DP - Relation Optimization (12) LeetCode - Classic (12) Level Order Traversal (12) Prime (12) Pruning (12) Reconstruct Tree (12) Thinking (12) X Sum (12) AOJ (11) Bit Mask (11) Company-Snapchat (11) DP - Space Optimization (11) Dequeue (11) Graph DFS (11) MinMax (11) Miscs (11) Princeton (11) Quick Sort (11) Stack - Tree (11) 尺取法 (11) 挑战程序设计竞赛 (11) Coin Change (10) DFS+Backtracking (10) Facebook Hacker Cup (10) Fast Slow Pointers (10) HackerRank Easy (10) Interval Tree (10) Limited Range (10) Matrix - Traverse (10) Monotone Queue (10) SPOJ (10) Starting Point (10) States (10) Stock (10) Theory (10) Tutorialhorizon (10) Kadane - Extended (9) Mathblog (9) Max-Min Flow (9) Maze (9) Median (9) O(32N) (9) Quick Select (9) Stack Overflow (9) System Design (9) Tree - Conversion (9) Use XOR (9) Book Notes (8) Company-Amazon (8) DFS+BFS (8) DP - States (8) Expression (8) Longest Common Subsequence(LCS) (8) One Pass (8) Quadtrees (8) Traversal Once (8) Trie - Suffix (8) 穷竭搜索 (8) Algorithm Problem List (7) All Sub (7) Catalan Number (7) Cycle (7) DP - Cases (7) Facebook Interview (7) Fibonacci Numbers (7) Flood fill (7) Game Nim (7) Graph BFS (7) HackerRank Difficult (7) Hackerearth (7) Inversion (7) Kadane’s Algorithm (7) Manacher (7) Morris Traversal (7) Multiple Data Structures (7) Normalized Key (7) O(XN) (7) Radix Sort (7) Recursion (7) Sampling (7) Suffix Array (7) Tech-Queries (7) Tree - Serialization (7) Tree DP (7) Trie - Bit (7) 蓝桥杯 (7) Algorithm - Brain Teaser (6) BFS - Priority Queue (6) BFS - Unusual (6) Classic Data Structure Impl (6) DP - 2D (6) DP - Monotone Queue (6) DP - Unusual (6) DP-Space Optimization (6) Dutch Flag (6) How To (6) Interviewstreet (6) Knapsack - MultiplePack (6) Local MinMax (6) MST (6) Minimum Spanning Tree (6) Number - Reach (6) Parentheses (6) Pre-Sum (6) Probability (6) Programming Pearls (6) Rabin-Karp (6) Reverse (6) Scan from right (6) Schedule (6) Stream (6) Subset Sum (6) TSP (6) Xpost (6) n00tc0d3r (6) reddit (6) AI (5) Abbreviation (5) Anagram (5) Art Of Programming-July (5) Assumption (5) Bellman Ford (5) Big Data (5) Code - Solid (5) Code Kata (5) Codility-lessons (5) Coding (5) Company - WMware (5) Convex Hull (5) Crazyforcode (5) DFS - Multiple (5) DFS+DP (5) DP - Multi-Dimension (5) DP-Multiple Relation (5) Eulerian Cycle (5) Graph - Unusual (5) Graph Cycle (5) Hash Strategy (5) Immutability (5) Java (5) LogN (5) Manhattan Distance (5) Matrix Chain Multiplication (5) N Queens (5) Pre-Sort: Index (5) Quick Partition (5) Quora (5) Randomized Algorithms (5) Resources (5) Robot (5) SPFA(Shortest Path Faster Algorithm) (5) Shuffle (5) Sieve of Eratosthenes (5) Strongly Connected Components (5) Subarray Sum (5) Sudoku (5) Suffix Tree (5) Swap (5) Threaded (5) Tree - Creation (5) Warshall Floyd (5) Word Search (5) jiuzhang (5)

Popular Posts