算法导论 第14章 14.1 动态顺序统计

一、概念

1.动态顺序统计

动态顺序统计是指,在一个动态的无序的集合中,任意的顺序统计量都可以在O(lgn)时间内确定。

2.基础数组结构

红黑树,每个树结点的域包括:key[x],color[x],left[x],right[x]

3.附加信息

size[x]:以结点x为根的子树的内部结点(包括x)数,即子树的大小。
如果定义哨兵nil,则size[nil[T]]为0
size[x] = size[left[x]] + size[right[x]] + 1

4.对信息的维护

(1)插入操作:
第一个阶段,即从根开始,沿树下降,将新结点插入为某个已有结点的孩子,这一阶段的维护方法是对路径上每个结点的size都+1,时间是O(lgn)
第二个阶段,即沿树上升,做一些颜色修改和旋转以保持红黑性质,例如LEFT-ROTATE(T, x),维护方法是size[y]<-size[x],size[x]<-size[left[x]] + size[right[x]] + 1,由于至多旋转两次,时间是O(1)
(2)删除操作:
第一阶段,即对查找树进行操作,维护方法是对路径上每个结点的size都-1,时间是O(lgn)
第二阶段到多做三次旋转,维护方式与上面相同,时间是O(1)

5.设计新的操作

OS-SELECT(x, i):找出顺序统计树T中的第i小关键字,时间为O(lgn)
OS-RANK(T, x):给定指向一顺序统计树T中结点x的指针,返回对T进行中序遍历后得到 的线性序中x的位置,时间为O(lgn)

二、代码

1.Os_Tree.h

#include <iostream>
using namespace std;

#define BLACK 0
#define RED 1

//顺序统计量树结点结构
struct node
{
	/*红黑树的域*/
	int key;
	bool color;
	node *p;
	node *left;
	node *right;
	/*附加信息*/
	int size;//以结点x为根的子树的内部结点的个数,x->key=x->left->key+x->right->key+1

