集合算法:实现D森林

一、考虑2类指令

Link(v,r): v是一棵树中的结点,r是另一棵树的根,Link的执行使得r成为v的子结点,从而实现两树的合并。

Find-Depth(w):求出结点w的当前深度。

二、D-森林的运用原因:

     如果采用前述的树结构形式,且Find-Depth指令不具路径压缩功能, 则执行O(n)条Find-Depth指令,最坏情况下时间复杂度为O(n2);但如果采用具有路径压缩功能的Find-Depth指令,则原先树中在被压缩路径上的各结点深度会发生改变,如不采取其它措施,对其中某结点执行Find-Depth指令时,就会得到错误的深度信息。如果我们给每个结点增加一个字段记录其在原树中的深度,这样,虽可在O(1)时间完成一条Find-Depth指令,但在执行Link指令时,由于以r为根的子树中结点的深度全部发生了变化,我们势必要修改该子树中所有结点的深度字段。此工作量很大,最坏情况下,O(n)条Link指令的时间复杂度也为O(n2);为了既能求得各点在原先树中的正确深度、又能使时间复杂度较小, 我们需要使用具有路径压缩功能的Find-Depth指令;同时我们还需要采取一些辅助手段来保证深度计算的正确性。为此,我们对每个结点v增加2个字段(Count[v]和Weight[v]),并把经过改造的此类结点和树所构成的森林称为D-森林

三 、D-森林的算法分析

    为减小时间复杂度,所有操作均是在D-森林中执行的,但我们必须保证它能正确反映原森林中任何结点在任何时刻的深度。

     在上例中,原先对左图中两子树执行Link(r,u)就成为了中图的形状;为使时间复杂度小,Link(r,u)在D-森林中执行后成为右图的形状。虽然形状变了,但我们只要保证在D-森林中执行Find-Depth时,一定能得到

    Find-Depth(r)=0, Find-Depth(u)=1, Find-Depth(w)=2;

    即保证Find-Depth能给出所有结点在原先森林中的正确深度即可。要实现这一点,首先需要对结点进行改造,即要增加2个字段:

    Count[v]: 记录在D-森林中以v为根的子树中结点的个数。

    Weight[v]满足下述性质:设在D-森林中执行指令Find-Depth(v)

从v找到根结点a路径上的结点是vi1(=v)vi2vi3…vik(=a), 则等于v在原树中的深度(注意不是在D-森林中的)。(如在上例中,应有Weight[u]=1, Weight[r]=-1, Weight[w]=1,这样就有, Depth(r)= Weight[r]+Weight[u]=0;

Depth(u)= Weight[u]=1;Depth(w)= Weight[w]+Weight[u]=2。)

请注意,初始时即任何Link指令尚未执行(合并)时,不论是在原森林还是在D-森林中,所有结点均为单结点树,故所有结点的Weight值均为0即其深度为0。

(1)Find(i,w,p,Weight)算法

    为了保证Weight值所具的性质, 当在D-森林中执行Find指令实施路径压缩时,(即把vi1、vi2、vi3、…vi,k-2变为根结点a(即vik)的子结点时,另外,请注意vi,k-1原本就是根结点a的子结点)要使每一个结点vip (1≤p≤k-2)的新权变为,也就是:

 vip的新Weight值= vip的旧Weight 值+vip原父结点的新Weight值

Find(p, i, w, Weight):

  if ip[i] then {

p[i]Find(p, p[i], father_new_weight, Weight);

                     Weight[i]Weight[i]+father_new_weight;

                     wWeight[i];

        }

  else  w0;

  return p[i]

}

 

(2)Find-Depth(v)算法

      Find-Depth要给出结点vi在原先未经压缩的森林中的深度Depth(i)。有了经过修改的Find指令,构造Find-Depth(i)指令就很容易了:要计算Depth(i),首先看在D-森林中vi是否为根,即是否有i=p[i],若vi为根,则Weight[i]就是Depth(i);若vi不为根则调用Find(i,w),不论原先vi是否为根a的儿子,执行Find(i,w)后vi一定是根的儿子,  而此时的Depth(i)为Weight[a] +新Weight[i]。

 

