算法导论 14章 数据结构的扩张(二) 区间树

区间树——红黑树的扩张

          将红黑树进行扩张以支持由区间构成的动态集合,其节点主要除红黑树节点常规信息之外,还有一个区间信息,这样的一颗树称之为区间树。我们将利用14.2节总结的红黑树扩张的四个步骤来讨论如何进行扩充以得到区间树。

步骤1:基础数据结构

        毫无疑问,我们将选择红黑树。该区间树的每个节点有一个区间信息,对于节点x,即为int[x],用low表示int[x]的左端点,同时low还将作为该节点的关键字,这样中序遍历的时候就可以按照左端点的次序依次输出各区间了,high表示int[x]的右端点,其中表示的区间为[low,high],闭区间。

步骤2:附加信息

       为了该树的某些操作,我们还将添加一个max域,max[x]表示以x为根的子树中,所有区间的右端点的最大值。

步骤3:对信息的维护

         对于每一次的插入和删除一个区间,很显然需要的时间为O(lgn)。而对于给定的节点x,我们可以根据该节点区间以及左右节点得出max值,即:

                  max[x] = MAX(high[int[x]],max[left[x]],max[right[x]])。

根据红黑树的扩张定理以及在习题14.2-2中证明的那样,在旋转过程中max域的更新只需在O(1)就可以完成。

步骤4:设计新操作

        由于是个动态集合,我们经常需要插入、删除和查找,对于前两者,已有的红黑树操作不需任何改变即能满足要求,因此,我们只需要提供该区间树独特的查找操作search即可。


        对于任意的两个区间i和i‘,如果重叠,那么说明它们满足low[i] <= high[i’]以及low[i‘] <= high[i]。任意的两个区间之间存在三种可能的关系:

        a) i和i'重叠;

        b) i在i'左边,即high[i] < low[i'];

        c) i在i'右边,即high[i'] < low[i]。

如下图所示:

下图是一棵区间树,其区间分别为:16 21   8 9   25 30   5 8   15 23   17 19   26 26   0 3   6 10   19 20

对于区间树新增加的search操作,有如下伪代码:

search(T, i)
{
	x <- root[T];
	while (x != nil && i does not overlap int[x])
	{
		if (left[x] != nil && max[left[x]] >= low[i])
			x <- left[x];
		else x <- right[x];
	}
	return x;
}

