您的位置:首页 > 理论基础 > 数据结构算法

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

2015-02-02 10:51 351 查看
动态顺序统计树(Orderly Statistic Tree)

在一个包含n个元素的集合中,第i个顺组统计量就是该集合的第i小关键字,这里的i我们称为秩。在一个无序的集合中,确定任意一个统计量都能在O(n)的时间内找到。现在,我们介绍一种更快速的方法,利用对红黑树,作出一些修改和扩张,使得任意统计量的查找耗时降为O(lgn),这样的一棵树称之为顺序统计树。

顺序统计树的节点中增加了一个域size,它记录了在以本节点为根的子树中节点的数量(包括该节点本身),定义哨兵nil的size域为0 。对于节点x,它的size域的计算如下:

size[x] = size[left[x]] + size[right[x]] + 1;

下图是一棵顺序统计树的例子





1、检索确定秩的元素

给定一个确定的秩i,过程select查找出秩为i,即该树中第i小的节点,返回指向该节点的指针,算法如下:

select(x, i)
{
r < -left[x] + 1;
if (i == r) return x;
else if (i < r)
return select(left[x], i);
else return select(right[x], i - r);
}

算法select中,第三行先计算当前节点x的秩r,然后与给定的秩i进行比较,如果相等,那么说明x即为要找的目标;如果i < r,则继续在left[x]中递归寻找;若i > r,则需要在right[x]上查找,此时需要查找的节点的在右子树上的秩为i-r,即是右子树上第(i - r)小的元素。

结合上图,我们执行以下这个过程select(T,17)。第一趟,计算出关键字为26的秩为13,比17小,那么所找节点肯定在右子树,且在此子树的秩为4;第二趟,计算出根的右子树中关键字41的秩为6,大于4,那么所找节点必定在其左子树;第三趟,计算出关键字41的左孩子30的秩为2,小于4,那么所找节点必定在该关键字节点的右子树中的第(4 - 2)位置上;第四趟,计算出关键字为38的节点的秩为2,两者相等,说明已找到,返回该节点指针,查找结束。

select过程和普通的查找locate一样,每进行一次,就下降一层,最终要么找到,要么指向nil,结束。所以时间复杂度为O(lgn),与树高成正比。

2、给定一个元素,确定它的秩

给定一个指向树中某节点的指针,确定该节点在树中的秩,先给出算法

rank(x)
{
r <- size[left[x]] + 1;
while (parent[x] != nil)
{
if (x == right[parent[x]])
r <- size[left[parent[x]]] + 1 + r;
x <- parent[x];
}
return r;
}

该算法的执行是从局部到整体,从子树到整棵树推进的:一开始时,在当前以该节点,也就是x为根的子树中计算出x的秩,然后如果该节点不是整棵树的树根且其为其父节点的右孩子,那么在以其父节点为根的子树中,x的秩还要加上其兄弟子树的size,外加父节点本身,如下图关键字47的秩;如果其为其父节点的左孩子,那么在以其父节点为根的子树中,x的秩没变,那么只需修改指针,继续向上一层推进,直至到根。while循环中每执行一次,就向上推进一层,因此时间为O(lgn)。



3、对子树规模的维护

在每个节点增加了size域后能够快速确定顺序统计信息,那么,在插入和删除过程中如何对这个size域进行维护呢?毕竟,如果不能得到有效的维护,是不可能达到所期望的目标的。改变树的结构的操作只有插入、删除以及旋转,接下来我们就着手讨论这三个方面如何对size进行维护。

插入

我们知道,对于红黑树的插入,分为两个阶段。第一个阶段是自顶向下搜寻找到插入位置,然后在此处插入节点;第二阶段是从该节点开始自底向上调整该路径相关节点以保持红黑性质,其中会涉及到旋转。

那么在第一阶段的搜寻过程中,对每个被遍历过的节点,其size域都增加1,因为这个新插入节点最终会是它们的子孙。这个过程时间复杂度为O(lgn)。

在第二阶段调整中,改变红黑树的仅仅只会是旋转,且最多旋转两次,另外旋转是一种局部操作,只涉及两个节点,时间为O(lgn),对于左旋,我们只需要在代码中增加下述两行即可:

size[y] <- size[x];
size[x] <- size[left[x]] + size[right[x]] + 1;