(3)Link(v,r)算法

     Link指令仍沿用同一名称,但在D-森林中的执行与在原森林不同,目的仍然是为了减少执行的时间复杂度。D-森林中Link指令的执行要保证原森林中的各点深度能正确计算。

仍以前述图为例

     设左上图是Link执行前原森林、D-森林的形态(假定恰好相同)。如Link(r,u)在原森林中执行,则应为上图中间的形态;但Link(r,u)在D-森林中执行后,r,u,w三结点组织结构如右上图所示。

    之所以这样处理,其目的是为了减小D-森林中树的高度,这样在执行Find-Depth指令时,所处理的路径长度也就相应短。

由于在原森林中r的深度为0,u的深度为1,w的深度为2,故Link(r,u)在D-森林中执行后,三结点的Weight值应为:Weight[u]=1, Weight[r]=-1, Weight[w]=1。注意这里Weight[u]¹0,即在D-森林中,根的Weight值不一定就是0。另外还需注意的是,

        D-森林中的根在原森林中不一定就是根(如u),

原森林中的根在D-森林中也不一定就是根(如r)。

Link(v,r) 的执行:

按原始定义, 在原森林中, v是一棵树中的结点, r是另一棵树的根,Link的执行使得r成为v的子结点,从而实现两树的合并。在D-森林中,把含有v和r的2棵树分别记为Tv和Tr。要注意的是,在D-森林中r不一定是Tr的根,如右上图;但r也有可能是Tr的根,只是执行时无法预测倒底是哪种情况。

在D-森林中执行Link时,并不直接把r做成v的子结点,(在实际执行时,按下述规则有可能碰巧出现这样的情况)而是把结点少的树接到结点多的树的根下,实现两树的合并,同时修改所有应发生变化的结点的Weight值,从而使该树能够正确反映在原森林中把r接在v之下时各结点的深度。

    设在D-森林中,Tv和Tr的根分别是v’和r’,合并时按Count[v’]和Count[r’]值的大小分为两种情况:

   Count[r’] ≤ Count[v’]和Count[v’]< Count[r’]。

   若在D-森林中Count[r’] ≤ Count[v’]则v’作为根, 反之则r’作为根。

1、Count[r’] ≤ Count[v’]

因为要把结点少的树并入结点多的树,故把r’作为v’的儿子。

         

注意到:Link指令在原森林中执行的目标,就是把r接到v之下;而在原森林中,此种接入对含有v的树中各结点深度不产生任何影响;在D-森林中,现在把Tr接在v’之下,与合并前相比,Tv中的任一条路径也没有受到此接入的任何影响,故在D-森林中,当Count[r’] ≤ Count[v’]即Tv中的结点多于中Tr的结点时, Link执行时Tv树中各结点的Weight值无需修改。但合并时,为使Tr树中各结点的Weight值继续保持其应有的性质,要对Weight[r’]进行修改。

   分析:设用Find-Depth指令可以查到v的深度为Depth(v),(注意Depth(v)是执行Find-Depth(v)指令的返回值而不是函数) 而在原先的森林中,由于r是根,故原先有Depth(r)=0(也是值)。执行Link指令后,把r作为v的儿子,

则此时在原先的森林中,r的深度为Depth(v)+1。

    设在D-森林中,r’的儿子是s。由Weight的性质,在D-森林中的2棵树合并之前,有:从r到s路径上各结点的权和+旧Weight[r’]=0(r在原森林中的深度)

    按上述分析,在原森林中的2棵树合并之后, r的深度为Depth(v)+1; 另一方面,D-森林中对应的2棵树合并后,按Weight的性质,r在原森林中的深度为:从r到s的权和+新Weight[r’]+Weight[v’],因此有从r到s的权和+新Weight[r’]+Weight[v’]= Depth(v)+1。

    联立上述两兰色式子可得:

     新Weight[r’]=Depth[v]+1-Weight[v’]+旧Weight[r’]。

