顺序统计量树

介绍

    有关顺序统计量在前文中位数与顺序统计量里有详细介绍,在前文是用数组实现的。而在这里,是基于红黑树的实现,效率更加高效。

实现

    在红黑树的基础上,增加了一个size数据域,用来标明以当前结点为子树的结点总数,也包含自身:

typedef struct _Node
{
	char color;
	int data;
	int size;
	_Node* parent;
	_Node* left;
	_Node* right;
}Node;

    然后,就是创建结点的时候,默认结点数为1:

void initNode(Node* node)
{
	node->parent = g_sentry;
	node->left = g_sentry;
	node->right = g_sentry;
	node->size = 1;
}

void init()
{
	g_sentry = new Node;
	g_sentry->color = static_cast<char>(BLACK);
	initNode(g_sentry);
	g_sentry->size = 0;

	g_tree = new Tree;
	g_tree->root = g_sentry;
}

    并且,默认哨兵的结点数为0,即不统计哨兵。

    接着就是旋转,因为旋转交换了2个节点的位置,固需要修改它的结点值:
    左转:

void leftRotate(Tree* tree, Node* node)
{
	Node* y = node->right;
	node->right = y->left;
	y->left->parent = node;
	y->parent = node->parent;
	if (g_sentry == node->parent)
	{
		tree->root = y;
	}else if (node == node->parent->left)
	{
		node->parent->left = y;
	}else
		node->parent->right = y;
	y->left = node;
	node->parent = y;
	y->size = node->size;
	node->size = node->left->size + node->right->size + 1;
}

    把旋转之前的父结点的包含结点数给右结点,然后重新统计父结点的包含结点数,右转一样:

void rightRotate(Tree* tree, Node* node)
{
	Node* x = node->left;
	node->left = x->right;
	x->right->parent = node;
	x->parent = node->parent;
	if (g_sentry == node->parent)
	{
		tree->root = x;
	}else if (node == node->parent->left)
	{
		node->parent->left = x;
	}else
		node->parent->right = x;
	x->right = node;
	node->parent = x;
	x->size = node->size;
	node->size = node->left->size + node->right->size + 1;
}

    插入的时候,因为多了一个结点,固在这个结点的路上的所有结点都需要+1结点数:

void treeInsert(Tree* tree, Node* node)
{
	assert(g_sentry != node);
	Node* y = g_sentry;
	Node* x = tree->root;
	while (g_sentry != x)
	{
		y = x;
		if (node->data < x->data)
		{
			x = x->left;
		}else
			x = x->right;
	}
	node->parent = y;
	if (g_sentry == y)
	{
		tree->root = node;
	}else if (node->data < y->data)
	{
		y->left = node;
	}else
		y->right = node;
	node->left = g_sentry;
	node->right = g_sentry;
	node->color = static_cast<char>(RED);
	Node* p = node->parent;
	while(p != g_sentry)
	{
		++ p->size;
		p = p->parent;
	}
	RBInsertFixup(tree, node);
}

    而删除和插入相反,再删除以后,需要把这条路径上所有的值都-1:

Node* treeDelete(Tree* tree, Node* node)
{
	assert(g_sentry != node);
	Node* p = node->parent;
	while(p != g_sentry)
	{
		-- p->size;
		p = p->parent;
	}
	
	Node* y = g_sentry;
	if (g_sentry == node->left
		|| g_sentry == node->right)
	{
		y = node;
	}else
		y = treeSuccessor(node);
	Node* x = g_sentry;
	if (g_sentry != y->left)
	{
		x = y->left;
	}else
		x = y->right;
	x->parent = y->parent;
	if (g_sentry == y->parent)
	{
		tree->root = x;
	}else if (y == y->parent->left)
	{
		y->parent->left = x;
	}else
		y->parent->right = x;
	if (y != node)
	{
		node->data = y->data;
	}
	if (y->color == static_cast<char>(BLACK))
	{
		RBDeleteFixup(tree, x);
	}

	return y;
}

    然后在遍历输了的时候增加输出结点数:

void inorderTreeWalk(Node* node)
{
	if (g_sentry != node)
	{
		inorderTreeWalk(node->left);
		cout << node->data << "--" << node->size << " ";
		inorderTreeWalk(node->right);
	}
}

    然后就是求出第i大的顺序统计数的函数了:

Node* select(Node* node, int order)
{
	int r = node->left->size + 1;
	if (order == r)
	{
		return node;
	}else if (order < r)
	{
		return select(node->left, order);
	}else
	{
		return select(node->right, order - r);
	}
}

    根据包含的结点数来判断当前的第i大的数据的位置,也可以根据相应的结点得出他位于序列的第几:

int rank(Tree* tree, Node* node)
{
	int order = node->left->size + 1;
	Node* y = node;
	while (y != tree->root)
	{
		if (y == y->parent->right)
		{
			order += (y->parent->left->size + 1);
		}
		y = y->parent;
	}
	return order;
}

    测试代码如下:

int _tmain(int argc, _TCHAR* argv[])
{
	init();
	for (int i = 0; i < 10; ++ i)
	{
		Node* p = createNode(i);
		treeInsert(g_tree, p);
	}
	Node* q = createNode(15);
	treeInsert(g_tree, q);
	for (int i = 20; i < 100; ++ i)
	{
		Node* p = createNode(i);
		treeInsert(g_tree, p);
	}
	inorderTreeWalk(g_tree->root);
	cout << endl;
	cout << "red black tree hight = " << getHight()<< endl;
	Node* pFind = treeSearch(g_tree->root, 15);
	assert(g_sentry != pFind);
	cout << "find data = " << pFind->data << endl;
	pFind = iterativeTreeSearch(g_tree->root, 15);
	assert(g_sentry != pFind);
	cout << "iterative find data = " << pFind->data << endl;
	Node* predecessor = treePredecessor(pFind);
	assert(g_sentry != predecessor);
	cout << pFind->data << "'s predecessor is " << predecessor->data << endl;
	Node* successor = treeSuccessor(pFind);
	assert(g_sentry != successor);
	cout << pFind->data << "'s successor is " << successor->data << endl;

	Node* orderSelect = select(g_tree->root, 11);
	assert(orderSelect != g_sentry);
	cout << "order 11 is " << orderSelect->data << endl;
	int order = ::rank(g_tree, orderSelect);
	cout << "orderSelect is at " << order << endl;

	pFind = treeDelete(g_tree, pFind);
	assert(pFind == q);
	cout << "delete pFind success" << endl;
	inorderTreeWalk(g_tree->root);
	cout << endl;
	cout << "red black tree hight = " << getHight()<< endl;
	pFind = treeSearch(g_tree->root, 15);
	assert(g_sentry == pFind);
	cout << "not find 15" << endl;
	orderSelect = select(g_tree->root, 11);
	assert(orderSelect != g_sentry);
	cout << "order 11 is " << orderSelect->data << endl;

	finit();
	return 0;
}

    输出结果如下:orderstatistics-result

orderstatistics-result

    可以去计算一下,输出的结果值是否正确。

总结

    总体来说,复杂度和红黑树相当。在效率上对于红黑树并没有多大的影响,但是确可以方便的输出当前第i个顺序统计量,并且也可以根据结点才得出它的所包含的结点数,对于一些特定的场合,比如说,需要知道第i大的值的地方是比较有用的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Z小偉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值