	/*构造函数*/
	node(node *init, int k):left(init),right(init),p(init),key(k),color(BLACK),size(1){}  
};
//顺序统计量树结构
class Os_Tree
{
public:
	node *root;
	node *nil;//哨兵
	/*构造函数*/
	Os_Tree(){nil = new node(NULL, -1);root = nil;nil->size = 0;};  
	/*动态顺序统计相关操作*/
	node *Os_Select(node *x, int i);
	int Os_Rank(node *x);
	/*红黑树的相关操作*/
	void Left_Rotate(node *x);//左旋
	void Right_Rotate(node *x);//右旋
	void Os_Insert_Fixup(node *z);//插入调整
	void Os_Insert(node *z);//插入
	void Os_Delete_Fixup(node *x);//删除调整
	node *Os_Delete(node *z);//删除
	void Print();//输出
	void Print(node *x);//输出
	node *Os_Search(node *x, int k);//在x的子树中查找关键字为k的结点
	node *Tree_Successor(node *x);//求后继
	node *Tree_Minimum(node *x);//求关键字最小的点
};
//左旋,令y = x->right, 左旋是以x和y之间的链为支轴进行旋转
//涉及到的结点包括:x,y,y->left,令node={p,l,r},具体变化如下:
//x={x->p,x->left,y}变为{y,x->left,y->left}
//y={x,y->left,y->right}变为{x->p,x,y->right}
//y->left={y,y->left->left,y->left->right}变为{x,y->left->left,y->left->right}
void Os_Tree::Left_Rotate(node *x)
{
	//令y = x->right
	node *y = x->right;
	//按照上面的方式修改三个结点的指针,注意修改指针的顺序
	x->right = y->left;
	if(y->left != nil)
		y->left->p = x;
	y->p = x->p;
	if(x->p == nil)//特殊情况:x是根结点
		root = y;
	else if(x == x->p->left)
		x->p->left = y;
	else 
		x->p->right = y;
	y->left = x;
	x->p = y;
	//对附加信息的维护
	y->size = x->size;
	x->size = x->left->size + x->right->size + 1;
}
//右旋,令y = x->left, 左旋是以x和y之间的链为支轴进行旋转
//旋转过程与上文类似
void Os_Tree::Right_Rotate(node *x)
{
	node *y = x->left;
	x->left = y->right;
	if(y->right != nil)
		y->right->p = x;
	y->p = x->p;
	if(x->p == nil)
		root = y;
	else if(x == x->p->right)
		x->p->right = y;
	else 
		x->p->left = y;
	y->right = x;
	x->p = y;
	//对附加信息的维护
	y->size = x->size;
	x->size = x->left->size + x->right->size + 1;
}
//红黑树调整
void Os_Tree::Os_Insert_Fixup(node *z)
{
	node *y;
	//唯一需要调整的情况,就是违反性质2的时候,如果不违反性质2,调整结束
	while(z->p->color == RED)
	{
		//p[z]是左孩子时,有三种情况
		if(z->p == z->p->p->left)
		{
			//令y是z的叔结点
			y = z->p->p->right;
			//第一种情况,z的叔叔y是红色的
			if(y->color == RED)
			{
				//将p[z]和y都着为黑色以解决z和p[z]都是红色的问题
				z->p->color = BLACK;
				y->color = BLACK;
				//将p[p[z]]着为红色以保持性质5
				z->p->p->color = RED;
				//把p[p[z]]当作新增的结点z来重复while循环
				z = z->p->p;
			}
			else
			{
				//第二种情况:z的叔叔是黑色的,且z是右孩子
				if(z == z->p->right)
				{
					//对p[z]左旋,转为第三种情况
					z = z->p;
					Left_Rotate(z);
				}
				//第三种情况:z的叔叔是黑色的,且z是左孩子
				//交换p[z]和p[p[z]]的颜色,并右旋
				z->p->color = BLACK;
				z->p->p->color = RED;
				Right_Rotate(z->p->p);
			}
		}
		//p[z]是右孩子时,有三种情况,与上面类似
		else if(z->p == z->p->p->right)
		{
			y = z->p->p->left;
			if(y->color == RED)
			{
				z->p->color = BLACK;
				y->color = BLACK;
				z->p->p->color = RED;
				z = z->p->p;
			}
			else
			{
				if(z == z->p->left)
				{
					z = z->p;
					Right_Rotate(z);
				}
				z->p->color = BLACK;
				z->p->p->color = RED;
				Left_Rotate(z->p->p);
			}
		}
	}
	//根结点置为黑色
	root->color = BLACK;
}
//插入一个结点
void Os_Tree::Os_Insert(node *z)
{
	node *y = nil, *x = root;
	//找到应该插入的位置,与二叉查找树的插入相同
	while(x != nil)
	{
		x->size++;
		y = x;
		if(z->key < x->key)
			x = x->left;
		else
			x = x->right;
	}
	z->p = y;
	if(y == nil)
		root = z;
	else if(z->key < y->key)
		y->left = z;
	else
		y->right = z;
	z->left = nil;
	z->right = nil;
	//将新插入的结点转为红色
	z->color = RED;
	//从新插入的结点开始,向上调整
	Os_Insert_Fixup(z);
}
//对树进行调整,x指向一个红黑结点,调整的过程是将额外的黑色沿树上移
void Os_Tree::Os_Delete_Fixup(node *x)
{
	node *w;
	//如果这个额外的黑色在一个根结点或一个红结点上,结点会吸收额外的黑色,成为一个黑色的结点
	while(x != root && x->color == BLACK)
	{
		//若x是其父的左结点(右结点的情况相对应)
		if(x == x->p->left)
		{
			//令w为x的兄弟,根据w的不同,分为三种情况来处理
			//执行删除操作前x肯定是没有兄弟的,执行删除操作后x肯定是有兄弟的
			w = x->p->right;
			//第一种情况:w是红色的
			if(w->color == RED)
			{
				//改变w和p[x]的颜色
				w->color = BLACK;
				x->p->color = RED;
				//对p[x]进行一次左旋
				Left_Rotate(x->p);
				//令w为x的新兄弟
				w = x->p->right;
				//转为2.3.4三种情况之一
			}
			//第二情况:w为黑色,w的两个孩子也都是黑色
			if(w->left->color == BLACK && w->right->color == BLACK)
			{
				//去掉w和x的黑色
				//w只有一层黑色,去掉变为红色,x有多余的一层黑色,去掉后恢复原来颜色
				w->color = RED;
				//在p[x]上补一层黑色
				x = x->p;
				//现在新x上有个额外的黑色,转入for循环继续处理
			}
			//第三种情况,w是黑色的,w->left是红色的,w->right是黑色的
			else
			{
				if(w->right->color == BLACK)
				{
					//改变w和left[x]的颜色
					w->left->color = BLACK;
					w->color = RED;
					//对w进行一次右旋
					Right_Rotate(w);
					//令w为x的新兄弟
					w = x->p->right;
					//此时转变为第四种情况
				}
				//第四种情况:w是黑色的,w->left是黑色的,w->right是红色的
				//修改w和p[x]的颜色
				w->color =x->p->color;
				x->p->color = BLACK;
				w->right->color = BLACK;
				//对p[x]进行一次左旋
				Left_Rotate(x->p);
				//此时调整已经结束,将x置为根结点是为了结束循环
				x = root;
			}
		}
		//若x是其父的左结点(右结点的情况相对应)
		else if(x == x->p->right)
		{
			//令w为x的兄弟,根据w的不同,分为三种情况来处理
			//执行删除操作前x肯定是没有兄弟的,执行删除操作后x肯定是有兄弟的
			w = x->p->left;
			//第一种情况:w是红色的
			if(w->color == RED)
			{
				//改变w和p[x]的颜色
				w->color = BLACK;
				x->p->color = RED;
				//对p[x]进行一次左旋
				Right_Rotate(x->p);
				//令w为x的新兄弟
				w = x->p->left;
				//转为2.3.4三种情况之一
			}
			//第二情况:w为黑色,w的两个孩子也都是黑色
			if(w->right->color == BLACK && w->left->color == BLACK)
			{
				//去掉w和x的黑色
				//w只有一层黑色,去掉变为红色,x有多余的一层黑色,去掉后恢复原来颜色
				w->color = RED;
				//在p[x]上补一层黑色
				x = x->p;
				//现在新x上有个额外的黑色,转入for循环继续处理
			}
			//第三种情况,w是黑色的,w->right是红色的,w->left是黑色的
			else
			{
				if(w->left->color == BLACK)
				{
					//改变w和right[x]的颜色
					w->right->color = BLACK;
					w->color = RED;
					//对w进行一次右旋
					Left_Rotate(w);
					//令w为x的新兄弟
					w = x->p->left;
					//此时转变为第四种情况
				}
				//第四种情况:w是黑色的,w->right是黑色的,w->left是红色的
				//修改w和p[x]的颜色
				w->color =x->p->color;
				x->p->color = BLACK;
				w->left->color = BLACK;
				//对p[x]进行一次左旋
				Right_Rotate(x->p);
				//此时调整已经结束,将x置为根结点是为了结束循环
				x = root;
			}
		}
	}
	//吸收了额外的黑色
	x->color = BLACK;
}
//找最小值   
node *Os_Tree::Tree_Minimum(node *x)  
{  
    //只要有比当前结点小的结点   
	while(x->left != nil)  
        x = x->left;  
    return x;  
} 
//查找中序遍历下x结点的后继,后继是大于key[x]的最小的结点   
node *Os_Tree::Tree_Successor(node *x)  
{  
    //如果有右孩子   
	if(x->right != nil)  
        //右子树中的最小值   
        return Tree_Minimum(x->right);  
    //如果x的右子树为空且x有后继y,那么y是x的最低祖先结点,且y的左儿子也是   
    node *y = x->p;  
    while(y != NULL && x == y->right)  
    {  
        x = y;  
        y = y->p;  
    }  
    return y;  
}  
//递归地查询二叉查找树   
node *Os_Tree::Os_Search(node *x, int k)  
{  
    //找到叶子结点了还没找到,或当前结点是所查找的结点   
	if(x->key == -1 || k == x->key)  
        return x;  
    //所查找的结点位于当前结点的左子树   
    if(k < x->key)  
        return Os_Search(x->left, k);  
    //所查找的结点位于当前结点的左子树   
    else  
        return Os_Search(x->right, k);  
} 
//红黑树的删除
node *Os_Tree::Os_Delete(node *z)
{
	//找到结点的位置并删除,这一部分与二叉查找树的删除相同
	node *x, *y;
	if(z->left == nil || z->right == nil)
		y = z;
	else y = Tree_Successor(z);
	node *p = y->p;
	while(p != nil)
	{
		p->size--;
		p = p->p;
	}
	if(y->left != nil)
		x = y->left;
	else x = y->right;
	x->p = y->p;
	if(y->p == nil)
		root = x;
	else if(y == y->p->left)
		y->p->left = x;
	else
		y->p->right = x;
	if(y != z)
		z->key = y->key;
	//如果被删除的结点是黑色的,则需要调整
	if(y->color == BLACK)
		Os_Delete_Fixup(x);
	return y;
}
void Os_Tree::Print(node *x)
{
	if(x->key == -1)
		return;
	Print(x->left);
	cout<<x->key<<' '<<x->color<<endl;
	Print(x->right);
}
void Os_Tree::Print()
{
	Print(root);
	cout<<endl;
}
//查找以x为根结点的树中第i大的结点
node *Os_Tree::Os_Select(node *x, int i)
{
	//令x左子树中点的个数为r-1,
	int r = x->left->size +1;
	//那么x是x树中第r大的结点
	if(r == i)
		return x;
	//第i大的元素在x->left中
	else if(i < r)
		return Os_Select(x->left, i);
	//第i大的元素在x->right中
	else
		return Os_Select(x->right, i - r);
}
//计算树T中进行顺序遍历后得到的线性序中x的位置
int Os_Tree::Os_Rank(node *x)
{
	//置r为以x为根的子树中key[x]的秩
	int r = x->left->size + 1;
	node *y = x;
	while(y != root)
	{
		//若y是p[y]的右孩子,p[y]和p[y]左子树中所有结点前于x
		if(y == y->p->right)
			r = r + y->p->left->size + 1;
		y = y->p;
	}
	return r;
}