下图显示了旋转过程中size域是如何改变的,右旋与此对称。



删除

删除操作同样包括两个阶段,第一阶段自顶向下确定删除节点时,可以对每个被遍历的节点的size域减1,因为它们的某个子孙将要被删掉了;在第二阶段的调整中,也只有旋转会改变树的结构,时间为O(1)

所以,我们能够在不增加插入和删除渐进时间的前提下,有效地维护size域。

对红黑树的扩张

当红黑树被选为基础数据结构进行扩张时,对于某些类型的信息可以在插入和删除过程中进行有效的维护。下面的定理说明了在什么样的情况下可以达到这个目的。



该定理的证明可以参考《算法导论》,下面我们通过习题14.2-4来加深对这个定理的理解和运用。



借用下图,我们来做出证明。



证明过程如下图所示:



很明显可以看出,size域就满足题目条件。这是本定理的一个运用。

习题 14.1-3

参考下面将会给出的代码select

习题 14.1-4

参考下面将会给出的代码keyRank

习题 14.1-5

参考下面将会给出的代码I_successor

习题 14.1-6

顺序统计树的节点中增加rank域,用以存储当前节点的秩。

插入时,如果当前节点较大,要往左走,那么将当前节点的rank增加1,否则不变;

删除时,鉴于删除节点不一定就是我们找到的节点,可能实际删除的是其后继,因此我们可以再找到该节点后,在自底向上进行修改。如果当前节点是其父节点的左孩子,则将父节点的rank减1,否则不变,直至到根。

左旋时,参考上图14-2,rank[x]不变,rank[y] <- rank[x] + rank[y];

右旋时,rank[y]不变,rank[x] = rank[x] + rank[y].

习题 14.1-7
解答:在搜寻插入位置过程中,当待插入的关键字比当前关键字要小,即将要向左搜寻插入位置时,累加此节点以及其右子树的size;如果大于当前节点,则不管,继续搜寻;不断进行下去,直到红黑树建立完毕。每次搜寻插入位置时间为O(lgn),总共n个节点,故时间为O(nlgn)。这个累加值就是该数组的逆序数。

习题 14.1-8

【转】参考此解答

习题 14.2-4
见上面

习题 14.2-5
实际上,本题的意思就是输出[a,b]范围内的关键字,我们可以将中序遍历稍作修改即可,算法如下:

RB - ENUMERATE(x, a, b)
{
curr < -x;
if (curr != nil)
{
if (a <= key[curr] <= b)
{//如果当前关键字依然在[a,b]内,则递归左右子树
RB - ENUMERATE(left[curr], a, b);
print(curr);
RB - ENUMERATE(right[curr], a, b);
}
else if (key[curr] < a)//若小于a,则说明已到达最左端,只需递归右子树
RB - ENUMERATE(right[curr], a, b);
else RB - ENUMERATE(left[curr], a, b);//同上理
}
}

采用中序遍历,首先得不断找到大于a的最小节点,这个时间为O(lgn),和树高成正比;然后开始依次输出,直到遇到第一个大于b的节点结束,该节点不输出,输出过程时间为O(m),故最终运行时间为O(m + lgn)。

下面给出该顺序统计树的C++实现代码:

/*****顺序统计树,是一般红黑树的扩张
*增加了size域,存储以当前节点为根的统计树的规模
*增加了rank,keyRank,select以及I_successor四个成员函数
*修改了insert和erase以及insertFixup和eraseFixup函数,
*以支持在节点删除和插入时对size域的维护
*/
#include<iostream>
#include<iomanip>

using namespace std;
enum COLOR { red, black };//枚举,定义颜色

template <typename T> class OSTree;

template <typename T>
class node
{
private:
friend class OSTree<T>;
node *parent;
node *left;
node *right;
T key;
size_t size;
COLOR color;
node(){}//默认构造函数,只供创建nil时调用
public:
node(const T &k, COLOR c = red) :key(k), color(c), size(1),
parent(NULL), left(NULL), right(NULL){}
T& getKey(){ return key; }
const T& getKey()const { return key; }
//省略指针域的getter和setter
};

