笔试算法题(38):并查集(Union-Find Sets)

出题:并查集(Union-Find Sets)

分析:

  • 一种树型数据结构,用于处理不相交集合(Disjoint
    Sets)的合并以及查询;一开始让所有元素独立成树,也就是只有根节点的树;然后根据需要将关联的元素(树)进行合并;合并的方式仅仅是将一棵树最原始的节点的父亲索引指向另一棵树;

  • 优化:加入一个rank数组存储节点深度的下界(从当前节点到其最远子节点的距离),从而可以启发式的对树进行合并,从而减少树的深度,防止树的退化;使
    得包含较少节点的树根指向包含较多节点的树根,具体指代为树的高度;另一个优化就是路径压缩,尽可能将子节点都直接连接到根节点之后;

  • 并查集的空间复杂度为O(N),构建一个集合的时间复杂度为O(N);压缩后的查找复杂度是一个很小的常数;应用:Kruskal算法求最小生成树中判断新加入的边是否在同一棵树内部;两个节点的最近公共祖先(Least
    Common Ancestors);

  • 初始化father:各个节点独立成树,并且其father[i]=i,也就是其父节点就是其自身;

    father[i]
    0 1 2 3 4 5 6 7 8 9
    0 1 2 3 4 5 6 7 8 9

    初始化rank:各个节点为根节点,所以高度都为1;
    rank[i]
    0 1 2 3 4 5 6 7 8 9
    1 1 1 1 1 1 1 1 1 1

    合并2和6:由于rank[2]=rank[6],所以将2的父亲索引指向6,这样2和6就在同一棵树;并需将6的rank值增加1;
    father[i]
    0 1 2 3 4 5 6 7 8 9
    0 1 6 3 4 5 6 7 8 9
    rank[i]
    0 1 2 3 4 5 6 7 8 9
    1 1 1 1 1 1 2 1 1 1

解题:


 1 int *father;
2 int *rank;
3
4 /**
5 * 并查集的初始化:
6 * 数组father中的元素在最开始ide时候都是独立的树,也就是只有根节点
7 * 的树,数组father的下标i表示节点,而father[i]的值表示i节点的父亲
8 * 节点;rank[i]=1表示一开始所有树节点的高度都为1
9 * */
10 void init(int cap) {
11 father=new int[cap];
12 rank=new int[cap];
13 /**
14 * 时间复杂度为O(N)
15 * */
16 for(int i=0;i<10;i++) {
17 father[i]=i;
18 rank[i]=1;
19 }
20 }
21
22 void clean() {
23 delete [] father;
24 delete [] rank;
25 }
26 /**
27 * 查找元素所在的集合并进行路劲压缩:
28 * 由于需要频繁使用GetFather()函数,并且其时间复杂度受树结构影响;
29 * 当元素较多的时候,集合退化成链表,则GetFather()需要O(N),所以
30 * 需要对其进行优化,每次调用GetFather()的时候都将输入元素压缩成
31 * 最原始父亲节点的直接子节点
32 * */
33 int GetFather(int son) {
34 if(father[son]==son)
35 return son;
36 else {
37 father[son]=GetFather(father[son]);
38 return father[son];
39 }
40 }
41
42 /**
43 * 合并两个不相交的集合:
44 * 输入元素x和y来自两个不相交的集合,找到其最原始的父亲节点
45 * 并将一个原始父亲节点设置为另一个原始父亲节点的父亲节点
46 * */
47 void Union1(int x, int y) {
48 /**
49 * GetFather()为递归寻找输入节点的最原始的父亲节点
50 * */
51 int fx=GetFather(x);
52 int fy=GetFather(y);
53 /**
54 * 判断x和y是否来自同一棵树,如果不是才进行赋值;其实可以
55 * 不同进行判断(省去if语句)
56 * 注意最原始父亲节点的father[i]=i;
57 * */
58 if(fx!=fy)
59 father[fx]=fy;
60 }
61
62 /**
63 * 利用rank加权数组启发式进行合并
64 * */
65 void Union2(int x, int y) {
66 int fx=GetFather(x);
67 int fy=GetFather(y);
68
69 if(fx==fy) return;
70 /**
71 * rank[fx]较大,说明其越靠近根节点,则将
72 * fy连接到其后面可以压缩路径
73 * */
74 if(rank[fx]>rank[fy])
75 father[fy]=fx;
76 else {
77 if(rank[fx]==rank[fy])
78 rank[fy]++;
79 father[fx]=fy;
80 }
81 }
82
83 /**
84 * 判断两个元素是否属于同一个集合:
85 * 利用GetFather()函数判断其最原始父亲节点是否相同
86 * */
87 bool IsSameSet(int x, int y) {
88 return GetFather(x)==GetFather(y);
89 }

笔试算法题(38):并查集(Union-Find Sets),布布扣,bubuko.com

时间: 2024-10-28 22:42:09

笔试算法题(38):并查集(Union-Find Sets)的相关文章

笔试算法题(50):简介 - 广度优先 &amp; 深度优先 &amp; 最小生成树算法

广度优先搜索&深度优先搜索(Breadth First Search & Depth First Search) BFS优缺点: 同一层的所有节点都会加入队列,所以耗用大量空间: 仅能非递归实现: 相比DFS较快,空间换时间: 适合广度大的图: 空间复杂度:邻接矩阵O(N^2):邻接表O(N+E): 时间复杂度:O(V+E): DFS优缺点: 无论是系统栈还是用户栈保存的节点数都只是树的深度,所以空间耗用小: 有递归和非递归实现: 由于有大量栈操作(特别是递归实现时候的系统调用),执行速度