2.main.cpp

#include <iostream>
#include "Os_Tree.h"
using namespace std;

int main()
{
	char ch;
	int x;
	//生成一棵顺序统计树
	Os_Tree *T = new Os_Tree;
	while(1)
	{
		cin>>ch;
		switch(ch)
		{
			//插入一个元素
		case 'I':
			{
				cin>>x;
				node *z = new node(T->nil, x);
				T->Os_Insert(z);
				break;
			}
			//删除一个元素
		case 'D':
			{
				cin>>x;
				node *z = T->Os_Search(T->root, x);
				if(z == NULL)
					cout<<"not exist"<<endl;
				else
					T->Os_Delete(z);
				break;
			}
			//计算第x小关键字
		case 'S':
			{
				cin>>x;
				node *z = T->Os_Select(T->root, x);
				if(z == NULL)
					cout<<"not exist"<<endl;
				else
					cout<<z->key<<endl;
				break;
			}
			//计算x的位置
		case 'R':
			{
				cin>>x;
				node *z = T->Os_Search(T->root, x);
				if(z == NULL)
					cout<<"not exist"<<endl;
				else
					cout<<T->Os_Rank(z)<<endl;
				break;	
			}
		case 'P':
			T->Print();
			break;
		default:
			break;
		}
	}
	return 0;
}

