代码改变世界

《算法导论》第14章 数据结构的扩张 (1)动态顺序统计

2012-04-08 17:57  htc开发  阅读(223)  评论(0编辑  收藏  举报


《数据结构扩张》是《算法导论》第三部分的最后一章。在介绍学习了这么多种数据
结构之后,简要介绍了当这些基本数据结构不满足需求时,如何扩张它们来满足需求。
这才是学习算法的目的,能够根据需求选择合适的数据结构和算法,并在无法满足需求
时能够扩张它。这才是对算法的思想和本质的学习!

可以将本章看做深入学习的前奏吧,因为紧接着就要开始进入第四部分《高级设计和分析
技术》了。那么赶快来看看如何扩张数据结构,然后就进入高级部分的学习吧!


1.如何扩张数据结构?

1)选择基础数据结构
2)确定要在基础数据结构中添加哪些信息
3)验证可用基础数据结构上的基本操作来维护新添加的信息
4)设计新的操作

下面来看一个简单的数据扩张的例子 - 动态顺序统计树。《算法导论》中选用红黑树
作为基础数据结构,这里为了简单,用最简单的二叉查找树来实现。这样我们可以集中
注意力在数据结构扩张的方法上。


2.动态顺序统计树

在第九章《中位数和顺序统计》中,我们曾研究过如何求数组中的元素的顺序统计量。
如查找最大值、最小值、第 i 小的值等等。其中求第 i 小元素时采用了随机快速排序的
RANDOMIZED-PARTITION划分方法,从而达到了Θ(n)的期望运行时间。

本节要研究的是动态顺序统计量,与之前的有些类似,首先我们来看面对这个需求,
如何选择合适的数据结构,并在必要时进行简单的扩张。

1)选择基础数据结构

我们面对的需求是维护动态集合,即数据可能会频繁的插入和删除,固定长度的数组
显然不适合这种情形。所以我们可以选择二叉查找树作为基础数据结构,但基本的
二叉查找树实现顺序统计功能是很低效的,所以还要添加新的域来扩张它。

2)确定要在基础数据结构中添加哪些信息

我们选择在每个结点添加一个新的size域,来保存每棵子树的大小(包含的结点树,包括
此根结点自身)。更自然的想法是新加一个rank域,直接保存每个结点的顺序统计量,
但这样插入和删除时可能会麻烦一些,参见习题14.1-6。

3)验证可用基础数据结构上的基本操作来维护新添加的信息

如前所述,添加size而不是rank域是为了只对原有数据结构略作改动,就足以维护附加信息,
这是比较理想的情况。如果添加rank域,当插入一个最小元素时,所有结点的rank域都要变。

4)设计新的操作

新的操作就是我们需求中所需的,OS-SELECT求顺序统计量为 i 的结点和OS-RANK求一个
结点的顺序统计量。


3.实现源码

下面来看实现代码,这里只列出BST实现的改动部分,着重来看新加的操作和对BST的影响。



4.运行情况详细分析

现在以查找第 i = 17小的元素来看OS-SELECT的执行过程。


    |                                                                                                                                                    |
    |                                                                                                                                                    |
[  3, 7,  10, 12, 14,14, 16, 17, 19,   20,21,      21, 26, 28,       30,35, 38, 39,41,        47]

1.从根结点26开始,其顺序统计量 r = 26左子结点size + 1 = 13 < i,则递归OS-SELECT(26右子结点, i - r),
即OS-SELECT(结点41,4)。

[  3, 7,  10, 12, 14,14, 16, 17, 19,   20,21,      21, 26, 28,       30,35, 38, 39,41,        47]

2.根结点变为41,i = 4,r = 5 + 1 = 6 > i,递归OS-SELECT(41的左子结点, i ),即OS-SELECT(结点30, 4)。

 3, 7,  10, 12, 14,14, 16, 17, 19,   20,21,      21, 26, 28,       30,35, 38, 39,41,        47]

3.根结点变为30,i = 4,r = 1 + 1 = 2 < i,递归OS-SELECT(结点38, 2)。

[  3, 7,  10, 12, 14,14, 16, 17, 19,   20,21,      21, 26, 28,       30,35, 38, 39,41,        47]

4.根结点变为38,i = 2,r = 1 + 1 = 2 == i,找到了!结点38即为顺序统计量17的元素。

 3, 7,  10, 12, 14,14, 16, 17, 19,   20,21,      21, 26, 28,       30,35, 38, 39,41,        47]

看到OS-SELECT的实现了吧,跟第九章的RANDOMIZED-SELECT多么相似。从根结点开始遍历,
首先计算根节点的顺序统计量设为r,若比 i 小则继续处理根节点的左子树,否则处理右子树。
我们可以将每次递归处理的根节点node看做RANDOMIZED-PARTITION返回的划分元素A[q],
若 i 比A[q]的顺序统计量小则继续处理较小的数组部分,否则处理大的那部分。如出一辙!