笔试算法题(39):Trie树(Trie Tree or Prefix Tree)

出题:TRIE树 (Trie Tree or Prefix Tree): 分析: 又称字典树或者前缀树,一种用于快速检索的多叉树结构:英文字母的Trie树为26叉树,数字的Trie树为10叉树:All the descendants of a node have a common prefix of the sequence associated with that node, and the root is associated with the empty sequence. 由于不同的se

笔试算法题(09):查找指定和值的两个数 &amp; 构造BST镜像树

出题:输入一个已经升序排序的数组和一个数字:要求在数组中查找两个数,这两个数的和正好等于输入的那个数字,输出任意一对数字就可以,要求时间复杂度是O(n): 分析:对于升序排序的数组{-i-j-k-m--},只有可能是i+m=j+k(j和k可能是同一个数),所以可以从两边往中间收缩而忽视其他交叉相加的情况: 解题: 1 void FindSumFactor(int *array, int length, int sum) { 2 int left=0, right=length-1; 3 whil

笔试算法题(07):还原后序遍历数组 &amp; 半翻转英文句段

出题:输入一个整数数组,判断该数组是否符合一个二元查找树的后序遍历(给定整数数组,判定其是否满足某二元查找树的后序遍历): 分析:利用后序遍历对应到二元查找树的性质(序列最后一个元素必定是根节点,从左向右第一个比根节点大的元素开始直到根节点之前的所有元素必定在右子树,之前的所有元素必定在左子树): 解题: 1 bool PostOrderCheck(int *array, int i, int j) { 2 /** 3 * 如快速排序一样,解决小子文件 4 * */ 5 if(j-i+1 ==

笔试算法题(51):简介 - 红黑树(RedBlack Tree)

红黑树(Red-Black Tree) 红黑树是一种BST,但是每个节点上增加一个存储位表示该节点的颜色(R或者B):通过对任何一条从root到leaf的路径上节点着色方式的显示,红黑树确保所有路径的差值不会超过一倍,最终使得BST接近平衡: 红黑树内每个节点包含五个属性:color, key, left, right和p,p表示指向父亲节点的指针:一棵BST需要同时满足下述五个性质才能称作红黑树: 每个节点只能是红色或者黑色节点中的一种: 根节点必须是黑色: 每个叶节点(NULL)必须是黑色:

笔试算法题(24):找出出现次数超过一半的元素 &amp; 二叉树最近公共父节点

出题:数组中有一个数字出现的次数超过了数组长度的一半,请找出这个数字: 分析: 解法1:首先对数组进行排序,时间复杂度为O(NlogN),由于有一个数字出现次数超过了数组的一半,所以如果二分数组的话,划分元素肯定就是这个数字: 解法2:首先创建1/2数组大小的Hash Table(哈希表可以替代排序时间,由于一个数字出现超过了数组的一半,所以不同元素个数肯定不大于数组的一半),空间复杂度O(N),顺序扫描映射数 组元素到Hash Table中并计数,最后顺序扫描Hash Table,计数超过数组

笔试算法题(06):最大连续子数组和 &amp; 二叉树路径和值

出题:预先输入一个整型数组,数组中有正数也有负数:数组中连续一个或者多个整数组成一个子数组,每个子数组有一个和:求所有子数组中和的最大值,要求时间复杂度O(n): 分析: 时间复杂度为线性表明只允许一遍扫描,当然如果最终的最大值为0表明所有元素都是负数,可以用线性时间O(N)查找最大的元素.具体算法策略请见代码和注释: 子数组的起始元素肯定是非负数,如果添加的元素为正数则记录最大和值并且继续添加:如果添加的元素为负数,则判断新的和是否大于0,如果小于0则以下一个元素作为起始元素重新开始,如果大于

笔试算法题(42):线段树(区间树,Interval Tree)

议题:线段树(Interval Tree) 分析: 线段树是一种二叉搜索树,将一个大区间划分成单元区间,每个单元区间对应一个叶子节点:内部节点对应部分区间,如对于一个内部节点[a, b]而言,其左子节点表示的区间为[a, (a+b)/2],其右子节点表示的区间为[1+(a+b)/2, b]: 对于区间长度为N的线段树,由于其单元节点都是[a, a]的叶子节点,所以其叶子节点数为N,并且整棵树为平衡二叉树,所以总节点数为2N-1,树的深度为log(N)+1: 插入操作:将一条线段[a, b]插入到

笔试算法题(20):寻找丑数 &amp; 打印1到N位的所有的数

出题:将只包含2,3,5的因子的数称为丑数(Ugly Number),要求找到前面1500个丑数: 分析: 解法1:依次判断从1开始的每一个整数,2,3,5是因子则整数必须可以被他们其中的一个整除,如果不包含任何其他因子则最终的结果为1: 解法2:小丑数必然是某个大丑数的因子,也就是乘以2,3,或者5之后的值,所以可以利用已经找到的丑数来寻找下一个丑数,使用数组有序保存已经找到的丑 数,并且当前最大丑数值为M:用大于M/2的丑数乘以2得到M1,用大于M/3的丑数乘以3得到M2,用大于M/5的丑数