template <typename T>
class OSTree
{
private:
static node<T> *nil;//哨兵,静态成员,被整个OSTree类所共有
node<T> *root;
OSTree(const OSTree&);//只声明不定义,以禁止复制构造
OSTree operator=(const OSTree&);//禁止赋值
void leftRotate(node<T>*);//左旋
void rightRotate(node<T>*);//右旋
void insertFixup(node<T>*);//插入节点后红黑性质调整
void eraseFixup(node<T>*);//删除节点后红黑性质调整
public:
OSTree() :root(nil)
{
nil->parent = nil;
nil->left = nil;
nil->right = nil;
nil->color = black;
nil->size = 0;//nil的size域为0
}
OSTree(node<T> *rbt) :root(rbt){}//复制构造函数,用于创建子红黑树对象
void insert(const T&);//插入
void create();//创建红黑树
void erase(const T&);//删除
size_t rank(node<T> *curr)const;//返回curr节点的排序数
size_t keyRank(const T &k)const;//返回关键字为k在此树中的排序数
node<T>* I_successor(node<T> *curr, size_t i)const;//查找curr节点的第i个后继
node<T>* select(size_t i)const;//在树中查找中序遍历第i个节点
node<T>* locate(const T&)const;//查找
node<T>* minimum()const;//最小值
node<T>* maximum()const;//最大值
node<T>* successor(const T&)const;//找后继
node<T>* predecessor(const T&)const;//前驱
void preTraversal()const;//先根遍历
void inTraversal()const;//中根遍历
void destroy();//销毁红黑树
bool empty()const{ return root == nil; }//判空
};

template <typename T> node<T> *OSTree<T>::nil = new node<T>;//定义静态成员nil

template <typename T>
void OSTree<T>::leftRotate(node<T> *curr)
{
if (curr->right != nil)
{//存在右孩子时才能左旋
node<T> *rchild = curr->right;
curr->right = rchild->left;
if (rchild->left != nil)
rchild->left->parent = curr;
rchild->parent = curr->parent;
if (curr->parent == nil)
root = rchild;
else if (curr == curr->parent->left)
curr->parent->left = rchild;
else curr->parent->right = rchild;
curr->parent = rchild;
rchild->left = curr;
rchild->size = curr->size;
curr->size = curr->left->size + curr->right->size + 1;
}
}

template <typename T>
void OSTree<T>::rightRotate(node<T> *curr)
{
if (curr->left != nil)
{//存在左孩子时才能右旋
node<T> *lchild = curr->left;
curr->left = lchild->right;
if (lchild->right != nil)
lchild->right->parent = curr;
lchild->parent = curr->parent;
if (curr->parent == nil)
root = lchild;
else if (curr == curr->parent->left)
curr->parent->left = lchild;
else curr->parent->right = lchild;
lchild->right = curr;
curr->parent = lchild;
lchild->size = curr->size;
curr->size = curr->left->size + curr->right->size + 1;
}
}

template <typename T>
void OSTree<T>::insert(const T &k)
{
node<T> *pkey = new node<T>(k),
*p = nil, *curr = root;
while (curr != nil)
{//找插入位置
++curr->size;
p = curr;//记住当前节点父亲
if (k < curr->key)//往左找
curr = curr->left;
else curr = curr->right;//向右找
}
pkey->parent = p;
if (p == nil)//插入的是第一个节点
root = pkey;
else if (k < p->key)
p->left = pkey;
else p->right = pkey;
pkey->left = pkey->right = nil;
insertFixup(pkey);//调整
}

template <typename T>
void OSTree<T>::insertFixup(node<T> *curr)
{
while (curr->parent->color == red)
{//父亲为红节点时才需要进入循环调整
if (curr->parent == curr->parent->parent->left)
{//父亲是祖父左孩子
node<T> *uncle = curr->parent->parent->right;
if (uncle != nil && uncle->color == red)
{//情况1,叔叔节点存在且为红色
curr->parent->color = black;
uncle->color = black;
curr->parent->parent->color = red;
curr = curr->parent->parent;
}
else if (curr == curr->parent->right)
{//情况2,叔叔节点为黑色,且当前节点是父亲右孩子
curr = curr->parent;
leftRotate(curr);//将父节点左旋,以转变为情况3
}
else
{//情况3,叔叔节点为黑色,且当前节点是父亲左孩子
curr->parent->color = black;
curr->parent->parent->color = red;
rightRotate(curr->parent->parent);
}
}
else
{//父亲是祖父右孩子,与上面三种情况对称
node<T> *uncle = curr->parent->parent->left;
if (uncle != nil && uncle->color == red)
{//情况1
curr->parent->color = black;
uncle->color = black;
curr->parent->parent->color = red;
curr = curr->parent->parent;
}
else if (curr == curr->parent->left)
{//情况2
curr = curr->parent;
rightRotate(curr);
}
else
{//情况3
curr->parent->color = black;
curr->parent->parent->color = red;
leftRotate(curr->parent->parent);
}
}
}
root->color = black;//跳出循环时将根节点置为黑色
}

