动态连接
Posted redo19990701
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动态连接相关的知识,希望对你有一定的参考价值。
[TOC]
动态连接
这里有一系列的对象(一开始之间没有连接)
graph TD
0((0))
1((1))
2((2))
3((3))
4((4))
- Union command(连结命令):连接两个对象
- Find/connected query(连结查询):这两个对象间有没有连接?
通过Union操作使得对象之间产生连接
- union(0,1)
- union(2,3)
- union(3,4)
graph TD
0((0))===1((1))
2((2))===3((3))
3===4((4))
现在可以做一些查询
connected(0,1) | 是,0和1之间有连线 |
---|---|
connected(1,3) | 否,从1无法通过连线到达3 |
connected(2,4) | 是,2可以通过连线到达4 |
这里连接的特性
- 自反性
- 对称性
- 传递性
快速查找
数据结构
- 使用简单的长度为N的数组来表示(N为节点个数)
- 解释:以数组下标标识每一个独立的节点,如果数组不同下标的位置上数字相同则表示这两个下标标识的节点有连接关系
方法实现
- 上面提到的Find(i,j)只需要检查数组下标为i,j的位置是否有相同的数字,如果是就返回true,反之返回false
- 上面提到的union(i,j)只需要将数组下标为i,j的位置的数字记录下来为a,b,然后在数组中寻找,如果有某一个下标的位置上的数字为b,就将该下标位置的数字更改为a(即是使得数字统一等于i和j中小的下标的数字)
演示(圆圈表示一个节点,圈内数字表示该节点被该数字下标所标识)
- 一开始各下标的数字等于下标,即节点间互相不连接
id | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
num(存储的数字) | 0 | 1 | 2 | 3 | 4 |
graph TD
0((0))
1((1))
2((2))
3((3))
4((4))
- union(0,1)
- 使得下标为0,1的位置的数字一致(这里总是使得大的下标的数字等于小的下标的数字)
id | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
num(存储的数字) | 0 | 0 | 2 | 3 | 4 |
graph TD
0((0))===1((1))
2((2))
3((3))
4((4))
- union(3,4)
- 使得下标为3,4的位置的数字一致(这里总是使得大的下标的数字等于小的下标的数字)
id | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
num(存储的数字) | 0 | 0 | 2 | 3 | 3 |
graph TD
0((0))===1((1))
2((2))
3((3))===4((4))
- union(1,3)
- 使得下标为3,4的位置的数字一致(这里总是使得大的下标的数字等于小的下标的数字)
- union()操作不一定只更改一个下标位置的数字
id | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
num(存储的数字) | 0 | 0 | 2 | 0 | 0 |
graph LR
0((0))===1((1))
2((2))
1===3
3((3))===4((4))
Code by java
public class QuickFindUF {
private int[] id;//数组
public QuickFindUF(int N){
id=new int[N];//分配空间,N即为节点个数
for(int i=0;i<N;i++)
id[i]=i;//下标位置数字等于下标
}
public boolean connected(int i,int j){
return id[i]==id[j];//检查数组下标为i,h的两个数字是否相同
}
public void union(int i,int j){
int a=id[i];//记录数组下标为i,j的数字
int b=id[j];
if(a==b)
return;
for(int k=0;k<id.length;k++)
if(id[k]==b)//使得数组中所有的数字b更改为a;
id[k]=a;
}
}
特性分析
操作 | initialization初始化 | union连接 | find查找 |
---|---|---|---|
访问数组次数 | N | N | 1 |
- 查找很迅速
- 连接操作很费时,如果对节点为N个的对象进行N次union操作,复杂的会达到N^^2^
快速合并
数据结构
- 使用简单的长度为N的数组来表示(N为节点个数)
- 解释:以数组下标标识每一个独立的节点,数组下标的位置上的数字表示该下标标识的节点的父节点是以该数字为下标的节点.
- 如果两个节点有共同的根节点,那么这两个节点有连接关系
方法实现
- 上面提到的Find(i,j)只需要检查两个节点是否有共同的根节点,如果有就返回true,反之返回false
- 上面提到的union(i,j)只需要将数组下标为i,j的节点各自所在的两棵树化为一棵树,总是将j所在的树的根节点变为i所在树根节点的子节点
演示(父节点-->子节点)
- 一开始各下标的数字等于下标,即每个节点都是一个单独的树
- 数组下标位置的数字表示该下标标识节点的父节点的下标
- 任意一个根节点所在位置,下标等于该下标位置的数字
- 以数组为形,以树为魂
id | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
num(存储的数字) | 0 | 1 | 2 | 3 | 4 |
graph TD
0((0))
1((1))
2((2))
3((3))
4((4))
- union(0,1)
- 将1节点所在的树的根节点变为0节点所在树根节点的子节点
id | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
num(存储的数字) | 0 | 0 | 2 | 3 | 4 |
graph TD
0((0))==>1((1))
2((2))
3((3))
4((4))
- union(3,4)
- 将4节点所在的树的根节点变为3节点所在树根节点的子节点
id | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
num(存储的数字) | 0 | 0 | 2 | 3 | 3 |
graph TD
0((0))==>1((1))
2((2))
3((3))==>4((4))
- union(1,3)
- 将3节点所在的树的根节点变为1节点所在树根节点的子节点
id | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
num(存储的数字) | 0 | 0 | 2 | 0 | 3 |
graph TD
0((0))==>1((1))
0==>3
2((2))
3((3))==>4((4))
- union(2,0)
- 将0节点所在的树的根节点变为3节点所在树根节点的子节点
id | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
num(存储的数字) | 0 | 0 | 2 | 0 | 3 |
graph TD
0((0))==>1((1))
0==>3
2((2))==>0
3((3))==>4((4))
Code by java
public class QuickFindUF {
private int[] id;//数组
public QuickFindUF(int N){
id=new int[N];//分配空间,N即为节点个数
for(int i=0;i<N;i++)
id[i]=i;//下标位置数字等于下标
}
private int root(int i){
while(i!=id[i])//下标数字不等于下标
i=id[i];//将其父节点的下标赋值给i
return i;
}
public boolean connected(int i,int j){
return root(i)==root(j);//检查数组下标为i,h的两个节点是否有相同的根节点
}
public void union(int i,int j){
int a=root(i);//获取两个下标标识的节点各自的根节点
int b=root(j);
id[a]=b;//使得b标识节点的根节点等于a标识节点的根节点
}
}
特性分析
操作 | initialization初始化 | union连接 | find查找 |
---|---|---|---|
访问数组次数 | N | N- | N(最坏情况) |
- 查找很费时
- 树会随着union操作变高,对查找不利
加权快速合并
- 加权快速合并是在快速合并基础上做的改进
- 在树的合并时遵守一条原则:总节点少的树被添加到节点多的树上(总节点少的树被当作节点多的树的根节点的子节点)
数据结构
- 使用简单的长度为N的数组来表示(N为节点个数)
- 解释:以数组下标标识每一个独立的节点,数组下标的位置上的数字表示该下标标识的节点的父节点是以该数字为下标的节点.
- 使用简单的长度为N的数组来表示权重(N为节点个数)
- 解释:以数组下标标识每一个独立的节点,数组下标的位置上的数字表示该下标标识的节点所在树的总节点的数量(只对根节点有效)初始化都为1
- 如果两个节点有共同的根节点,那么这两个节点有连接关系
方法实现
- 上面提到的Find(i,j)只需要检查两个节点是否有共同的根节点,如果有就返回true,反之返回false
- 上面提到的union(i,j)只需要将数组下标为i,j的节点各自所在的两棵树化为一棵树,总是将总节点少的树添加到节点多的树上,同时对权值数组进行改变,将总节点少的树的根节点的权值加到节点多的树的根节点的权值上
演示(父节点-->子节点)
- 一开始各下标的数字等于下标,即每个节点都是一个单独的树
- 数组下标位置的数字表示该下标标识节点的父节点的下标
- 任意一个根节点所在位置,下标等于该下标位置的数字
- 以数组为形,以树为魂
id | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
num(存储的数字) | 0 | 1 | 2 | 3 | 4 |
w | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
权值(根节点所在树的总节点数目) | 1 | 1 | 1 | 1 | 1 |
graph TD
0((0))
1((1))
2((2))
3((3))
4((4))
- union(0,1)
- w[root(0)]==w[root(1)],所以将1节点所在的树的根节点变为0节点所在树根节点的子节点
- w[root(0)]+=w[root(1)]
id | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
num(存储的数字) | 0 | 0 | 2 | 3 | 4 |
w | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
权值(根节点所在树的总节点数目) | 2 | 1 | 1 | 1 | 1 |
graph TD
0((0))==>1((1))
2((2))
3((3))
4((4))
- union(3,4)
- w[root(3)]==w[root(4)],所以将4节点所在的树的根节点变为3节点所在树根节点的子节点
- w[root(3)]+=w[root(4)]
id | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
num(存储的数字) | 0 | 0 | 2 | 3 | 3 |
w | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
权值(根节点所在树的总节点数目) | 2 | 1 | 1 | 2 | 1 |
graph TD
0((0))==>1((1))
2((2))
3((3))==>4((4))
- union(1,3)
- w[root(1)]==w[root(3)],所以将3节点所在的树的根节点变为1节点所在树根节点的子节点
- w[root(1)]+=w[root(3)]
id | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
num(存储的数字) | 0 | 0 | 2 | 0 | 3 |
w | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
权值(根节点所在树的总节点数目) | 4 | 1 | 1 | 2 | 1 |
graph TD
0((0))==>1((1))
0==>3
2((2))
3((3))==>4((4))
- union(2,0)
- w[root(2)]<w[root(0)],所以将2节点所在的树的根节点变为0节点所在树根节点的子节点
id | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
num(存储的数字) | 0 | 0 | 2 | 0 | 3 |
w | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
权值(根节点所在树的总节点数目) | 5 | 1 | 1 | 2 | 1 |
graph TD
0((0))==>1((1))
0==>3
0==>2
2((2))
3((3))==>4((4))
Code by java
public class QuickFindUF {
private int[] id;//数组描述父子节点关系
private int[] w;//数组描述根节点所在树的节点总数
public QuickFindUF(int N){
id=new int[N];//分配空间,N即为节点个数
w=new int[N];
for(int i=0;i<N;i++){
id[i]=i;//下标位置数字等于下标
w[i]=1;//初始每个节点都是根节点,即每个节点所在树的总结点数为1
}
}
private int root(int i){
while(i!=id[i])//下标数字不等于下标
i=id[i];//将其父节点的下标赋值给i
return i;
}
public boolean connected(int i,int j){
return root(i)==root(j);//检查数组下标为i,h的两个节点是否有相同的根节点
}
public void union(int i,int j){
int a=root(i);//获取两个下标标识的节点各自的根节点
int b=root(j);
if(w[a]>=w[b]){//如果i标识的节点所在树的总结点数大于j标识的节点所在树的总结点数
id[b]=a;//将j标识的节点所在的树添加到i标识的节点所在的树上
w[a]+=w[b];//将j标识的节点所在的树总节点数添加到i标识的节点所在的树的总节点数上
}else{
id[a]=b;//将i标识的节点所在的树添加到j标识的节点所在的树上
w[b]+=w[a];//将i标识的节点所在的树总节点数添加到j标识的节点所在的树的总节点数上
}
}
}
路径压缩快速合并
- 基于快速合并
- 调用root(i)时,会使得i所标识的节点指向它的祖先节点
Code只修改root函数中的一行
private int root(int i){
while(i!=id[i]){//下标数字不等于下标,即该下标标识的节点不是根节点
id[i]=id[id[i]];//令i标识的节点指向该节点的父节点的父节点
i=id[i];//将其父节点的下标赋值给i
}
return i;
}
在N个节点的连接中执行M次查找的效率
算法 | 耗时 |
---|---|
快速查找 | MN |
快速合并 | MN |
加权+快速合并 | N+M*logN |
路径压缩+快速合并 | N+M*logN |
加权+路径压缩+快速合并 | N+M* lg*N |
lg*N是指对N一直取对数直到等于1的次数
应用场景
- 图片上的像素点
- 计算机网络
- 社交网络中的关系网
- 计算机晶片中的晶体管
以上是关于动态连接的主要内容,如果未能解决你的问题,请参考以下文章