由以上算法可以看出,每经过一次迭代,查找就下降一层,直到找满足条件或者x指向nil,因此search的时间复杂度与树高成正比,即O(lgn)。假设我们要在上述区间树中查找一个与区间[22,25]i重叠的区间。根据算法所呈现的,首先,x从根节点开始,可知int[x]不与i重叠,由于max[left[int[x]]]为23,大于22,因而继续往左搜寻;在节点[8,9]处,依然不与i重叠,而max[left[int[x]]]为10,小于22,那么应该向右搜寻;来到节点[15,23]处,可知与区间i重叠,则结束并返回该节点指针。

       现在我们来重点讨论该算法的正确性,即讨论while循环体如何保证在该树中如果有这样一个区间,则一定可以找到。在while循环中的if语句中,若条件均不满足,即left[x] == nil 或者max[left[x]] < low[i],对于前者,显然左子树不包含这样的区间,应该继续向右搜寻,对于后者由于max[left[x]] < low[i],则可以推断,对于x的左子树中的任意区间i'有:

              high[i'] <= max[left[x]] < low[i],显然左子树不可能包含这样的区间,那么应该继续向右子树搜寻。

若if语句中的条件满足,我们将继续向左搜寻,那么如果左子树依然不存在这样的区间的时候,对于尚未搜寻过的右子树是否存在这样的区间呢?答案是肯定不会有。由于if条件满足,那么存在max[left[x]] >= low[i],对于左子树而言则必然存在区间i1有:

                    high[i1] == max[left[x]] >= low[i];由于在左子树没有这样的区间,根据之前的分析,只有区间i1位于i的右边,即:

                    high[i] < low[i1]。区间树中左端点是关键字,那么对于右子树中的任意一个区间i2,有:

                    low[i2] >= low[i1] > high[i]。即右子树中的所有区间均在区间i的右边,自然不可能重叠,因此,如果左子树不存在这样的区间,那么右子树也是不可能存在的;如果左子树存在这样的区间,右子树也是可能存在的,不过这两者没有因果关系的。search操作只需找到一个即可。

         其实,如果我们可以对区间树稍作修改,或者说当初创建这种数据结构的时候,在每个节点中额外添加的信息不是max,而是min,以存储该节点所在子树的左端点的最小值,且将右端点作为节点的关键字。那么我们可以先在右子树搜索,在右子树不存在这样的区间的时候左子树也不可能存在。当然,search操作要做适当更改。一切其实都是对称的.。


习题 14.3-1 见稍后给出的代码


习题 14.3-2

         去掉所有判断条件中的等号“==”


习题 14.3-3

         将while循环中的判断是否重叠语句提到循环体内,记下当前满足的区间的左端点以及其地址,在下次遇到这样的区间的时候比较该区间的左端点与之前已记录的区间的左端点值大小以判断是否要做修改,最后返回该指针即可。代码见后。


习题 14.3-4

         对while循环稍作修改,遇到一个满足的区间即输出,而不是返回其地址,并且要遍历左右子树。代码见后。


习题14.3-5

        对while循环体的条件做修改,不仅仅只是重叠,要将条件严苛地变为必须满足左右端点相等,而且对于向哪个分支走的判断也有些许变化。代码见后。


习题 14.3-6 最小差幅树


区间树C++实现代码:

/****区间树Interval Tree——红黑树扩充而得
*增加了key_low,high和max数据成员
*node增加了getInterval函数,ITree增加了search系列函数
*修改了其他函数,以支持对上述三个域的维护
*/

#include<iostream>

using namespace std;
#define MAX(i,j,k) (i > j ? (i > k ? i : k):(j > k ? j : k));//求三个树最大值
enum COLOR { red, black };//枚举,定义颜色

class node
{
private:
	friend class ITree;
	node *parent;
	node *left;
	node *right;
	int key_low;//既是区间左端点,也是节点的关键字
	int high;//区间右端点
	int max;//以当前节点为根的子树中右端点最大值
	COLOR color;
	node(){}//默认构造函数,只供创建nil时调用
public:
	node(int kl, int hi, COLOR c = red) :key_low(kl), high(hi), color(c),
		max(hi), parent(NULL), left(NULL), right(NULL){}
	void getInterval()//打印区间信息
	{
		cout << '[' << key_low << ',' << high << "]\t" << max;
		if (color == red) cout << "\tred" << endl;
		else cout << "\tblack" << endl;
	}
	//省略指针域的getter和setter
};

class ITree
{
private:
	static node *nil;//哨兵,静态成员,被整个ITree类所共有
	node *root;
	ITree(const ITree&);//只声明不定义,以禁止复制构造
	ITree operator=(const ITree&);//禁止赋值
	void leftRotate(node*);//左旋
	void rightRotate(node*);//右旋
	void insertFixup(node*);//插入节点后区间性质调整
	void eraseFixup(node*);//删除节点后区间性质调整
public:
	ITree() :root(nil)
	{//设置nil的各个域
		root->parent = nil;
		root->left = nil;
		root->right = nil;
		root->color = black;
		nil->key_low = nil->high = nil->max = -1;
	}
	ITree(node *rbt) :root(rbt){}//复制构造函数,用于创建子区间树对象
	void insert(int, int);//插入
	void create();//创建区间树
	void erase(int);//删除
	node* locate(int)const;//查找
	node* search(int low, int high)const;//在区间树中查找与[low,high]重叠的区间
	node* searchMinLeftPoint(int low, int high)const;//在区间树中查找与[low,high]重叠的左端点最小的区间
	void searchAll(int low, int high)const;//在区间树中找出所有与[low,high]重叠的区间
	node* searchExactly(int low, int high)const;//在区间树中找出与[low,high]完全匹配的区间
	node* minMumInterval()const;//找到区间左端点最小值区间
	node* maxMumInterval()const;//区间左端点最大值区间
	//node* longestInterval()const;//查找长度最长的区间
	node* successor(int)const;//找后继区间
	node* predecessor(int)const;//找前驱区间
	void inTraversal()const;//中根遍历
	void destroy();//销毁区间树
	bool empty()const{ return root == nil; }//判空
};

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

void ITree::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->max = curr->max;
		curr->max = MAX(curr->max, curr->left->max, curr->right->max);
	}
}

void ITree::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->max = curr->max;
		curr->max = MAX(curr->max, curr->left->max, curr->right->max);
	}
}

