并查集
并查集能很好地解决网络中两个节点是否连接的问题,但并查集不会给出节点之间的路径。正因为如此,并查集在判断节点的连通性时效率很高。其时间复杂度为O(1)。
并查集实现
思路:
- 相连的节点具有相同的id值
- 要实现p,q两个节点的合并,就需要将所有与p节点id值相同的节点的id改为q的id。
function unionFind(n){
this.id = [];
for (var i = 0; i < n; i++) {
//初始化时,每个节点的id值都不相同
this.id[i] = i;
}
this.find = function(p){
if (p >= 0 && p < this.id.length) {
return this.id[p];
}
}
this.isConnected = function(p,q){
return this.find(p) == this.find(q);
}
this.unionElements = function(p,q){
var pId = this.id[p];
var qId = this.id[q];
if (pId == qId) {
return;
}
for (var i = 0; i < this.id.length; i++) {
if (this.id[i] == pId) {
this.id[i] = qId;
}
}
}
}
并查集优化(1)
思路:
- 为每个元素指定父节点, 初始化时每个元素的父节点都指向自身
- 两个元素相连可以看做是一个元素是另一个元素的父节点
function unionFind2(n){
this.parent = [];
for (var i = 0; i < n; i++) {
//初始化时父节点等于自身
this.parent[i] = i;
}
this.find = function(p){
if (p < 0 || p >= this.parent.length) {
return -1;
}
//获得元素p的根节点
while(parent[p] != p ){
p = parent[p];
}
return p;
}
this.isConnected = function(p,q){
//两个元素根节点相等即代表相连
return this.find(p) == this.find(q);
}
this.unionElements = function(p,q){
int pParent = find[p];
int qParent = find[q];
if (pParent == qParent) {
return;
}
//将一个元素的父节点设置为另一个元素,则代表两个元素相连
this.parent[pParent] = qParent;
}
}
并查集优化(2)
思路:
- 如果节点之间相连所形成的树层数太高的话,查到元素的根节点所耗费的时间会增加
- 增加一个数组,用于记录以每个节点为根节点的树中子节点的数量
- 每次合并元素之前,先判断一下待合并的元素所在的根节点哪个节点数量多,将数量少的根节点的父节点设置为数量多的根节点,并维护子节点数量数组
function unionFind2(n){
this.parent = [];
this.size = [];
for (var i = 0; i < n; i++) {
this.parent[i] = i;
//初始化时,每个节点的根节点都是自身,因此,size数量都为1
this.size[i] = 1;
}
this.find = function(p){
if (p < 0 || p >= this.parent.length) {
return -1;
}
while(parent[p] != p ){
p = parent[p];
}
return p;
}
this.isConnected = function(p,q){
return this.find(p) == this.find(q);
}
this.unionElements = function(p,q){
int pParent = find[p];
int qParent = find[q];
if (pParent == qParent) {
return;
}
//合并时判断哪个分支数量多,将数量少的父节点设置为数量多的
if (this.size[pParent] > this.size[qParent]) {
this.parent[qParent] = pParent;
this.size[pParent] += this.size[qParent];
}else{
this.parent[pParent] = qParent;
this.size[qParent] += this.size[pParent];
}
}
}
并查集优化(3)
思路:
- 用节点数量判断应该怎样连接两个分支是不准确的,因为节点数量不能代表一个分支的节点层数
- 用节点层数来判断怎样合并两个分支更加合理
function unionFind2(n){
this.parent = [];
//rank表示以每个节点为根节点的分支树的高度
this.rank = [];
for (var i = 0; i < n; i++) {
this.parent[i] = i;
//初始化时每个分支的高度都为1
this.rank[i] = 1;
}
this.find = function(p){
if (p < 0 || p >= this.parent.length) {
return -1;
}
while(parent[p] != p ){
p = parent[p];
}
return p;
}
this.isConnected = function(p,q){
return this.find(p) == this.find(q);
}
this.unionElements = function(p,q){
int pParent = find[p];
int qParent = find[q];
if (pParent == qParent) {
return;
}
//将层数少的分支的父节点设置为层数大的分支的父节点,此时,分支层数不会发生变化
if (this.rank[pParent] > this.rank[qParent]) {
this.parent[qParent] = pParent;
}else if(this.rank[pParent] < this.rank[qParent]){
this.parent[pParent] = qParent;
} else{
//当两个分支层数相同时,连接在一起之后,其中一个分支的层数将+1
this.parent[pParent] = qParent;
this.rank[qParent] += 1;
}
}
}
并查集优化(4)
思路:
- 每一个分支的层数越小,查询的效率越高
- 在find寻找某一元素的根节点的过程中,可以同时对分支的路径进行压缩,减少分支层数
function unionFind2(n){
this.parent = [];
this.rank = [];
for (var i = 0; i < n; i++) {
this.parent[i] = i;
this.rank[i] = 1;
}
this.find = function(p){
if (p < 0 || p >= this.parent.length) {
return -1;
}
while(parent[p] != p ){
parent[p] = parent[parent[p]];//路径压缩
p = parent[p];
}
return p;
}
this.isConnected = function(p,q){
return this.find(p) == this.find(q);
}
this.unionElements = function(p,q){
int pParent = find[p];
int qParent = find[q];
if (pParent == qParent) {
return;
}
if (this.rank[pParent] > this.rank[qParent]) {
this.parent[qParent] = pParent;
}else if(this.rank[pParent] < this.rank[qParent]){
this.parent[pParent] = qParent;
} else{
this.parent[pParent] = qParent;
this.rank[qParent] += 1;
}
}
}
并查集优化(5)
思路:
- 路径压缩是减少分支层数的有效方法,最理想的路径压缩效果是:在每个分支中,所有子节点的父节点都为根节点,即分支的层数为2。
function unionFind2(n){
this.parent = [];
this.rank = [];
for (var i = 0; i < n; i++) {
this.parent[i] = i;
this.rank[i] = 1;
}
this.find = function(p){
if (p < 0 || p >= this.parent.length) {
return -1;
}
//第二种路径压缩方法,通过递归的方式将所有节点的父节点都设置为根节点
if (this.parent[p] != p ) {
this.parent[p] = find(this.parent[p]);
}
return this.parent[p];
}
this.isConnected = function(p,q){
return this.find(p) == this.find(q);
}
this.unionElements = function(p,q){
int pParent = find[p];
int qParent = find[q];
if (pParent == qParent) {
return;
}
if (this.rank[pParent] > this.rank[qParent]) {
this.parent[qParent] = pParent;
}else if(this.rank[pParent] < this.rank[qParent]){
this.parent[pParent] = qParent;
} else{
this.parent[pParent] = qParent;
this.rank[qParent] += 1;
}
}
}