template <typename T>
void OSTree<T>::create()
{
T k;
cout << "Enter element(s),CTRL+Z to end" << endl;//换行后CTRL+Z结束输入
while (cin >> k)
insert(k);
cin.clear();
}

template <typename T>
void OSTree<T>::preTraversal()const
{
node<T> *curr = root;
if (curr != nil)
{
cout << curr->key << " : ";
if (curr->color == red) cout << left << setw(12) << "red";
else cout << left << setw(12) << "black";
cout << "size: " << curr->size << endl;
OSTree LEFT(curr->left);//继续左子树先根遍历
LEFT.preTraversal();
OSTree RIGHT(curr->right);
RIGHT.preTraversal();
}
}

template <typename T>
void OSTree<T>::inTraversal()const
{
node<T> *curr = root;
if (curr != nil)
{
OSTree LEFT(curr->left);
LEFT.inTraversal();
cout << curr->key << " : ";
if (curr->color == red) cout << left << setw(12) << "red";
else cout << left << setw(12) << "black";
cout << "size: " << curr->size << endl;
OSTree RIGHT(curr->right);//继续右子树中根遍历
RIGHT.inTraversal();
}
}

template <typename T>
node<T>* OSTree<T>::successor(const T &k)const
{
node<T> *curr = locate(k);
if (curr->right != nil)
{//若右子树不为空,则后继为右子树最小值
OSTree RIGHT(curr->right);
return RIGHT.minimum();
}
node<T> *p = curr->parent;
while (p != nil && curr == p->right)
{//否则为沿右指针一直向上直到第一个拐弯处节点
curr = p;
p = p->parent;
}
return p;
}

template <typename T>
node<T>* OSTree<T>::minimum()const
{
node<T> *curr = root;
while (curr->left != nil)
curr = curr->left;
return curr;
}

template <typename T>
node<T>* OSTree<T>::maximum()const
{
node<T> *curr = root;
while (curr->right != nil)
curr = curr->right;
return curr;
}

template <typename T>
node<T>* OSTree<T>::predecessor(const T &k)const
{
node<T> *curr = locate(k);
if (curr->left != nil)
{//若左子树不为空,则前驱为左子树最大值
OSTree LEFT(curr->left);
return LEFT.maximum();
}
node<T> *p = curr->parent;
while (p != nil && curr == p->left)
{//否则为沿左指针一直往上的第一个拐弯处节点
curr = p;
p = p->parent;
}
return p;
}

template <typename T>
void OSTree<T>::erase(const T &k)
{
node<T> *curr = locate(k), *pdel, *child;
if (curr->left == nil || curr->right == nil)//决定删除节点
pdel = curr;//若当前节点至多有一个孩子,则删除它
else pdel = successor(k);//否则若有两孩子,则删除其后继
node<T> *par = pdel->parent;
while (par != nil)
{
--par->size;
par = par->parent;
}
if (pdel->left != nil)//记下不为空的孩子
child = pdel->left;
else child = pdel->right;
child->parent = pdel->parent;
if (pdel->parent == nil)//若删除的是根节点
root = child;
else if (pdel == pdel->parent->left)//否则若被删节点是其父亲左孩子
pdel->parent->left = child;
else pdel->parent->right = child;
if (curr != pdel)
curr->key = pdel->key;//若被删的是后继,则将后继值赋给当前节点
if (pdel->color == black)//被删节点为黑色时才调整
eraseFixup(child);
delete pdel;//释放所占内存
}

