算法导论 第14章 数据结构的扩张(四) 14-1 最大重叠点

本文介绍了一种基于红黑树的数据结构——最大重叠点(POM)树,用于求解多个区间最大重叠点的问题。文章详细阐述了POM树的设计思路,包括基础数据结构的选择、附加信息的维护、新操作的设计,并提供了完整的C++实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目

   

    习题14.3-6我们依然运用数据结构扩张的四个步骤来解决这个问题。


步骤1:选取基础数据结构

    我们选择红黑树作为基础数据结构来扩张,红黑树是这章的主题,扩张之后所得的树称之为最大重叠点(POM)树。由于要求得区间的最大重叠点,因而当然的,我们的节点必然包含一个域存储以该节点为根的子树的最大POM,即为pom域。


步骤2:附加信息

    除了pom域之外,为了能够有效地维护pom域,我们还将增加另外三个域:对于节点x而言,p,当x为某区间的左端点时,p为1;若为右端点,则为-1;p_sum,记录以x为根的子树中所有p的和;max,记录以x为根的子树中p的最大值,其实max就是节点x中的pom值重叠的次数。


步骤3:对信息的维护

    新增加的三个域都是为了能够有效维护pom而增加的,结合下面的更新某个节点信息的update函数我们来讨论如何维护这些信息。

void update(node *curr)
{
	curr->p_sum = curr->left->p_sum + curr->p + curr->right->p_sum;
	curr->max = Max(curr->left->max, curr->left->p_sum + curr->p,
		curr->left->p_sum + curr->p + curr->right->max);
	if (curr->left != nil && curr->max == curr->left->max) curr->pom = curr->left->pom;
	else if (curr->right != nil &&
		curr->max == curr->left->p_sum + curr->p + curr->right->max)curr->pom = curr->right->pom;
	else curr->pom = curr->key;
}

    函数第四行计算当前节点curr的max,最大值是左子树的max,左子树的p_sum与curr的p的和,以及左子树的p_sum、curr的p还有右子树的max的和,这三者之间的最大值。关于后两者的计算,可以这样理解:当树的大小从局部左子树扩张到当前节点,即包含节点时,左子树中可能既存在某些区间的左端点,也有右端点。那么对于当前节点而言,它势必不在这个区间中,因此不能够是左子树的max和curr的p值之和,而是p_sum与p的和,同理后后者也是如此。update函数最后的if语句则判断以x为根的子树中最大重叠点是哪个,这主要通过判断由上步算出的max与哪个值相等来确定。

    明显地,该函数的执行时间为O(1)。同习题14.3-6,插入和删除过程的更新过程是不能做到自顶向下的,只能在确定位置之后,在自底向上去更新,查找时间是O(lgn),向上更新也是,因而插入和删除的渐进时间并没有改变。


步骤4:设计新操作

     除了update函数,还将增加findPom函数返回最大重叠点以及自底向上的更新函数keep。


     OK,解决这四步,稍微对红黑树其他操作做些修改就可以构造出一颗最大重叠点树。下面是该树的C++实现代码:

/*****求多个区间的最大重叠点,姑且称为最大重叠点树,红黑树扩张
*增加了p,p_sum,max,pom等数据成员
*增加了update和keep函数
*微调insert,左右旋以及erase函数
*/
#include<iostream>
//#include<cstdlib>

using namespace std;
enum COLOR { red, black };//枚举,定义颜色
#define Max(i,j,k) (i > j ? (i > k ? i : k) : (j > k) ? j : k);
const int MAX = 0x7fffffff;

class node
{
private:
	friend class POMTree;
	node *parent;
	node *left;
	node *right;
	int key;
	int p;//左端点为1,右端点为-1
	int p_sum;//以当前节点为根的子树中p的和
	int max;//以当前节点为根的子树中p的和的最大值
	int pom;//使其取到最大值的关键字,即此子树的最大重叠点
	COLOR color;
	node(){}//默认构造函数,只供创建nil时调用
public:
	node(int k, COLOR c = red) :key(k), p_sum(0), max(0), pom(k), color(c),
		parent(NULL), left(NULL), right(NULL){}
	void print()const
	{
		printf("key:%-10d POM:%-10d max:%-10d p_sum:%-10d p:%-10d", key, pom, max, p_sum, p);
		if (color == red) printf("red\n");
		else printf("black\n");
	}
	//省略指针域的getter和setter
};


