在编译器的实现等应用中需要用到上述两类指令.
问题是 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- 森林 。
一、问题说明
问题描述
编写能实现上述功能的Link(v,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) 。 打印或抄写每条指令执行后各点的Weight 值,画出其时的两种森林。
二、程序功能说明 以程序演示
程序功能说明
find_depth(i) 主要是找到 D 森林根节点编号和 i 节点深度
link(v, r) 将以 r 链接到 v ,合并两棵树
程序演示结果
输入 : dtree.link(2, 1);
dtree.link(3, 2);
dtree.link(5, 4);
dtree.link(4, 3);
dtree.link(7, 6);
dtree.link(9, 8);
dtree.link(8, 7);
dtree.find_depth(6)
dtree.find_depth(4)
dtree.find_depth(7)
输出结果:(注意下,这里的深度表示原森林的深度)
6 的深度: 3
weight[1]:1 weight[2]:7 weight[3]:-1 weight[4]:1 weight[5]:-3 weight[6]:3 weight[7]:2 weight[8]:1 weight[9]:-7
4 的深度: 5
7 的深度: 2
深度 权值
三、 算法设计
find_depth(i) 返回 D 森林根节点编号和 i 节点深度 ,它 的实现如下所示,要注意几点:
1. 如果是根节点 , 一个参数是 D 森林根节点编号,另一个为 weight 为 0
2. i 的深度 =weight[i] + i 的父节点的 weight + i 的父节点的父节点的 weight+ ... + 一直到 D 森林根节点
3. 第一次 find 需要经过路径压缩 O(n) 次需要 O(n*G(n))
// 返回D森林根节点编号和i节点深度 private Pair find_depth(int i) { if (i == point[i]) // 如果是根节点,一个参数是D森林根节点编号,另一个为weight为0 return new Pair(i, 0); // 返回根节点的编号和i节点的新权值 Pair pair = find1(i); // 获得根节点编号 int rootId = pair.first; // 获得i节点的新权值(=weight[i] + i的父节点的weight + i的父节点的父节点的weight+ ... + // D森林k-1节点) int iNewWeight = pair.second; // 返回根节点编号和i节点深度 // 深度=weight[i] + i的父节点的weight + i的父节点的父节点的weight+ ... + 一直到D森林根节点 // 第一次find需要经过路径压缩O(n)次需要O(n*G(n)) return new Pair(rootId, iNewWeight + weight[rootId]); }
link(v, r) 将以 r 链接到 v ,合并两棵树, 它 的实现如下所示,但同样也要注意几点:
1. 首先要获得 D 森林 v 的根节点编号和 D 森林 r 的根节点编号以及 v 节点深度
2. 如果 count[rootOfV ] >= count[rootOfR] 则将 r 所在 D 森林中的根 rootOfR(r') 指向 v 所在 D 森林中的根 rootOfV(v')
3. 新 weight[rootOfR]- 旧 weight[rootOfR]+weight[rootOfV]=depth(v) +1
4. 如果 count[rootOfV ] < count[rootOfR] 则 v’ 接到 r’ 上
5. 需要修改两处地方: 新 weight[rootOfR] - 旧 weight[rootOfR] = depth(v) + 1 和旧 weight[rootOfV] + 新 weight[rootOfV] + weight[rootOfR] = depth[v]
// link(v, r) 将以r链接到v,合并两棵树 public void link(int v, int r) { // 返回D森林v的根节点编号和v节点深度 Pair pairV = find_depth(v); // 返回D森林r的根节点编号和r节点深度 Pair pairR = find_depth(r); // D森林v的根节点编号 int rootOfV = pairV.first; // D森林v节点深度 int depthV = pairV.second; // D森林r的根节点编号 int rootOfR = pairR.first; // D森林r节点深度 int depthR = pairR.second; if (count[rootOfV] >= count[rootOfR]) { count[rootOfV] += count[rootOfR]; // r接到v上时候深度发生变化因此需要修改D森林r的根的权 // -旧weight[rootOfR] + 新weight[rootOfR] + weight[rootOfV] = depth(v) + 1 weight[rootOfR] = depthV + 1 + weight[rootOfR] - weight[rootOfV]; // 将r所在D森林中的根rootOfR(r')指向v所在D森林中的根rootOfV(v') point[rootOfR] = rootOfV; } else { count[rootOfR] += count[rootOfV]; // r接到v上时候深度发生变化因此需要修改D森林r的根的权 // 新weight[rootOfR] - 旧weight[rootOfR] = depth(v) + 1 weight[rootOfR] = depthV + 1 + weight[rootOfR]; // D森林中,rootOfV(书中是v')的根发生变化,变为rootOfR(书中是r') // 旧weight[rootOfV] + 新weight[rootOfV] + weight[rootOfR] = depth[v] weight[rootOfV] = weight[rootOfV] - weight[rootOfR]; // 将v所在D森林中的根rootOfV(v')指向r所在D森林中的根rootOfR(r') point[rootOfV] = rootOfR; } }
四、实验结果分析
注意一下,在实现 find_depth 的时候使用到了 find , find 实施带权路径压缩,我们这里使用递归的形式, O(n) 次指令的时间复杂度为 O(n*G(n))
对于实验结果,我们可以通过验证的方式来看是否正确, 6 、 4 、 7 的深度分别为 3 、 5 、 2 这是正确的,因为原树是以 9 为根,然后其他节点是链式连接的,如下图所示
对于权值我们可以这样验证,最后得出的 D 森林如下所示:(执行到 Link(6, 5) 时,当然如果继续往下执行的话,权值又会变化,因为路径会继续压缩)
比如说 6 节点,因为 6 节点在原森林的深度为 3 ,而从图中可以看出 6 的权值 +9 的权值 +2 的权值 =3+-7+7 = 3 因此,是正确的,同理,可以验证其他节点的情况。
五、“编”后感
算法侧重于时间和空间,该程序主要时间花费是在 find_depth 和 link ,各自的时间复杂度都为 O(n*G(n)) ,另外就是验证程序,只需要验证程序的权值累加起来是否等于原森林的深度即可。要注意一点的是压缩后的权值和没有压缩后的权值是不一样的。
全部代码下载:http://download.csdn.net/source/2936858