template <typename T>
void OSTree<T>::eraseFixup(node<T> *curr)
{
while (curr != root && curr->color == black)
{//当前不为根,且为黑色
if (curr == curr->parent->left)
{//若其是父亲左孩子
node<T> *brother = curr->parent->right;//兄弟节点肯定存在
if (brother->color == red)
{//情况1,兄弟是红色,转变为情况2,3,4
brother->color = black;
curr->parent->color = red;
leftRotate(curr->parent);
brother = curr->parent->right;
}
if (brother->left->color == black && brother->right->color == black)
{//情况2,兄弟是黑色,且两孩子也是黑色,将当前节点和兄弟去一重黑色
brother->color = red;
curr = curr->parent;
}
else if (brother->right->color == black)
{//情况3,兄弟左孩子为红,右孩子为黑,转变为情况4
brother->color = red;
brother->left->color = black;
rightRotate(brother);
brother = curr->parent->right;
}
else
{//情况4,右孩子为黑色,左孩子随意
brother->color = curr->parent->color;
curr->parent->color = black;
brother->right->color = black;
leftRotate(curr->parent);
curr = root;
}
}
else
{//若其是父亲右孩子,与上面四中情况对称
node<T> *brother = curr->parent->left;
if (brother->color == red)
{//情况1
brother->color = black;
curr->parent->color = red;
rightRotate(curr->parent);
brother = curr->parent->left;
}
if (brother->right->color == black && brother->left->color == black)
{//情况2
brother->color = red;
curr = curr->parent;
}
else if (brother->left->color == black)
{//情况3
brother->color = red;
brother->right->color = black;
leftRotate(brother);
brother = curr->parent->left;
}
else
{//情况4
brother->color = curr->parent->color;
curr->parent->color = black;
brother->left->color = black;
rightRotate(curr->parent);
curr = root;
}
}
}
curr->color = black;//结束循环时将当前节点置为黑色
}

template <typename T>
node<T>* OSTree<T>::locate(const T &k)const
{
node<T> *curr = root;
while (curr != nil && curr->key != k)
{
if (k < curr->key)curr = curr->left;
else curr = curr->right;
}
return curr;
}

template <typename T>
node<T>* OSTree<T>::select(size_t i)const
{
node<T> *curr = root;
size_t rank = curr->left->size + 1;
while (i != rank && curr != nil)
{
if (i < rank) curr = curr->left;
else
{//在右子树中
curr = curr->right;
i = i - rank;
}
rank = curr->left->size + 1;
}
return curr;
}

template <typename T>
size_t OSTree<T>::rank(node<T> *curr)const
{
if (curr == nil) return 0;
size_t rank = curr->left->size + 1;
while (curr->parent != nil)
{
if (curr == curr->parent->right) //如果是父亲右孩子,则排序数还要加上左兄弟树节点数及其根
rank = rank + curr->parent->left->size + 1;
curr = curr->parent;
}
return rank;
}

template <typename T>
size_t OSTree<T>::keyRank(const T &k)const
{
if (root == nil)
{
cout << "Error,no the key!" << endl;
exit(0);
}
node<T> *curr = root;
if (k == curr->key) return curr->left->size + 1;
else if (k < curr->key)
{//在左子树找
OSTree<T> LEFT(curr->left);
return LEFT.keyRank(k);
}
else
{//右子树找
OSTree<T> RIGHT(curr->right);
return RIGHT.keyRank(k) + curr->left->size + 1;
}
}

template <typename T>
node<T>* OSTree<T>::I_successor(node<T> *curr, size_t i)const
{
size_t r = rank(curr);//得到节点curr的排序数
return select(r + i);//查找第r + i个节点,即curr节点的第i个后继
}

template <typename T>
void OSTree<T>::destroy()
{
while (root != nil)
{
cout << "erase: " << root->key << endl;
erase(root->key);
}
delete nil;
}

int main()
{//26 17 41 14 21 30 47 10 16 19 23 28 38 7 12 15 20 35 39 3
//11 2 14 1 7 15 5 8 4,以下按本组数据测试
OSTree<int> ost;
cout << "create----------" << endl;
ost.create();
cout << "inTraversal-----" << endl;
ost.inTraversal();
cout << "preTraversal----" << endl;
ost.preTraversal();
node<int> *curr = ost.select(5);
cout << curr->getKey() << endl;
cout << ost.rank(curr) << endl;
cout << ost.I_successor(curr, 3)->getKey() << endl;
cout << ost.keyRank(14) << endl;
ost.destroy();
getchar();
return 0;
}



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: