算法导论 14.1-7 顺序统计树求逆序对 O(nlgn)

一、题目

说明如何在O(nlgn)的时间内,利用顺序统计树对大小为n的数组中的逆序对(见思考题2-4)进行计数。

 

二、思考

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

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

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

 

三、代码

#include 
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){}  
};
//顺序统计量树结构
struct Os_Tree
{
	node *root;
	node *nil;
	Os_Tree()
	{
		nil = new node(NULL, -1);//哨兵结点
		root = nil;nil->size = 0;//初始时,树为空,root指向哨兵
	};  
};
//计算树T中进行顺序遍历后得到的线性序中x的位置
int Os_Rank(Os_Tree *T, node *x)
{
	//置r为以x为根的子树中key[x]的秩
	int r = x->left->size + 1;
	node *y = x;
	while(y != T->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;
}
//左旋,令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 Left_Rotate(Os_Tree *T, node *x)
{
	//令y = x->right
	node *y = x->right;
	//按照上面的方式修改三个结点的指针,注意修改指针的顺序
	x->right = y->left;
	if(y->left != T->nil)
		y->left->p = x;
	y->p = x->p;
	if(x->p == T->nil)//特殊情况:x是根结点
		T->root = y;
	else if(x == x->p->left)
		x->p->left = y;
	else 
		x->p->right = y;
	y->left = x;
	x->p = y;
	//因为旋转而修改size
	y->size = x->size;
	x->size = x->left->size + x->right->size + 1;
}
//右旋,令y = x->left, 左旋是以x和y之间的链为支轴进行旋转
//旋转过程与上文类似
void Right_Rotate(Os_Tree *T, node *x)
{
	node *y = x->left;
	x->left = y->right;
	if(y->left != T->nil)
		y->right->p = x;
	y->p = x->p;
	if(x->p == T->nil)
		T->root = y;
	else if(x == x->p->right)
		x->p->right = y;
	else 
		x->p->left = y;
	y->right = x;
	x->p = y;
	//因为旋转而修改size
	y->size = x->size;
	x->size = x->left->size + x->right->size + 1;
}
//红黑树调整
void RB_Insert_Fixup(Os_Tree *T, 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(T, z);
				}
				//第三种情况:z的叔叔是黑色的,且z是左孩子
				//交换p[z]和p[p[z]]的颜色,并右旋
				z->p->color = BLACK;
				z->p->p->color = RED;
				Right_Rotate(T, 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(T, z);
				}
				z->p->color = BLACK;
				z->p->p->color = RED;
				Left_Rotate(T, z->p->p);
			}
		}
	}
	//根结点置为黑色
	T->root->color = BLACK;
}
//向树上加入一个元素
void Os_Insert(Os_Tree *T, node *z)
{
	node *y = T->nil, *x = T->root;
	//找到应该插入的位置,与二叉查找树的插入相同
	while(x != T->nil)
	{
		y = x;
		x->size++;
		if(z->key < x->key)
		{
			//如果插入到x->left中,x->size要+1
			x = x->left;
		}
		else
			x = x->right;
	}
	z->p = y;
	if(y == T->nil)
		T->root = z;
	else if(z->key < y->key)
		y->left = z;
	else
		y->right = z;
	z->left = T->nil;
	z->right = T->nil;
	//将新插入的结点转为红色
	z->color = RED;
	//从新插入的结点开始,向上调整
	RB_Insert_Fixup(T, z);
}
int main()
{
	//测试数据,计算数组s的逆序数
	int i, sum = 0, n, x;
	//输入一组测试数据中有几个数字
	while(cin>>n)
	{
		sum = 0;
		//生成一个顺序统计量树
		Os_Tree *T = new Os_Tree;
		//依次插入结点并计算顺序统计量
		for(i = 0; i < n; i++)
		{
			cin>>x;
			//生成一个新的结点
			node *z = new node(T->nil, x);
			//插入这么结点
			Os_Insert(T, z);
			//Os_Rank(T, z)表示s[0..i]中<=s[i]和数的个数
			//i+1表示s[0..i]总的数的个数
			//相减便得到出现在s[i]前但比s[i]大的数的个数
			sum = sum + i + 1 - Os_Rank(T, z);
		}
		cout<


四、代码测试

你可能感兴趣的:(算法导论)