这章讲得主要是在原有数据结构基础之上,增添某种符合需要的属性,并对原程序的各种操作进行维护。这章是非常有意义的一章,为今后的学习和工作提供了一种常用的方法来改进和维护现有的程序。
14.1动态顺序统计
秩:即为它在集合线性序中的位置。
顺序统计树:在红黑树上新增加1个size属性,这个属性包含了以x为根的子树(包括x本身)的(内)结点数。
查找具有给定秩的元素
代码如下:
struct OS_Tree*OS_SELECT(struct OS_Tree*x,int i)//查找顺序统计树给定秩的元素
{
int r=x->left->size+1;
if (i==r)
{
return x;
}
else if (i<r)
{
return OS_SELECT(x->left,i);
}
else return OS_SELECT(x->right,i-r);
}
确定一个元素的秩
代码如下;
int OS_RANK(struct OS_Tree*T,struct OS_Tree*x)//确定顺序统计树的秩
{
int r=x->left->size+1;
struct OS_Tree*y=x;
while (y!=root)
{
if (y==y->parent->right)
{
r=r+y->parent->left->size+1;
}
y=y->parent;
}
return r;
}
对子树规模的维护
插入函数:从新插入结点到根向上遍历的每个结点的size属性+1。
删除函数:从删除结点到根结点的路径上的每个结点的size属性-1.
旋转:仅会使两个结点的size属性失效,并且函数最后添加两行代码:①y.size=x.size②x.size=x.left.size+x.right.size+1
练习:
14.1-1对于图14-1中的红黑树T,说明执行OS_SELECT(T.root,10)的过程。
当前结点秩:r=13,当前结点x=26.
由于i<r 进入下一层递归,当前秩r=8,当前结点x=17;
由于i>r 进入下一层递归, 当前秩r=3,当前结点x=21.;
由于i<r(i=2)进入下一层递归,当前秩r=1,当前结点x=19.
由于i>r(i=2)进入下一层递归,当前秩r=1,当前结点x=20.
i=r=1,终止递归。
14.1-2对于图14-1中的红黑树T和关键字x.key为35的结点x,说明执行OS_RNAK(T,x)的过程。
可以通过单步调试来看迭代过程,这是一个不断从待查找结点向根部计算过程r=1时,y=35 =>r=3时,y=38 =>r=16时,y=41,=> r=16时,y=26,已经到达根部停止迭代。
14.1-3写出OS_SELECT的非递归版本。
struct OS_Tree*ITERATIVE_OS_SELECT(struct OS_Tree*x,int i)//查找顺序统计树给定秩的元素
{
int r=x->left->size+1;
while (i!=r)
{
r=x->left->size+1;
if (i<r)
{
x=x->left;
}
else if(i>r)
{
x=x->right;
i=i-r;
}
}
return x;
}
14.1-4 写出一个递归过程OS_KEY_RANK(T,k),以一棵顺序统计树T和一个关键字k作为输入,要求返回k在由T表示的动态集合中的秩。假设T的所有关键字都不相同。
先对关键字k所对应的指针执行一个查找函数struct OS_Tree*x=ITERATIVE_TREE_SEARCH(root,35);,然后将此结点x带入递归OS_KEY_RANK(x,k).代码如下:
struct OS_Tree*x=ITERATIVE_TREE_SEARCH(root,35);//主函数运行的函数。
OS_RANK(x,35);//主函数运行的函数。
int OS_RANK( struct OS_Tree*y,int k)//递归的查找一个元素的秩。
{
static int r=y->left->size+1;
if (y==root)
{
return r;
}
else
{
if (y==y->parent->right)
{
r=r+y->parent->left->size+1;
}
return OS_RANK(y->parent,k);
}
}
14.1-5给定n个元素的顺序统计树中的一个元素x和一个自然数i,如何在O(lgn)时间内确定x在该树线性序中的第i个后继?
先调用OS_RANK确定元素x的秩r,然后调用OS_SELECT确定秩r+i所对应的元素,这个元素即为所求。
14.1-6 在OS_SELECT或OS_RANK中,注意到无论什么时候引用结点的size属性都是为了计算一个秩。相应地,假设每个结点都存储它在以自己为根的子树中的秩。试说明在插入和删除时,如何维护这个信息。(注意,这两种操作都可能引起旋转。)
关于维护,上面开篇已经给出说明,代码在插入和删除维护size属性
14.1-7 说明如何在O(nlgn)时间内,利用顺序统计树对大小为n的数组中的逆序对进行计数。
插入过程中,假设第i个元素插入到树中,那么i的秩可以求出,也就计算出所有小于等于秩为r的元素个数,那么总共插入了i个数,i-rank(i)=所有在i之前插入但又大于i的逆序对。对其进行累加,最后求逆序对和。
14.1-8现有一个圆的n条弦,每条弦都由其端点来定义。请给出一个能在O(nlgn)时间内确定圆内相交弦对数的算法。(例如,如果n条弦都为直径,它们相交于圆心,则正确的答案为C(n,2))假设任意两条弦都不会共享端点。
我们以圆的12点位置到圆心的直线距离为轴,顺时针计算各种交于圆的点的角度。设一条弦交于圆两点Qi,Pi(i=0,1,...n) ∠Qi<∠Pi.存在两条直线l1,l2.有∠Q1<∠Q2<∠P1<∠P2.