Tr中的其它各结点的Weight值均无需改变。这是由于Tr中的其它各结点相对于r’的位置在合并后均未改变,故只要r’的新Weight值正确,则Tr中的其它各结点就都能够正确计算出其在原森林中的深度。(更确切地, 设j是Tr中的任一结点, j’是j至r’路径上r’的子结点,合并前,j在原森林中的深度即j相对于r的深度(因r是根),故有j相对于r的深度=j至j’路径上的权值之和+旧Weight[r’];而合并后,j在原森林中的深度为:j相对于r的深度+Depth(v)+1,此值应等于Weight[v’]+新Weight[r’]+ j至j’路径上的权值之和,联立两式可知,利用新Weight[r’],就能正确计算Tr中各结点的深度。)

         

2、Count[v’]< Count[r’]时,把v’作为r’的儿子。合并后应有:从r到s的权和+新Weight[r’]= Depth(v)+1。故有新Weight[r’]= Depth(v)+1+旧Weight[r’],Tr中的其它各结点的Weight值均无需改变,理由与上小节类似。

另外,Weight[v’]也要修改。设在D-森林中,v’的儿子是t,

则合并前有:从v到t的权和+旧Weight[v’]=Depth(v) (v在原森林里的深度)。

而合并以后应有:从v到t的权和+新Weight[v’]+新Weight[r’]=Depth(v)

(合并时是将r接到v之下,故v在原森林里的深度并不改变。)

    2式联立得:新Weight[v’]=旧Weight[v’]-新Weight[r’]。

不论哪一种情况,Weight值修改完后,都要将新根的Count值改为Count[v’]+Count[r’]。另外,在Link程序中需知道v’和r’, 且要用到Depth(v), 以便修改Weight值和Count值。故在Link(v,r)程序中,要执行Find-Depth(v)(以计算出Depth(v)并找到Tv的根v’)、以及改造后的Find(r,w)(以找到Tr的根r’并压缩时修改权值)。

四、作业

    设初始时两森林均由单结点树构成。上机依次执行:Link(2,1), 

Link(3,2), Link(5,4), Link(4,3), Link(7,6),Link(9,8), Link(8,7), 

Find-Depth(6), Link(6,5), Find-Depth(4), Find-Depth(7)。 

程序代码:

//head.h文件一  类的定义

#include<iostream>

using namespace std;

#ifndef TreeNode_H

#define TreeNode_H

 

class TreeNode

{

  public:

     int count;     //以这个点为根的子结点数

     int weight;    //结点权值

     int father;    //结点的父结点

     int name;      //结点名

     static int number; // 结点数

 TreeNode();      //TreeNode类的构造函数

 void Display();  //显示函数

 int Find(TreeNode P[],int i,int& lastweight); //寻找根节点

         int Loop(TreeNode B[],int k);  //查根与结点的关系

 int Find_Depth(TreeNode D[],int m,int& root); //计算结点的深度

 void Link(TreeNode C[],int nodev,int noder);  //结点的连接

};

#endif

//End of file head.h

 

//main.cpp文件而二 主函数

#include<string.h>

#include<stdlib.h>

#include<stdio.h>

#include<iostream>

#include<windows.h>

using namespace std;

#include"head.h"

int TreeNode::number=0;

 

int main()

