二叉查找树:
1.若左子树非空,则左子树所有结点小于根结点。
2.若右子树非空,则右子树所有结点大于根结点。
3.其左右子树也为二叉查找树。
二叉排序树可用作字典,也可做优先队列。其主要支持的操作有:Search,Insert,Delete。这三个操作的时间复杂度都与树高有关,为O(h)。但由于插入顺序的不同,二叉树的构造也不同,若按从小到大插入或从大到小插入,则二叉树将退化为链表,其三个操作的时间复杂度将为O(n)。为避免这一情况,就需要对插入删除操作做一些修正,使树尽量保持平衡。这里用到的就是红黑树。
红黑树:
1. 每个结点为红色或黑色。
2. 根结点为黑色。
3. 每个叶结点(NIL)为黑色。
4. 若一个结点为红色,则他的两个儿子都是黑色的。
5. 对每个结点,从该结点到其子孙结点的所有路径包含的黑色结点皆相同。
这五条性质保证了红黑树没有一条路径比其他路径长两倍,是接近平衡的,所以,就要在插入、删除时进行修正,使其保持这五条性质。
enum Color
{
red,black
};
template <class DataType>
struct BNode
{
DataType data;
int sum;
Color color;
BNode<DataType> *lchild, *rchild, *parent;
};
class RBTree
{
public:
RBTree();
RBTree(vector<int>&);
~RBTree(){ Release(root); }
void SearchRBT(int k){ Print(Search(k)); }//查询
void DeleteRBT(int k){ Delete(Search(k)); }//删除
void InsertRBT(int k)//插入
{
BNode<int> *s = new BNode<int>;
s->data = k;
s->color = red;
s->parent = nil;
s->lchild = nil;
s->rchild = nil;
s->sum = 0;
Insert(s);
}
int Max()//返回最大值
{
BNode<int>* p = root;
while (p != nil)
p = p->rchild;
return p->data;
}
int Min()//返回最小值
{
BNode<int>* p = root;
while (p != nil)
p = p->lchild;
return p->data;
}
bool empty()
{
if (root == nil)
return true;
else
return false;
}
private:
BNode<int> *root;//树根
BNode<int> *nil;//哨兵用来代表NIL,其颜色为黑
void Release(BNode<int>* );//删除整棵树
BNode<int> *Search(int );//查询并返回结点
void Print(BNode<int>* );//打印指定结点信息
void LeftRotate(BNode<int>* );//左旋
void RightRotate(BNode<int>* );//右旋
void Insert(BNode<int>* );//插入
void Insert_Fixup(BNode<int>* );//插入操作修正
void Delete(BNode<int>* );//删除
void Delete_Fixup(BNode<int>* );//删除操作修正
};
RBTree::RBTree()
{
nil = new BNode < int > ;
nil->color = black;
root = nil;
}
RBTree::RBTree(vector<int>& a)
{
nil = new BNode < int > ;
nil->color = black;
root = nil;
for (size_t i = 0; i < a.size(); ++i)
{
BNode<int> *s = new BNode<int>;
s->data = a[i];
s->color = red;
s->parent = nil;
s->lchild = nil;
s->rchild = nil;
s->sum = 0;
Insert(s);
}
}
void RBTree::Release(BNode<int>* bt)
{
if (bt != nil)
{
Release(bt->lchild);
Release(bt->rchild);
delete bt;
}
}
void RBTree::Print(BNode<int>* it)
{
if (it != nil)
cout << it->data << " repeat " << it->sum << "times." << endl;
else
cout << "Can't find." << endl;
}
首先在修正中将会用到两个操作,左旋和右旋,其原理如图:
Z Z
| |
X 对X左旋 Y
/ \ -----------------à / \
a Y ß----------------- X c
/ \ 对Y右旋 / \
b c a b
左旋和右旋并没有破坏二叉排序树的性质,只是改造了树的结构。
void RBTree::LeftRotate(BNode<int>* x)
{
BNode<int>* y = x->rchild;
x->rchild = y->lchild;
if (y->lchild != nil)
y->lchild->parent = x;
y->parent = x->parent;
if (x->parent == nil)
root = y;
else if (x == x->parent->lchild)
x->parent->lchild = y;
else
x->parent->rchild = y;
y->lchild = x;
x->parent = y;
}
void RBTree::RightRotate(BNode<int>* y)
{
BNode<int>* x = y->lchild;
y->lchild = x->rchild;
if (x->rchild != nil)
x->rchild->parent = y;
x->parent = y->parent;
if (y->parent == nil)
root = x;
else if (y->parent->lchild == y)
y->parent->lchild = x;
else
y->parent->rchild = x;
x->rchild = y;
y->parent = x;
}
一.查找操作
无论是普通的二叉排序树还是红黑树,查找操作是一致的,因为查找并没有改变树的结构的操作只是访问。
BNode<int>* RBTree::Search(int k)
{
BNode<int>* p = root;
while (p != nil)
{
if (p->data < k)
p = p->rchild;
else if (p->data > k)
p = p->lchild;
else
break;
}
return p;
}
二.插入操作
新结点在插入前先涂成红色,然后在插入后对树进行修正,使其符合红黑树的五条性质。而除修正操作外其他操作与普通排序树并无二致。即按树性质查找,比当前位置关键字小,就查找左子树,大就查找右子树,直到当前位置为NIL,插入到当前位置。
void RBTree::Insert(BNode<int> *s)
{
BNode<int>* x = root;
BNode<int>* y = nil;
while (x != nil)
{
y = x;
if (s->data < x->data)
x = x->lchild;
else if (s->data == x->data)
{
++x->sum;
return;
}
else
x = x->rchild;
}
s->parent = y;
if (y == nil)
root = s;
else if (s->data < y->data)
y->lchild = s;
else
y->rchild = s;
Insert_Fixup(s);
}
现在来看修正操作,修正操作就是通过重新上色和左旋右旋来保持红黑性质,因为再插入前此树就是一棵红黑树,把出现违背红黑树性质的结点向上移,如果能移到根结点,那么很容易就能通过直接修改根结点来恢复红黑树的性质。直接通过修改根结点来恢复红黑树应满足的性质。穷举所有的可能性,之后把能归于同一类方法处理的归为同一类,不能直接处理的化归到下面的几种情况。所有在插入一个新元素后可能出现的情况:
1. 当前结点的父结点为黑色,符合所有性质,不修正。
2. 当前结点为根结点,将根结点涂黑,符合所有性质,修正完毕。
3. 当前结点的父结点为红,其叔叔结点(父结点的兄弟结点)也为红,则违反第4条性质,这时将当前结点的父结点和叔叔结点涂成黑色,将爷爷结点(父结点的父结点)涂成红色,因为父结点为红色,根据性质,当前结点必定存在爷爷结点。然后,将爷爷结点作为当前结点重新进入修正判断,因为这些操作只能保证当前结点符合了性质,但对爷爷结点的改变无法保证它的第4条性质成立,所以需要重新判断。
4. 当前结点的父结点为红色,叔叔结点为黑色,且当前结点为父结点的右子结点。针对这种情况将父结点作为当前结点然后对新的当前结点做左旋操作,此操作并没有改变任何性质,只是将情况转换为当前结点的父结点为红色,叔叔结点为黑色,且当前结点为父结点的左子结点。
5. 当前结点的父结点为红色,叔叔结点为黑色,且当前结点为父结点的左子结点。这种情况下,将当前结点父结点涂黑,爷爷结点涂红,然后对爷爷结点右旋,这样,这棵树就符合红黑树的五条性质了。
void RBTree::Insert_Fixup(BNode<int>* s)
{
while (s->parent->color == red)
{
BNode<int>* y;
if (s->parent == s->parent->parent->lchild)
y = s->parent->parent->rchild;
else
y = s->parent->parent->lchild;
if (y->color == red)
{
s->parent->color = black;
y->color = black;
s->parent->parent->color = red;
s = s->parent->parent;
}
else if (s == s->parent->rchild)
{
s = s->parent;
LeftRotate(s);
}
else
{
s->parent->color = black;
s->parent->parent->color = red;
RightRotate(s->parent->parent);
}
}
root->color = black;
}
三.删除操作
若待删除的结点没有子结点,则直接删除;若只有一个子结点,则将其子结点与其父结点连接,然后删除该结点;若有两个子结点,则删除该结点的后继结点,即大于该结点的最小子结点(即该结点右子树的最左结点),然后将被删除结点的结点替换掉当前结点,这样将问题转换为删除一个没有左子结点的情况。删除操作同样存在可能改变红黑性质的因素,所以需要分情况修正。
void RBTree::Delete(BNode<int>* z)
{
BNode<int> *y,*x;
if (z->lchild == nil || z->rchild == nil)
y = z;
else
{
BNode<int>* p = z->rchild;
while (p->lchild != nil)
p = p->lchild;
y = p;
}
if (y->lchild != nil)//x为y的唯一子结点或nil
x = y->lchild;
else
x = y->rchild;
x->parent = y->parent;
if (y->parent != nil)
root = x;
else if (y == y->parent->lchild)
y->parent->lchild = x;
else
y->parent->rchild = x;
if (y != z)
{
z->data = y->data;
z->sum = y->data;
z->color = y->color;
}
if (y->color == black)
Delete_Fixup(x);
delete y;
}
X初始为被删结点的子结点,可能出现的情况:
1. x为红色,直接涂黑。说明被删结点一定是黑色,为保证性质5,涂黑即可保持每条路的结点数相等。将此情况转换为情况
2. x为根结点且为黑,什么也不做。
3. x为黑色且其兄弟结点w为红色,将x的父结点涂红,w涂黑,然后对x的父结点左旋。将情况转换为4,5,6
4. x为黑色,其兄弟结点w为黑色且w的左右孩子皆为黑色,这时将w涂红,令x的父结点为新结点,重新进入算法。
5. x为黑色,其兄弟w为黑色且左孩子为红色右孩子为黑色,这时交换w和左孩子颜色,然后对w右旋,转换为情况4
6. x为黑色,其兄弟w为黑色且右孩子为红色,这时将w改为x父结点的颜色,然后x的父结点和w的右孩子涂黑,对x父结点左旋,此时修正完毕,令根结点为x,重新进入循环(即结束)。
void RBTree::Delete_Fixup(BNode<int>* x)
{
while (x != root&&x->color == black)
{
BNode<int>* w;
if (x == x->parent->lchild)
w = x->parent->rchild;
else
w = x->parent->lchild;
if (w->color == red)
{
w->color = black;
x->parent->color = red;
if (x == x->parent->lchild)
LeftRotate(x->parent);
else
RightRotate(x->parent);
}
else if (w->lchild->color == black&&w->rchild->color == black)
{
w->color = red;
x = x->parent;
}
else if (w->rchild->color == black)
{
w->lchild->color = black;
w->color = red;
RightRotate(w);
}
else
{
w->color = x->parent->color;
x->parent->color = black;
w->rchild->color = red;
if (x == x->parent->lchild)
LeftRotate(x->parent);
else
RightRotate(x->parent);
x = root;
}
}
x->color = black;
}