class POMTree
{
private:
	static node *nil;//哨兵,静态成员,被整个POMTree类所共有
	node *root;
	POMTree(const POMTree&);//禁止复制构造
	POMTree operator=(const POMTree&);//禁止赋值
	void leftRotate(node*);//左旋
	void rightRotate(node*);//右旋
	void insertFixup(node*);//插入节点后红黑性质调整
	void eraseFixup(node*);//删除节点后红黑性质调整
	void update(node*);//更新节点信息
	void keep(node*);//自底向上更新路径信息
public:
	POMTree() :root(nil)
	{
		root->parent = nil;
		root->left = nil;
		root->right = nil;
		root->color = black;
		root->key = MAX; root->p = 0;
		root->p_sum = 0; root->max = 0;
		root->pom = MAX;
	}
	POMTree(node *rbt) :root(rbt){}//复制构造函数,用于创建子红黑树对象
	void insert(int, bool);//插入
	void create();//创建红黑树
	void erase(int);//删除
	node* locate(int)const;//查找
	node* minMum()const;//最小值
	node* maxMum()const;//最大值
	node* successor(int)const;//找后继
	node* predecessor(int)const;//前驱
	void preTraversal()const;//先根遍历
	void inTraversal()const;//中根遍历
	void destroy();//销毁红黑树
	void findPom()const { root->print(); }
	bool empty()const{ return root == nil; }//判空
};

node *POMTree::nil = new node;//定义静态成员nil

void POMTree::update(node *curr)
{
	curr->p_sum = curr->left->p_sum + curr->p + curr->right->p_sum;
	curr->max = Max(curr->left->max, curr->left->p_sum + curr->p,
		curr->left->p_sum + curr->p + curr->right->max);
	if (curr->left != nil && curr->max == curr->left->max) curr->pom = curr->left->pom;
	else if (curr->right != nil &&
		curr->max == curr->left->p_sum + curr->p + curr->right->max)curr->pom = curr->right->pom;
	else curr->pom = curr->key;
}

void POMTree::keep(node *curr)
{
	while (curr != nil)
	{
		update(curr);
		curr = curr->parent;
	}
}

void POMTree::leftRotate(node *curr)
{
	if (curr->right != nil)
	{//存在右孩子时才能左旋
		node *rchild = curr->right;
		curr->right = rchild->left;
		if (rchild->left != nil)
			rchild->left->parent = curr;
		rchild->parent = curr->parent;
		if (curr->parent == nil)
			root = rchild;
		else if (curr == curr->parent->left)
			curr->parent->left = rchild;
		else curr->parent->right = rchild;
		curr->parent = rchild;
		rchild->left = curr;
		rchild->p_sum = curr->p_sum;
		rchild->max = curr->max;
		rchild->pom = curr->pom;
		update(curr);
	}
}

void POMTree::rightRotate(node *curr)
{
	if (curr->left != nil)
	{//存在左孩子时才能右旋
		node *lchild = curr->left;
		curr->left = lchild->right;
		if (lchild->right != nil)
			lchild->right->parent = curr;
		lchild->parent = curr->parent;
		if (curr->parent == nil)
			root = lchild;
		else if (curr == curr->parent->left)
			curr->parent->left = lchild;
		else curr->parent->right = lchild;
		lchild->right = curr;
		curr->parent = lchild;
		lchild->p_sum = curr->p_sum;
		lchild->max = curr->max;
		lchild->pom = curr->pom;
		update(curr);
	}
}


void POMTree::insert(int k, bool start)
{
	node *pkey = new node(k),
		*p = nil, *curr = root;
	if (start)
	{
		pkey->p = 1;
		pkey->p_sum = 1;
		pkey->max = 1;
		pkey->pom = k;
	}
	else
	{
		pkey->p = -1;
		pkey->p_sum = -1;
		pkey->max = 0;
		pkey->pom = k;
	}
	while (curr != nil)
	{//找插入位置
		p = curr;//记住当前节点父亲
		if (k < curr->key)//往左找
			curr = curr->left;
		else curr = curr->right;//向右找
	}
	pkey->parent = p;
	if (p == nil)//插入的是第一个节点
		root = pkey;
	else if (k < p->key)
		p->left = pkey;
	else p->right = pkey;
	pkey->left = pkey->right = nil;
	keep(pkey->parent);
	insertFixup(pkey);//调整
}


