并集算法 | Set 2 (Union By Rank and Path Compression)
在上一篇文章中,我们介绍了并集查找算法并使用它来检测图中的循环。我们对子集使用了以下union()和find()操作。
C++
// Naive implementation of find
int find(int parent[], int i)
{
if (parent[i] == -1)
return i;
return find(parent, parent[i]);
}
// Naive implementation of union()
void Union(int parent[], int x, int y)
{
int xset = find(parent, x);
int yset = find(parent, y);
parent[xset] = yset;
}
Java
// Naive implementation of find
static int find(int parent[], int i)
{
if (parent[i] == -1)
return i;
return find(parent, parent[i]);
}
// Naive implementation of union()
static void Union(int parent[], int x, int y)
{
int xset = find(parent, x);
int yset = find(parent, y);
parent[xset] = yset;
}
// This code is contributed by divyesh072019
Python3
# Naive implementation of find
def find(parent, i):
if (parent[i] == -1):
return i
return find(parent, parent[i])
# Naive implementation of union()
def Union(parent, x, y):
xset = find(parent, x)
yset = find(parent, y)
parent[xset] = yset
# This code is contributed by rutvik_56
C#
// Naive implementation of find
static int find(int []parent, int i)
{
if (parent[i] == -1)
return i;
return find(parent, parent[i]);
}
// Naive implementation of union()
static void Union(int []parent, int x, int y)
{
int xset = find(parent, x);
int yset = find(parent, y);
parent[xset] = yset;
}
// This code is contributed by pratham76
Javascript
上面的union()和find()是幼稚的,最坏情况的时间复杂度是线性的。为表示子集而创建的树可能是倾斜的,并且可能变得像一个链表。以下是最坏情况的示例。
Let there be 4 elements 0, 1, 2, 3
Initially, all elements are single element subsets.
0 1 2 3
Do Union(0, 1)
1 2 3
/
0
Do Union(1, 2)
2 3
/
1
/
0
Do Union(2, 3)
3
/
2
/
1
/
0
在最坏的情况下,上述操作可以优化到O(Log n) 。这个想法是总是在更深的树的根下附加更小的深度树。这种技术称为union by rank 。优先使用术语rank而不是 height,因为如果使用路径压缩技术(我们在下面讨论过),那么rank并不总是等于高度。此外,树的大小(代替高度)也可以用作rank 。使用大小作为等级也会产生 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
朴素方法的第二个优化是路径压缩。这个想法是在调用find()时展平树。当为元素 x 调用find()时,将返回树的根。 find()操作从 x 向上遍历找到根。路径压缩的思想就是把找到的根作为x的父节点,这样我们就不用再遍历所有的中间节点了。如果 x 是子树的根,则 x 下所有节点的路径(到根)也会压缩。
Let the subset {0, 1, .. 9} be represented as below and find() is called
for element 3.
9
/ | \
4 5 6
/ / \
0 7 8
/
3
/ \
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 and 0 as the child of 9 so
that when find() is called next time for 0, 1, 2 or 3, the path to root is reduced.
--------9-------
/ / / \ \
0 4 5 6 3
/ \ / \
7 8 1 2
这两种技术相辅相成。每个操作的时间复杂度变得比 O(Logn) 还要小。事实上,摊销的时间复杂度实际上变成了小常数。
以下是基于秩和路径压缩的联合,用于在图中找到循环。
C++
// A C++ program to detect cycle in a graph using union by
// rank and path compression
#include
using namespace std;
// a structure to represent an edge in the 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 xroot, int yroot)
{
// 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;
}
// Driver code
int main()
{
/* Let us create the following graph
0
| \
| \
1-----2 */
int V = 3, E = 3;
struct Graph* graph = createGraph(V, E);
// add edge 0-1
graph->edge[0].src = 0;
graph->edge[0].dest = 1;
// add edge 1-2
graph->edge[1].src = 1;
graph->edge[1].dest = 2;
// add edge 0-2
graph->edge[2].src = 0;
graph->edge[2].dest = 2;
if (isCycle(graph))
cout << "Graph contains cycle";
else
cout << "Graph doesn't contain cycle";
return 0;
}
// This code is contributed by Aditya Kumar (adityakumar129)
C
// A C program to detect cycle in a graph using union by
// rank and path compression
#include
#include
// a structure to represent an edge in the graph
typedef struct Edge {
int src, dest;
}Edge;
// a structure to represent a graph
typedef struct Graph {
// V-> Number of vertices, E-> Number of edges
int V, E;
// graph is represented as an array of edges
struct Edge* edge;
}Graph;
typedef struct subset {
int parent;
int rank;
}subset;
// Creates a graph with V vertices and E edges
Graph* createGraph(int V, int E)
{
Graph* graph = (Graph*)malloc(sizeof(Graph));
graph->V = V;
graph->E = E;
graph->edge = (Edge*)malloc(graph->E * sizeof(Edge));
return graph;
}
// A utility function to find set of an element i
// (uses path compression technique)
int find(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(subset subsets[], int xroot, int yroot)
{
// 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(Graph* graph)
{
int V = graph->V;
int E = graph->E;
// Allocate memory for creating V sets
subset* subsets = (subset*)malloc(V * sizeof(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;
}
// Driver code
int main()
{
/* Let us create the following graph
0
| \
| \
1-----2 */
int V = 3, E = 3;
Graph* graph = createGraph(V, E);
// add edge 0-1
graph->edge[0].src = 0;
graph->edge[0].dest = 1;
// add edge 1-2
graph->edge[1].src = 1;
graph->edge[1].dest = 2;
// add edge 0-2
graph->edge[2].src = 0;
graph->edge[2].dest = 2;
if (isCycle(graph))
printf("Graph contains cycle");
else
printf("Graph doesn't contain cycle");
return 0;
}
// This code is contributed by Aditya Kumar (adityakumar129)
Java
// A union by rank and path compression
// based program to detect cycle in a graph
class Graph
{
int V, E;
Edge[] edge;
Graph(int nV, int nE)
{
V = nV;
E = nE;
edge = new Edge[E];
for (int i = 0; i < E; i++)
{
edge[i] = new Edge();
}
}
// class to represent edge
class Edge
{
int src, dest;
}
// 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++;
}
}
// The main function to check whether
// a given graph contains cycle or not
int isCycle(Graph graph)
{
int V = graph.V;
int E = graph.E;
subset[] subsets = new subset[V];
for (int v = 0; v < V; v++) {
subsets[v] = new subset();
subsets[v].parent = v;
subsets[v].rank = 0;
}
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;
}
// Driver Code
public static void main(String[] args)
{
/* Let us create the following graph
0
| \
| \
1-----2 */
int V = 3, E = 3;
Graph graph = new Graph(V, E);
// add edge 0-1
graph.edge[0].src = 0;
graph.edge[0].dest = 1;
// add edge 1-2
graph.edge[1].src = 1;
graph.edge[1].dest = 2;
// add edge 0-2
graph.edge[2].src = 0;
graph.edge[2].dest = 2;
if (graph.isCycle(graph) == 1)
System.out.println("Graph contains cycle");
else
System.out.println(
"Graph doesn't contain cycle");
}
}
// This code is contributed
// by ashwani khemani
Python3
# A union by rank and path compression based
# program to detect cycle in a graph
from collections import defaultdict
# a structure to represent a graph
class Graph:
def __init__(self, num_of_v):
self.num_of_v = num_of_v
self.edges = defaultdict(list)
# graph is represented as an
# array of edges
def add_edge(self, u, v):
self.edges[u].append(v)
class Subset:
def __init__(self, parent, rank):
self.parent = parent
self.rank = rank
# A utility function to find set of an element
# node(uses path compression technique)
def find(subsets, node):
if subsets[node].parent != node:
subsets[node].parent = find(subsets, subsets[node].parent)
return subsets[node].parent
# A function that does union of two sets
# of u and v(uses union by rank)
def union(subsets, u, v):
# Attach smaller rank tree under root
# of high rank tree(Union by Rank)
if subsets[u].rank > subsets[v].rank:
subsets[v].parent = u
elif subsets[v].rank > subsets[u].rank:
subsets[u].parent = v
# If ranks are same, then make one as
# root and increment its rank by one
else:
subsets[v].parent = u
subsets[u].rank += 1
# The main function to check whether a given
# graph contains cycle or not
def isCycle(graph):
# Allocate memory for creating sets
subsets = []
for u in range(graph.num_of_v):
subsets.append(Subset(u, 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 u in graph.edges:
u_rep = find(subsets, u)
for v in graph.edges[u]:
v_rep = find(subsets, v)
if u_rep == v_rep:
return True
else:
union(subsets, u_rep, v_rep)
# Driver Code
g = Graph(3)
# add edge 0-1
g.add_edge(0, 1)
# add edge 1-2
g.add_edge(1, 2)
# add edge 0-2
g.add_edge(0, 2)
if isCycle(g):
print('Graph contains cycle')
else:
print('Graph does not contain cycle')
# This code is contributed by
# Sampath Kumar Surine
C#
// A union by rank and path compression
// based program to detect cycle in a graph
using System;
class Graph {
public int V, E;
public Edge[] edge;
public Graph(int nV, int nE)
{
V = nV;
E = nE;
edge = new Edge[E];
for (int i = 0; i < E; i++)
{
edge[i] = new Edge();
}
}
// class to represent edge
public class Edge {
public int src, dest;
}
// class to represent Subset
class subset
{
public int parent;
public 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++;
}
}
// The main function to check whether
// a given graph contains cycle or not
int isCycle(Graph graph)
{
int V = graph.V;
int E = graph.E;
subset[] subsets = new subset[V];
for (int v = 0; v < V; v++)
{
subsets[v] = new subset();
subsets[v].parent = v;
subsets[v].rank = 0;
}
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;
}
// Driver Code
static public void Main(String[] args)
{
/* Let us create the following graph
0
| \
| \
1-----2 */
int V = 3, E = 3;
Graph graph = new Graph(V, E);
// add edge 0-1
graph.edge[0].src = 0;
graph.edge[0].dest = 1;
// add edge 1-2
graph.edge[1].src = 1;
graph.edge[1].dest = 2;
// add edge 0-2
graph.edge[2].src = 0;
graph.edge[2].dest = 2;
if (graph.isCycle(graph) == 1)
Console.WriteLine("Graph contains cycle");
else
Console.WriteLine(
"Graph doesn't contain cycle");
}
}
// This code is contributed
// by Arnab Kundu
Javascript
Graph contains cycle
相关文章 :
并集算法 | Set 1(检测无向图中的循环)
不相交集数据结构(Java实现)
贪心算法 | Set 2(Kruskal 的最小生成树算法)
作业排序问题 |集 2(使用不相交集)