未经授权,禁止转载!创作不易,尊重原创!~~
前言
最近在看tensorflow中的placers算法, 发现其中应用了并查集算法来配置算子的设备device,因此学习了一下并查集相关的内容,记录下来。
背景
政府想要实施一项修路的惠民政策,要求县下面的各个镇都能够通过公路联通起来,如果没有直通的公路,通过其他镇的公路间接到达也行。求最低要修多少条路。
假设县里有5个镇【1,2,3,4,5】, 修了3条公路, 如下:
5,3 #镇的个数,公路数
1,2
3,4
2,5
分析,上面{1,2,5}是联通的,{3,4}是联通的,所以还需要再修一条公路将这两个联通分量连接起来。真实案例的数据量比这大很多,所以用代码实现会提高效率
代码的输入:
M, N // 镇数, 公路数
m, n // n行输入,表示联通的两个镇
代码输出:
n // 需要新修的公路数
概念
并查集
可以进行合并和查找的集合。
合并的条件: 只要两个集合中有公共的上级即可。以修路为例,1镇和5镇都和2镇连接, 因此1镇也可以达到5镇。把2镇当作1镇和5镇的上级, 1镇和5镇有共同的上级,因此【1,2,5】镇可以合并成一个集合, 专业名次就叫一个联通分量。
查找:查找元素的上级。 想要知道一个镇能不能到达另外一个镇,只需要查找两个镇有没有共同的上级即可。
设计
- 用一个长度为M的数组来记录联通情况;
- 数组下标表示各个镇;
- 对应的值表示属于哪一个联通分量。
怎么表示联通分量呢?当然我们可以用“是s1, s2,…”这样的字符串来做联通分量的表示符,也可以从从同一个集合中随便找一个元素来表示联通分量,例如用2镇表示【1,2,5】镇的联通分量。这样的一个好处是减少查找时对数组的修改,详情见代码分析。
实现
创建并查集集合
void makeSet() {
union_set_ = new int[len_];
for (int i = 0; i < len_; i++) {
union_set_[i] = i;
}
}
合并操作:
void union_op(int x, int y) {
//TODO(check)
if (std::max(x, y) > len_-1) {
std::cout << "input is invalid" << std::endl;
}
if (union_set_[x] != union_set_[y]) {
union_set_[y] = union_set_[x];
}
}
查找操作:
int find_op(int target) {
// TODO(check)
int father = union_set_[target];
if (father == target)
return target;
while (father != union_set_[father]) {
father = union_set_[father];
}
return father;
}
int find1_op(int target) {
// TODO(check)
int father = union_set_[target];
if (father == target)
return target;
return find1_op(father);
}
完整代码:
union find 代码
优化-路径压缩:
find操作, 如果上级太多,以至于找到根节点需要很多次。 最坏情况就是一字长蛇阵型的数组,需要的时间复杂度是O(n),这时候有一个优化就是希望每一节点的上级就是最终的root节点, 称为路径压缩。
代码实现:
int find2_op(int target) {
// TODO(check)
int father = union_set_[target];
if (father == target)
return target;
return union_set_[father] = find2_op(father);
}