{

    TreeNode Dtree;      //定义类TreeNode的对象Dtree

TreeNode example[6];  //定义类TreeNode的对象数组example[6]

/*****************给对象数组预设初值****************/

example[1].father=1;  

example[1].weight=1;

example[2].father=1;

example[2].weight=-1;

example[3].father=1;

example[3].weight=1;

example[4].father=2;

example[4].weight=3;

example[5].father=4;

example[5].weight=1;

system("color FC");

int root;

int Depth;

for(int i=1;i<6;i++)   //显示树的结点数值

{

root=Dtree.Loop(example,i);

example[i].Display();

cout<<"  root="<<root<<endl;

}

cout<<endl;

for(int j=1;j<6;j++)    //寻找Dtree结点的深度算法

{

Depth=Dtree.Find_Depth(example,j,root);

cout<<"结点"<<j<<"  的深度"<<Depth<<"  Dtree根为"<<root<<""<<endl;

}

//Link指令

TreeNode::number=0;

TreeNode D_tree[10];   //定义类TreeNode的对象数组D_tree[10]

cout<<endl<<"建一个九个点的D森林D_tree[10]"<<endl; 

Dtree.Link(D_tree,2,1);    

Dtree.Link(D_tree,3,2);

Dtree.Link(D_tree,5,4);

Dtree.Link(D_tree,4,3);

Dtree.Link(D_tree,7,6);

Dtree.Link(D_tree,9,8);

Dtree.Link(D_tree,8,7);

cout<<"运行Link(D_tree,2,1);Link(D_tree,3,2);"<<endl

<<"Link(D_tree,5,4);Link(D_tree,4,3);Link(D_tree,7,6);"<<endl

<<"Link(D_tree,9,8);Link(D_tree,8,7);之后"<<endl;

Depth=Dtree.Find_Depth(D_tree,6,root);

cout<<"此时结点6的深度为 "<<Depth<<" 根为"<<root<<endl;

    Depth=Dtree.Find_Depth(D_tree,4,root);

cout<<"此时结点4的深度为 "<<Depth<<" 根为"<<root<<endl;

Dtree.Link(D_tree,6,5);

Depth=Dtree.Find_Depth(D_tree,4,root);

cout<<endl<<"再执行完Link(D_tree,6,5)时结点4的深度为 "

<<Depth<<" 根为"<<root<<endl;

Depth=Dtree.Find_Depth(D_tree,7,root);

cout<<"此时结点7的深度为 "<<Depth<<" 根为"<<root<<endl;

cout<<endl<<"D_tree的连接的算法后的结果:"<<endl;

    //输出各结点的深度与根节点

for(int k=1;k<10;k++)

{

Depth=Dtree.Find_Depth(D_tree,k,root);

cout<<"结点"<<k<<"的深度是"<<Depth<<"  根为"<<root<<endl;

}

return 0;

}

//End of file main.cpp

 

//Dtree.cpp文件 类的函数实现

#include"head.h"

TreeNode::TreeNode()  //对各结点进行初始化

{

count=0;      

weight=0;

father=number;

name=number;

number++;

}

 

void TreeNode::Display()  //显示函数

{

cout<<"结点"<<name-1<<" count="<<count<<"   weight="<<weight;

}

 

//寻找根节点的Find递归调用方法

int TreeNode::Find(TreeNode P[],int i,int& lastweight)

{

if(P[i].father!=i)

{

P[i].father=Find(P,P[i].father,lastweight);

P[i].weight+=lastweight;

lastweight=P[i].weight;

return P[i].father;

}

else

{

lastweight=0;

return i;

}

}

 

//循环的调用寻找根与各结点的关系

int TreeNode::Loop(TreeNode B[],int k)//

{

int sum=0,root,accumulate=0,start=k;

while(B[k].father!=k)//

{

sum+=B[k].weight;

k=B[k].father;

}

root=k;

k=start;

while(B[k].father!=k)//

{

int temp=B[k].weight;

B[k].weight=sum-accumulate;

accumulate+=temp;

temp=B[k].father;

B[k].father=root;

k=temp;

}

return root;

}

 

//求取树结点m的深度和根root

int TreeNode::Find_Depth(TreeNode D[],int m,int& root)

{

if(D[m].father==m)

{

root=m;

return D[m].weight;

}

root=Loop(D,m);

return D[m].weight+D[D[m].father].weight;

}

 

//Link指令:nodev是一棵树中的结点,noder是另一棵树的根,

//Link的执行使得noder成为nodev的子结点,从而实现两树的合并。

void TreeNode::Link(TreeNode C[],int nodev,int noder)

{

int rootv=Loop(C,nodev);

int rootr=Loop(C,noder);

int countv=C[rootv].count;

int countr=C[rootr].count;

int n;

if(countv<=countr)

{

C[rootr].weight=Find_Depth(C,nodev,n)+1-C[rootv].weight+C[rootr].weight;

C[rootv].count+=C[rootr].count+1;

C[rootr].father=rootv;

}

else

{

C[rootr].weight=C[rootr].weight+Find_Depth(C,nodev,n)+1;

C[rootv].weight=C[rootv].weight-C[rootr].weight;

C[rootr].count+=C[rootv].count+1;

C[rootv].father=rootr;

}

}

//End of file Dtree.cpp

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值