三、练习

14.1-3

//14.1-3非递归地查找以x为根结点的树中第i大的结点
node *Os_Tree::Os_Select_Iterative(node *x, int i)
{
	//根结点开始找起
	while(x != NULL)
	{
		int r = x->left->size + 1;
		//如果找到了
		if(r == i)
			return x;
		//如果位置更靠前
		if(i < r)
			x = x->left;
		//如果位置更靠后
		else
		{
			x = x->right;
			i = i - r;
		}
	}
}

14.1-4

//14.1-4递归地计算树T中进行顺序遍历后得到的线性序中x的位置
int Os_Tree::Os_Rank_Recursion(node *root, node *x)
{
	if(x->key == root->key)
		return root->left->size + 1;
	if(x->key > root->key)
		//左子树的结点个数 + 根结点 + x在右结点中的秩
		return root->left->size + 1 + Os_Rank_Recursion(root->right, x);
	//在左子树中的秩
	return Os_Rank_Recursion(root->left, x);
}

 14.1-5

//14.1-5确定x在该树的线性序中第i个后继
node *Os_Tree::Next(node *x, int i)
{
	//就是当前结点
	if(i == 0)
		return x;
	//在x的右子树中
	if(x->right != nil && x->right->size >= i)
		return Os_Select(x->right, i);
	//或在x某个祖先的右子树中
	else
	{
		//找到某个“有右子树”且“x不在其右子树上”的祖先
		while(x->p != NULL && x == x->p->right)
			x = x->p;
		//找到了根结点,就停止查找
		if(x->p == NULL)
		{
			cout<<"error:not exist"<<endl;
			exit(0);
		}
		//对这个祖先进行递归的查找 
		Next(x, i-1);
	}
}

