将多个集合合并成没有交集的集合

 给定一个字符串的集合,格式如:。要求将其中交集不为空的集合合并,要求合并完成的集合之间无交集,例如上例应输出。

(1) 请描述你解决这个问题的思路;

(2) 给出主要的处理流程,算法,以及算法的复杂度;

(3) 请描述可能的改进。

 方案1:

????????

  采用并查集 (??并查集)首先所有的字符串都在单独的并查集中。然后依扫描每个集合,顺序合并将两个相邻元素合并。例如,对于,首先查看aaa和bbb是否在同一个并查集中,如果不在,那么把它们所在的并查集合并,然后再看bbb和ccc是否在同一个并查集中,如果不在,那么也把 它们所在的并查集合并。接下来再扫描其他的集合,当所有的集合都扫描完了,并查集代表的集合便是所求。复杂度应该是O(NlgN)的。改进的话,首先可以 记录每个节点的根结点,改进查询。合并的时候,可以把大的和小的进行合,这样也减少复杂度。

1、  概述

并查集(Disjoint set或者Union-find set)是一种树型的数据结构,常用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。

2、  基本操作

并查集是一种非常简单的数据结构,它主要涉及两个基本操作,分别为:

A. 合并两个不相交集合

B. 判断两个元素是否属于同一个集合

(1)       合并两个不相交集合(Union(x,y))

合并操作很简单:先设置一个数组Father[x],表示x的“父亲”的编号。那么,合并两个不相交集合的方法就是,找到其中一个集合最父亲的父亲(也就是最久远的祖先),将另外一个集合的最久远的祖先的父亲指向它。

上图为两个不相交集合,b图为合并后Father(b):=Father(g)

(2)       判断两个元素是否属于同一集合(Find_Set(x))

本操作可转换为寻找两个元素的最久远祖先是否相同。可以采用递归实现。

3、  优化

(1)       Find_Set(x)时,路径压缩

寻找祖先时,我们一般采用递归查找,但是当元素很多亦或是整棵树变为一条链时,每次Find_Set(x)都是O(n)的复杂度。为了避免这种情 况,我们需对路径进行压缩,即当我们经过”递推”找到祖先节点后,”回溯”的时候顺便将它的子孙节点都直接指向祖先,这样以后再次Find_Set(x) 时复杂度就变成O(1)了,如下图所示。可见,路径压缩方便了以后的查找。

(2)       Union(x,y)时,按秩合并

即合并的时候将元素少的集合合并到元素多的集合中,这样合并之后树的高度会相对较小。

4、  编程实现

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

int father[MAX];   /* father[x]表示x的父节点*/

 

int rank[MAX];     /*rank[x]表示x的秩*/

 

 

 

void Make_Set(int x)

 

{

 

father[x] = x; //根据实际情况指定的父节点可变化

 

rank[x] = 0;   //根据实际情况初始化秩也有所变化

 

}

 

/* 查找x元素所在的集合,回溯时压缩路径*/

 

int Find_Set(int x)

 

{

 

if (x != father[x])

 

{

 

father[x] = Find_Set(father[x]); //这个回溯时的压缩路径是精华

 

}

 

return father[x];

 

}

 

/*

 

按秩合并x,y所在的集合

 

下面的那个if else结构不是绝对的,具体根据情况变化

 

但是,宗旨是不变的即,按秩合并,实时更新秩。

 

*/

 

void Union(int x, int y)

 

{

 

x = Find_Set(x);

 

y = Find_Set(y);

 

if (x == y) return;

 

if (rank[x] > rank[y])

 

{

 

father[y] = x;

 

}

 

else

 

{

 

if (rank[x] == rank[y])

 

{

 

rank[y]++;

 

}

 

father[x] = y;

 

}

 

}

5、  复杂度分析

空间复杂度为O(N),建立一个集合的时间复杂度为O(1),N次合并M查找的时间复杂度为O(MAlpha(N)),这里Alpha是Ackerman函数的某个反函数,在很大的范围内(人类目前观测到的宇宙范围估算有10的80次方个原子,这小于 前面所说的范围)这个函数的值可以看成是不大于4的,所以并查集的操作可以看作是线性的。具体复杂度分析过程见参考资料(3)。

6、  应用

并查集常作为另一种复杂的数据结构或者算法的存储结构。常见的应用有:求无向图的连通分量个数,最近公共祖先(LCA),带限制的作业排序,实现Kruskar算法求最小生成树等。

7、  参考资料

(1)       并查集:http://www.nocow.cn/index.php/%E5%B9%B6%E6%9F%A5%E9%9B%86

(2)       博文《并查集详解》:http://www.cnblogs.com/cherish_yimi/

(3)       Thomas H. Cormen, Charles E.Leiserson, Ronald L. Rivest, and Clifford Stein. Introduction to Algorithms,Second Edition. MIT Press and McGraw-Hill, 2001. ISBN 0-262-03293-7. Chapter21: Data structures for Disjoint Sets, pp. 498–524.

查并集应用之集合合并 (2009-08-24 20:55)

分类: work

 

   给定一个字符串的集合,格式如下:{aaa  bbb ccc}, {bbb ddd},{eeefff}, {ddd hhh}要求将其中交集不为空的集合合并,要求合并后的集合之间无交集,例如eg中输出

  {aaa bbb ccc ddd hhh} {eee fff}

  1.不想交集合

    int A[26]...

    aaa<->1  bbb<->2

   

    root1 = find_root(A,1);

    root2 = find_root(A,2);

    union(root1,root2)

 输出的时候找到A[i]<-1的....就是跟再找A[j]=i的所有j就可以了

 

 

#include<stdio.h>
#include<stdlib.h>

#defineMAX 26

int relation[5][2]={
        {1,2},//{"aaa","bbb"}

        {1,3},//{"aaa","ccc"}

        {2,4},
        {5,6},
        {4,8}
      };
      
int find_root(int A[],int x)
{
  if(A[x]<0)
    return x;
  else
    return find_root(A, A[x]);
}

voidset_union(int A[],int num1,int num2)
{
  if(A[num1]<A[num2])
    A[num2]= num1;
  elseif(A[num1]>A[num2])
    A[num1]= num2;
  else
   {
    A[num1]--;
    A[num2]= num1;
   }
}

int main(int argc,char*argv[])
{
  int i;
  int root1;
  int root2;
  int j = 0;
  int root[2];
  int A[MAX];
  char str1[MAX];
  char str2[MAX];

  for(i=0;i<26;i++)
    A[i]=-1;
    
  for(i=0;i<6;i++)
   {
    root1 = find_root(A, relation[i][0]);
    root2 = find_root(A, relation[i][1]);
    set_union(A,root1, root2);
   }

  for(i=0;i<26;i++)
   {
    if(A[i]<-1)
     {
      printf("%c%c%c\t",i+'a'-1,i+'a'-1,i+'a'-1);
      for(j=0;j<26;j++)
       {
        if(A[j]==i)
          printf("%c%c%c\t",j+'a'-1,j+'a'-1,j+'a'-1);
        }
       printf("\n");
     }
   }
  system("PAUSE");    
  return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值