void POMTree::insertFixup(node *curr)
{
	while (curr->parent->color == red)
	{//父亲为红节点时才需要进入循环调整
		if (curr->parent == curr->parent->parent->left)
		{//父亲是祖父左孩子
			node *uncle = curr->parent->parent->right;
			if (uncle != nil && uncle->color == red)
			{//情况1,叔叔节点存在且为红色
				curr->parent->color = black;
				uncle->color = black;
				curr->parent->parent->color = red;
				curr = curr->parent->parent;
			}
			else if (curr == curr->parent->right)
			{//情况2,叔叔节点为黑色,且当前节点是父亲右孩子
				curr = curr->parent;
				leftRotate(curr);//将父节点左旋,以转变为情况3
			}
			else
			{//情况3,叔叔节点为黑色,且当前节点是父亲左孩子
				curr->parent->color = black;
				curr->parent->parent->color = red;
				rightRotate(curr->parent->parent);
			}
		}
		else
		{//父亲是祖父右孩子,与上面三种情况对称
			node *uncle = curr->parent->parent->left;
			if (uncle != nil && uncle->color == red)
			{//情况1
				curr->parent->color = black;
				uncle->color = black;
				curr->parent->parent->color = red;
				curr = curr->parent->parent;
			}
			else if (curr == curr->parent->left)
			{//情况2
				curr = curr->parent;
				rightRotate(curr);
			}
			else
			{//情况3
				curr->parent->color = black;
				curr->parent->parent->color = red;
				leftRotate(curr->parent->parent);
			}
		}
	}
	root->color = black;//跳出循环时将根节点置为黑色
}


void POMTree::create()
{
	int low, high;
	cout << "Enter element(s),CTRL+Z to end" << endl;//换行后CTRL+Z结束输入
	while (cin >> low >> high)
	{
		insert(low, true);
		insert(high, false);
	}
	cin.clear();
}


void POMTree::preTraversal()const
{
	node *curr = root;
	if (curr != nil)
	{
		curr->print();
		POMTree LEFT(curr->left);//继续左子树先根遍历
		LEFT.preTraversal();
		POMTree RIGHT(curr->right);
		RIGHT.preTraversal();
	}
}


void POMTree::inTraversal()const
{
	node *curr = root;
	if (curr != nil)
	{
		POMTree LEFT(curr->left);
		LEFT.inTraversal();
		curr->print();
		POMTree RIGHT(curr->right);//继续右子树中根遍历
		RIGHT.inTraversal();
	}
}


node* POMTree::successor(int k)const
{
	node *curr = locate(k);
	if (curr->right != nil)
	{//若右子树不为空,则后继为右子树最小值
		POMTree RIGHT(curr->right);
		return RIGHT.minMum();
	}
	node *p = curr->parent;
	while (p != nil && curr == p->right)
	{//否则为沿右指针一直向上直到第一个拐弯处节点
		curr = p;
		p = p->parent;
	}
	return p;
}


node* POMTree::minMum()const
{
	node *curr = root;
	while (curr->left != nil)
		curr = curr->left;
	return curr;
}


node* POMTree::maxMum()const
{
	node *curr = root;
	while (curr->right != nil)
		curr = curr->right;
	return curr;
}


node* POMTree::predecessor(int k)const
{
	node *curr = locate(k);
	if (curr->left != nil)
	{//若左子树不为空,则前驱为左子树最大值
		POMTree LEFT(curr->left);
		return LEFT.maxMum();
	}
	node *p = curr->parent;
	while (p != nil && curr == p->left)
	{//否则为沿左指针一直往上的第一个拐弯处节点
		curr = p;
		p = p->parent;
	}
	return p;
}


void POMTree::erase(int k)
{
	node *curr = locate(k), *pdel, *child;
	if (curr == nil)
	{
		cout << "Error:no data" << endl;
		return;
	}
	if (curr->left == nil || curr->right == nil)//决定删除节点
		pdel = curr;//若当前节点至多有一个孩子,则删除它
	else pdel = successor(k);//否则若有两孩子,则删除其后继
	node *pdel_parent = pdel->parent;//记下被删节点父亲
	if (pdel->left != nil)//记下不为空的孩子
		child = pdel->left;
	else child = pdel->right;
	child->parent = pdel_parent;
	if (pdel_parent == nil)//若删除的是根节点
		root = child;
	else if (pdel == pdel_parent->left)//否则若被删节点是其父亲左孩子
		pdel_parent->left = child;
	else pdel_parent->right = child;
	if (curr != pdel)
		curr->key = pdel->key;//若被删的是后继,则将后继值赋给当前节点
	keep(pdel_parent);
	if (pdel->color == black)//被删节点为黑色时才调整
		eraseFixup(child);
	delete pdel;//释放所占内存
}


