关于并差集笔者也实在不想扯那么多理论,代码这个东西越扯理论越糊涂,自己实践才会知道要点在哪里。
一、并差集概念
并查集就分为两个操作一个并一个查,通常这种题都是分类的题,类间元素都是有关系的,类外元素没有关系。题目一定会给出两个元素之间的关系,这时我们就查find(),如果两个元素在一个集合里面就什么都不作;如果两个元素不在一个集合里,我们将这两个元素所在集合合并,因为这个集合里面的元素都是相互之间有关系的。所以这样的题目,找到元素之间存在的关系就是题眼,关系就是分类的依据。
二、并差集操作
1、双亲表示法
这里我们采用双亲表示法作为实现并差集的数据结构,双表示法即每个结点要记录自己的父结点。
并差集随不同的数据结构会有不同的实现,所以还是那句话思想最重要。
//使用双亲表示法作为存储结构
public classNode {publicString data;public intparent;
}
2、初始化
privateNode[] ufs;
UFoperation(String[] datas) {
ufs= newNode[datas.length];for(int i = 0; i < datas.length; i++) {
ufs[i]= newNode();
ufs[i].data=datas[i];
ufs[i].parent= -1;
}
}
3、查操作
(1)常规查操作
intfind(String data) {int pos = -1;for(int i = 0; i < ufs.length; i++) {if(ufs[i].data==data) {
pos=i;break;
}
}while(ufs[pos].parent!=-1) {
pos=ufs[pos].parent;
}returnpos;
}
算法思想:就是找树的根节点啊
时间复杂度:O(h) h为树高
(2)带路径压缩的查操作
算法思想:我们每做一次查操作,就把待查结点以及其祖先结点(除根结点)都直接连在根结点之上,就是最终的树树高只有2,结点全部连到根结点上。
intfind(String data) {int pos = -1;int mark = -1;int root = -1;int pre = -1;for(int i = 0; i < ufs.length; i++) {if(ufs[i].data==data) {
pos=i;break;
}
}
mark=pos;while(ufs[pos].parent!=-1) {
pos=ufs[pos].parent;
}
root=pos;
pos=mark;//这里根节点一定不可以进来,因为使用了前置指针,会下标出界。
while(ufs[pos].parent!=-1) {
pre=ufs[pos].parent;
ufs[pos].parent=root;
pos=pre;
}returnroot;
}
时间复杂度:O(1)
4、并操作
算法思想:直接把一棵树根结点的父指针指向另一棵树的根结点
booleanmerge(String s1, String s2) {int p1 =find(s1);int p2 =find(s2);if(p1==p2) return false;else{
ufs[p2].parent=p1;return true;
}
}
时间复杂度:O(1)
完整代码:
public classUFoperation {privateNode[] ufs;
UFoperation(String[] datas) {
ufs= newNode[datas.length];for(int i = 0; i < datas.length; i++) {
ufs[i]= newNode();
ufs[i].data=datas[i];
ufs[i].parent= -1;
}
}/*int find(String data) {
int pos = -1;
for(int i = 0; i < ufs.length; i++) {
if(ufs[i].data==data) {
pos = i;
break;
}
}
while(ufs[pos].parent!=-1) {
pos = ufs[pos].parent;
}
return pos;
}*/
//带路径压缩的
intfind(String data) {int pos = -1;int mark = -1;int root = -1;int pre = -1;for(int i = 0; i < ufs.length; i++) {if(ufs[i].data==data) {
pos=i;break;
}
}
mark=pos;while(ufs[pos].parent!=-1) {
pos=ufs[pos].parent;
}
root=pos;
pos=mark;//这里根节点一定不可以进来,因为使用了前置指针,会下标出界。
while(ufs[pos].parent!=-1) {
pre=ufs[pos].parent;
ufs[pos].parent=root;
pos=pre;
}returnroot;
}booleanmerge(String s1, String s2) {int p1 =find(s1);int p2 =find(s2);if(p1==p2) return false;else{
ufs[p2].parent=p1;return true;
}
}public static voidmain(String[] args) {
String[] input= new String[] {"A","B","C","D","E","F","G","H","I"};
UFoperation u= newUFoperation(input);
String[][] test= {{"A","B"},{"A","C"},{"D","E"},{"D","F"},{"D","G"},{"H","I"}};for(int i = 0; i < test.length; i++) {
u.merge(test[i][0],test[i][1]);
}for(String s : input) {
System.out.print(u.find(s)+ " ");
}
}
}
View Code
5、带权路径
classSolution {private HashMap SS = new HashMap<>();private HashMap w = new HashMap<>();//记录到父节点的权值
public double[] calcEquation(List> equations, double[] values, List>queries) {
init(equations);double[] ans = new double[queries.size()];for(int i = 0; i < values.length; i++) {
List list =equations.get(i);
merge(list.get(0),list.get(1),values[i]);
}for(int i = 0; i < queries.size(); i++) {
List list =queries.get(i);if(find(list.get(1))==find(list.get(0))&&SS.containsKey(list.get(0)))
ans[i]= getRootValue(list.get(1))/getRootValue(list.get(0));elseans[i]= -1.0;
}returnans;
}private void init(List>equations) {for(Listlist : equations) {for(String s : list) {if(!SS.containsKey(s)) {
SS.put(s,s);
w.put(s,1.0);
}
}
}
}privateString find(String s1) {while(SS.get(s1)!=s1) {
s1=SS.get(s1);
}returns1;
}//求到根节点的权值
private doublegetRootValue(String s) {double value = 1.0;while(SS.get(s)!=s) {
value= value*(w.get(s));
s=SS.get(s);
}returnvalue;
}private void merge(String s1, String s2, doublevalue) {
String p1=find(s1);
String p2=find(s2);if(p1==p2) return;
SS.put(p2,p1);
w.put(p2,(getRootValue(s1)*value)/getRootValue(s2));
}
}
像这样的题要在40钟之内完成还是有压力的鸭。