并查集的实现
1、概述
并查集(Disjoint set或者Union-find set)是一种树型的数据结构,常用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。
2、基本操作
并查集是一种非常简单的数据结构,它主要未来解决如下两种经常性操作而产生的,分别为:
A. 合并两个不相交集合
B. 判断两个元素是否属于同一个集合(经常性)
(1)合并两个不相交集合
合并操作很简单:先设置一个数组Father[x],表示x的“父亲”的编号。那么,合并两个不相交集合的方法就是,找到其中一个集合最父亲的父亲(也就是最久远的祖先),将另外一个集合的最久远的祖先的父亲指向它。
上图为两个不相交集合,b图为合并后Father(b):=Father(g)
(2)判断两个元素是否属于同一集合
本操作可转换为寻找两个元素的最久远祖先是否相同。可以采用递归实现。
3、优化
(1) 路径压缩
寻找祖先时,我们一般采用递归查找,但是当元素很多亦或是整棵树变为一条链时,每次Find_Set(x)都是O(n)的复杂度。为了避免这种情况,我们需对路径进行压缩,即当我们经过”递推”找到祖先节点后,”回溯”的时候顺便将它的子孙节点都直接指向祖先,这样以后再次Find_Set(x)时复杂度就变成O(1)了,如下图所示。可见,路径压缩方便了以后的查找。
(2)合并时,按秩合并
即合并的时候将元素少的集合合并到元素多的集合中,这样合并之后树的高度会相对较小。
4、源代码
其代码如下,可点击这里进行下载:
从数据文件读取数据,然后构建完并查集后进行输出,保存在目标文件中,程序用到了boost library,所以运行程序需要添加相应库文件。
1 #include <fstream> 2 #include <iostream> 3 #include <string> 4 5 #include "boost/algorithm/string.hpp" 6 #include "boost/regex.hpp" 7 8 using namespace std; 9 using namespace boost; 10 11 const int MAX_ID = 200000001; 12 13 int father[MAX_ID]; 14 15 void init_father() 16 { 17 for (int i = 0; i < MAX_ID; i++) 18 { 19 father[i] = i; 20 } 21 } 22 23 int find_ancestry(int id, int& count) 24 { 25 count++; 26 if(id != father[id]) 27 { 28 father[id] = find_ancestry(father[id],count); 29 } 30 return father[id]; 31 } 32 33 int process(string instr, int id_x, int id_y, fstream &out_file) 34 { 35 //q 36 if (instr == "") 37 { 38 return -1; 39 } 40 //不是同一祖先 41 int deep_x = 0; 42 int deep_y = 0; 43 int temp_y = find_ancestry(id_y, deep_y); 44 int temp_x = find_ancestry(id_x, deep_x); 45 46 if (temp_x != temp_y) 47 { 48 //q 49 if(instr == "Q") 50 { 51 out_file << "N\n"; 52 } 53 else//p 进行合并 54 {//优化2 ? 优化1 : 优化1 55 deep_x >= deep_y ? father[temp_y] = temp_x 56 : father[temp_x] = temp_y; //y->x 57 } 58 } 59 else 60 { 61 if(instr == "Q") 62 { 63 out_file << "Y\n"; 64 } 65 } 66 return 0; 67 } 68 69 fstream FOpen(string file_name, int read) 70 { 71 if (file_name != "" && !file_name.empty() && (read == 0 || read == 1)) 72 { 73 fstream File; 74 if (read) 75 { 76 File.open(file_name); 77 } 78 else 79 File.open(file_name, ios::out); 80 81 if (!File) 82 { 83 cout << file_name << " can't not be opened." << endl; 84 abort(); 85 } 86 return File; 87 } 88 else 89 cout << "can't get the file name." << endl; 90 } 91 92 int checkError(const string &line_temp) 93 { 94 regex expr("(P|Q)(\\s\\d+){2}$"); 95 cmatch what; 96 if(!regex_match(line_temp.c_str(), what, expr)) 97 { 98 return -1; 99 } 100 return 0; 101 } 102 103 int read_line(fstream &cusu_file, string &line_temp) 104 { 105 if (cusu_file.eof()) 106 { 107 return -1; 108 } 109 else 110 { 111 getline(cusu_file, line_temp); 112 return 0; 113 } 114 } 115 116 int main() 117 { 118 init_father(); 119 vector <string> pro_data; 120 string temp_data = ""; 121 fstream in_file = FOpen("division.in", 1); 122 fstream out_file = FOpen("division.out", 0); 123 while(!read_line(in_file, temp_data)) 124 { 125 if(!checkError(temp_data)) 126 { 127 boost::algorithm::split(pro_data, temp_data, boost::algorithm::is_any_of(" ")); 128 if (!(pro_data[0] == "" || pro_data[1] == "" || pro_data[2] == "")) 129 { 130 process(pro_data[0], atoi(pro_data[1].c_str()), atoi(pro_data[2].c_str()), out_file); 131 } 132 } 133 else 134 { 135 out_file << "SORRY\n"; 136 break; 137 } 138 } 139 out_file.close(); 140 in_file.close(); 141 return 0; 142 }
数据文件:
输出文件(对于Hi?不标准的输入会提示出错):
5、小结
并查集对大规模数据的查询有快速响应的作用,在此程序中,可将数组替换成链表,也可以运用分布式存贮应对海量数据;此外,程序的两处优化对提高程序效率效果明显。