15.1-6

附加信息rank[x]
插入时,若插入到x结点的左子树中,则rank[x] = rank[x] + 1,否则不变
删除时,若删除的结点在x的左子树中,则rank[x] = rank[x] - 1,否则不变
左旋时,若对x执行左旋,令y=x->right,则rank[x]不变,rank[y] = rank[y] + rank[x]
右旋时,若对x执行右旋,令y=x->left,则rank[y]不变,rank[x] = rank[x] + rank[y]

14.1-7

算法导论-14.1-7

求逆序数中介绍了使用树状数组或归并排序求逆序对,这里使用顺序统计数。

数组中某个数字s[i]的逆序数是指出现在s[i]之前,但是比s[i]大的数字的个数。

根据顺序统计量的Os_Rank(),每插入到一个元素x后,可以求得在已经出现的元素中,比x大的数字的个数


14.1-8【转】

通过角度来判断两条弦是否相交,这样可以在O(n*logn)内完成。
对于两条弦P1P2和Q1Q2来说(顺时针),圆心与端点形成的向量有一个角度A
如果A(P1)<A(Q1)<A(P2)<A(Q2)或者A(Q1)<A(P1)<A(Q2)<A(P2),这样角度区间“交叉”就意味着两条弦有交叉。
通过角度来统计交叉弦的对数,和“逆序对”的问题本质上是一样的

这可以看做是“顺序统计树”的典型应用。
我们判断两条弦是否相交的依据还是上面提到的“角度”区间是否有“交叉”现象发生
注意一个区间包含另一个区间的情况不能算作“交叉”)
首先n条弦共2n个端点,每个端点对于圆心来说,都对应一个[0,2*pi)内的角度。
我们按角度大小(实际上就是逆时针方向)对这2n个角度进行排序,这一步需要花费O(n*logn)
对于每条弦来说,它的两个端点都可以看做是“事件点”:从0角度开始逆时针在圆上扫描,遇到弦的第一个点可以看成是弦的“起点”,遇到的第二个点看成是弦的“终点”。
然后,我们用一棵“顺序统计树”来辅助进行处理(初始化当然为空)。

按照角度小到大的顺序遍历这2n个端点:
如果该端点是某条弦X的“起点”
{
	将弦X插入顺序统计树中(以X的“起点”角度作为key);
}
如果该端点是某条弦X的“终点”
{
	统计出目前这棵树中有多少条弦的“起点”角度比X的“起点”角度大,这就是与X相交的弦的数量;
	//对于顺序统计树来说,上面这步只要O(logn)就能实现
	将弦X从顺序统计树中删除; //这一步同样只需要O(logn)
}


转载于:https://my.oschina.net/windmissing/blog/690464

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值