一、题目
说明如何在O(nlgn)的时间内,利用顺序统计树对大小为n的数组中的逆序对(见思考题2-4)进行计数。
二、思考
求逆序数中介绍了使用树状数组或归并排序求逆序对,这里使用顺序统计数。
数组中某个数字s[i]的逆序数是指出现在s[i]之前,但是比s[i]大的数字的个数。
根据顺序统计量的Os_Rank(),每插入到一个元素x后,可以求得在已经出现的元素中,比x大的数字的个数
三、代码
- #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){}
- };
- //顺序统计量树结构
- 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<<sum<<endl;
- delete T;
- }
- return 0;
- }
四、代码测试
- 上一篇:HDU-1043-八数码-代码