void ITree::insert(int low, int high)
{
	node *pkey = new node(low, high),
		*p = nil, *curr = root;
	while (curr != nil)
	{//找插入位置
		p = curr;//记住当前节点父亲
		if (low < curr->key_low)//往左找
			curr = curr->left;
		else curr = curr->right;//向右找
	}
	pkey->parent = p;
	if (p == nil)//插入的是第一个节点
		root = pkey;
	else if (low < p->key_low)
		p->left = pkey;
	else p->right = pkey;
	pkey->left = pkey->right = nil;
	while (p != nil)
	{
		p->max = MAX(p->max, p->left->max, p->right->max);
		p = p->parent;
	}
	insertFixup(pkey);//调整
}

void ITree::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 ITree::create()
{
	int low, high;
	cout << "Enter interval(s),CTRL+Z to end" << endl;//换行后CTRL+Z结束输入
	while (cin >> low >> high)
		insert(low, high);
	cin.clear();
}

void ITree::inTraversal()const
{
	node *curr = root;
	if (curr != nil)
	{
		ITree LEFT(curr->left);
		LEFT.inTraversal();
		curr->getInterval();//打印区间信息
		ITree RIGHT(curr->right);//继续右子树中根遍历
		RIGHT.inTraversal();
	}
}

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

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

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

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

void ITree::erase(int kl)
{
	node *curr = locate(kl), *pdel, *child;
	if (curr == nil) return;
	if (curr->left == nil || curr->right == nil)//决定删除节点
		pdel = curr;//若当前节点至多有一个孩子,则删除它
	else pdel = successor(kl);//否则若有两孩子,则删除其后继
	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_low = pdel->key_low;//若被删的是后继,则将后继值赋给当前节点
		curr->high = pdel->high;
	}
	curr = child->parent;
	while (curr != nil)
	{
		curr->max = MAX(curr->max, curr->left->max, curr->right->max);
		curr = curr->parent;
	}
	if (pdel->color == black)//被删节点为黑色时才调整
		eraseFixup(child);
	delete pdel;//释放所占内存
}

void ITree::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* ITree::locate(int kl)const
{
	node *curr = root;
	while (curr != nil && curr->key_low != kl)
	{
		if (kl < curr->key_low)curr = curr->left;
		else curr = curr->right;
	}
	return curr;
}

node* ITree::search(int low, int high)const
{
	node *curr = root;
	while (curr != nil && (low > curr->high || curr->key_low > high))
		if (curr->left->max >= low) curr = curr->left;
		else curr = curr->right;
		return curr;
}

node* ITree::searchMinLeftPoint(int low, int high)const
{
	node *curr = root, *pMin = nil;
	int minPoint = 0x7fffffff;
	while (curr != nil)
	{
		if ((curr->key_low <= high && low <= curr->high) && curr->key_low < minPoint)
		{
			pMin = curr;
			minPoint = curr->key_low;
		}
		if (curr->left != nil && curr->left->max >= low)
			curr = curr->left;
		else curr = curr->right;
	}
	return pMin;
}

void ITree::searchAll(int low, int high)const
{//递归,左右两子树均可能存在满足条件的
	node *curr = root;
	if (curr->key_low <= high && low <= curr->high)
		curr->getInterval();
	if (curr->left != nil && curr->left->max >= low)
	{
		ITree LEFT(curr->left);
		LEFT.searchAll(low, high);
	}
	if (curr->right != nil)
	{
		ITree RIGHT(curr->right);
		RIGHT.searchAll(low, high);
	}
}

node* ITree::searchExactly(int low, int high)const
{
	node *curr = root;
	while (curr != nil)
	{
		if (curr->key_low == low && curr->high == high)
			return curr;
		if (curr->key_low >= low)//注意这里和其他search的一点区别
			curr = curr->left;
		else curr = curr->right;
	}
	return curr;
}

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

int main()
{//16 21   8 9   25 30   5 8   15 23   17 19   26 26   0 3   6 10   19 20
	ITree itree;
	cout << "creare-------------" << endl;
	itree.create();
	cout << "inTraversal--------" << endl;
	itree.inTraversal();
	cout << endl;
	itree.searchExactly(26, 26)->getInterval();
	cout << "search-------------" << endl;
	itree.search(22,25)->getInterval();
	cout << endl;
	itree.minMumInterval()->getInterval();
	cout << endl;
	itree.maxMumInterval()->getInterval();
	itree.erase(16);
	itree.erase(26);
	itree.erase(16);
	cout << endl;
	itree.inTraversal();
	itree.insert(13,19);
	cout << endl;
	itree.searchAll(17, 19);
	itree.inTraversal();
	itree.destroy();
	getchar();
	return 0;
}




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值