void POMTree::eraseFixup(node *curr)
{
	while (curr != root && curr->color == black)
	{//当前不为根,且为黑色
		if (curr == curr->parent->left)
		{//若其是父亲左孩子
			node *brother = curr->parent->right;//兄弟节点肯定存在
			if (brother->color == red)
			{//情况1,兄弟是红色,转变为情况2,3,4
				brother->color = black;
				curr->parent->color = red;
				leftRotate(curr->parent);
				brother = curr->parent->right;
			}
			if (brother->left->color == black && brother->right->color == black)
			{//情况2,兄弟是黑色,且两孩子也是黑色,将当前节点和兄弟去一重黑色
				brother->color = red;
				curr = curr->parent;
			}
			else if (brother->right->color == black)
			{//情况3,兄弟左孩子为红,右孩子为黑,转变为情况4
				brother->color = red;
				brother->left->color = black;
				rightRotate(brother);
				brother = curr->parent->right;
			}
			else
			{//情况4,右孩子为黑色,左孩子随意
				brother->color = curr->parent->color;
				curr->parent->color = black;
				brother->right->color = black;
				leftRotate(curr->parent);
				curr = root;
			}
		}
		else
		{//若其是父亲右孩子,与上面四中情况对称
			node *brother = curr->parent->left;
			if (brother->color == red)
			{//情况1
				brother->color = black;
				curr->parent->color = red;
				rightRotate(curr->parent);
				brother = curr->parent->left;
			}
			if (brother->right->color == black && brother->left->color == black)
			{//情况2
				brother->color = red;
				curr = curr->parent;
			}
			else if (brother->left->color == black)
			{//情况3
				brother->color = red;
				brother->right->color = black;
				leftRotate(brother);
				brother = curr->parent->left;
			}
			else
			{//情况4
				brother->color = curr->parent->color;
				curr->parent->color = black;
				brother->left->color = black;
				rightRotate(curr->parent);
				curr = root;
			}
		}
	}
	curr->color = black;//结束循环时将当前节点置为黑色
}


node* POMTree::locate(int k)const
{
	node *curr = root;
	while (curr != nil && curr->key != k)
	{
		if (k < curr->key)curr = curr->left;
		else curr = curr->right;
	}
	return curr;
}


void POMTree::destroy()
{
	while (root != nil)
	{
		//cout << "erase: " << root->key << endl;
		erase(root->key);
	}
	delete nil;
}

int main()
{//16 21   8 9   25 30   5 8   15 23   19 20   17 19   26 26   0 3   6 10
	//16 21   8 9   25 30   5 8   15 23  17 19   26 26   0 3   6 10   19 20 
	//7 9   5 8   0 3   6 10
	POMTree ptree;
	ptree.create();
	cout << "inTraversal-----" << endl;
	ptree.inTraversal();
	cout << "preTraversal----" << endl;
	ptree.preTraversal();
	int low,high,choice;
	cout << "Enter your choice,1-insert,2-delete,3-POM,4-print,5-exit" << endl;
	do
	{
	cin >> choice;
	switch(choice)
	{
	case 1:
	cout << "Enter a interval,ex-> a b (a <= b): ";
	cin >> low >> high;
	ptree.insert(low,true);
	ptree.insert(high,false);
	break;
	case 2:
	cout << "Enter a interval,ex -> a b (a <= b): ";
	cin >> low >> high;
	ptree.erase(low);
	ptree.erase(high);
	break;
	case 3:
	ptree.findPom();
	break;
	case 4:
	cout << "inTraversal-----" << endl;
	ptree.inTraversal();
	cout << "preTraversal----" << endl;
	ptree.preTraversal();
	break;
	case 5:
	ptree.destroy();
	return 0;
	default:
	cout << "Bad choice" << endl;
	}
	//cout << "Enter your choice,1-insert,2-delete,3-POM,4-print,5-exit" << endl;
	}while(choice >= 1 && choice <= 5);
	ptree.destroy();
	return 0;
}


对于main函数中的三组测试数据,细心的朋友可能已经发现第一组和第二组只有区间[19,20]的输入顺序不同,这也正是本代码存在的一个小bug,即如果存在这样的两个区间[x,y]和[y,z],x <= y <= z,且y恰好是这些区间的最大重叠点,那么要保证得到正确结果,则需要将较靠后的区间先输入,即[y,z]先输入。这个bug我调了好久都没有找回解决方案,我估计还是更新或者初值设定有问题,如果哪位朋友知道是什么原因,希望能告知我,谢谢!


那么为了避免这个小bug,我们可以先将区间按左端点或者右端点从大到小排序,再输入;如果输入中不存在这样的区间,那最好了。







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值