红黑树:
红黑树是具有下列着色性质的二叉查找树:
- 每一个节点或者是红色,或者是黑色
- 根是黑色的
- 如果一个节点是红色的,那么它的子节点必须是黑色的
- 从一个节点到一个null指针的每一条路径必须包含相同数目的黑色节点
着色法则的一个结论是,红黑树的高最多是2log(N+1),因此,查找操作保证是一种对数的操作。
当将一个新项插入到树中时,通常把新项作为树叶放到树中,如果把该项涂成黑色,那么肯定违反条件4,因为那会建立一条更长的黑节点的路径,因此,这一项必须涂成红色。如果它的父节点是黑的,那么插入完成,如果它的父节点已经是红色的,则得到连续的红色节点,这就违反了条件3。在这种情况下,我们必须调整该树以确保条件3满足(且又不引起条件4被破坏)。用于完成这项任务的基本操作是颜色的改变和树木的旋转。
假设将新插入的新节点标记为X,此时X为红色,如果X为根节点,那么将X标记为黑色
如果X不是根节点,当X的父节点(parent)和父节点的兄弟节点(uncle)都为红色时:
- 将parent和uncle都标记为黑色
- 将grand parent(祖父)标记为红色
- 对变色后的祖父节点重复这个过程
如图所示:
X为红色,X的parent和uncle均为红色,此时将p和u均标记为黑色,将g标记为红色,发现g为祖父节点,将g标记为黑色,退出。
如果X不是根节点,当X的parent节点为为红,x的uncle节点为黑时,分四种情况处理(这时处理步骤主要是旋转):
- 左左(parent节点是祖父节点的左节点,X节点是parent节点的左节点):
这时向右旋转g(可以想象为手提起P节点),然后交换p和g的颜色即可。
- 左右:
首先左旋,使得X的父节点p被x取代,同时父节点P成为X的左孩子,然后应用左左情况
- 右右:
和左左相同,镜像
- 右左:
首先右旋,使得X的父节点P被X取代,同时父节点P成为X的右孩子,然后应用右右情况
参考:
https://zhuanlan.zhihu.com/p/79980618?utm_source=cn.wiz.note
AVL树:
AVL树是其每个节点的左子树和右子树的高度最多差1的二叉查找树(空树的高度定义为-1)。可以证明,一颗AVL树的高度最多为1.44log(N+2)-1.328,实际高度只会比logN稍多一些。因此,除去可能的插入和删除外,所有的树操作都可以以时间Olog(N)执行,当进行插入操作时,我们需要更新通向根节点路径上的那些节点的所有平衡信息,而插入操作隐含着困难的原因在于,插入一个节点可能破坏AVL树的特性,如果发生这种情况,那么就要在考虑这一步插入完成之前恢复平衡的性质。事实上,这总可以通过对树进行简单的修正来做到,这个修正的操作称为旋转。
在一次插入之后,只有那些从插入点到根节点的路径上的节点的平衡可能被改变,因为只有这些节点的子树可能发生变化。当沿着这条路径上行到根并更新平衡信息时,我们可以发现一个节点,它的新平衡破坏了AVL条件。可以在这个节点重新平衡这个树,并可以证明这一重新平衡保证整个树满足AVL性质。
将必须平衡的节点称为a,由于任意节点最多有两个儿子,因此出现高度不平衡就需要a的两颗子树的高度差2。容易看出这种不平衡可能出现在这四种情况中
- 对a的左儿子的左子树进行一次插入
- 对a的左儿子的右子树进行一次插入
- 对a的右儿子的左子树进行一次插入
- 对a的右儿子的右子树进行一次插入
其中1和4时镜像对称,2和3是镜像对称。1和4可以理解为发生在树的“外围”,该情况可以通过对树的一次单旋转完成调整,而2和3可以理解为发生在树的“内部”,该情况通过稍微复杂些的双旋转来处理。
以下通过一些图来说明怎样进行旋转,需要注意的是,下图中的T代表的是抽象的树的开始不平衡的根节点。
第一种情况,使用右旋:
第四种情况,也就是上图的镜像:
第二种情况,先左旋再右旋:
实例:
第三种情况,先右旋再左旋:
参考:
https://www.cnblogs.com/zhuwbox/p/3636783.html
二叉树的遍历:
二叉树的遍历是指从根结点出发,按照某种次序依次访问二叉树中所有的结点,使得每个结点被访问一次且仅被访问一次。共有四种遍历方式,分别为:先序遍历、中序遍历、后序遍历、层序遍历。
二叉查找树:
对于树中的每个节点X,它的左子树中所有项的值均小于X中的项,而它的右子树中所有项的值均大于X中的项。
如下图所示,左面是二叉查找树,而右面不是:
contains方法:
如果在树T中存在着含有项X的节点,那么这个操作需要返回true,如果这样的节点不存在,则返回false。
findMin和findMax方法:
这两个方法返回指向树中最小元素和最大元素的节点的指针。
insert方法:
为了将X插入到树T中,可以像contains那样沿着树查找,如果找到X,则什么都不用做,否则,将X插入到所遍历的路径的最后一点上。
remove方法:
当发现了要删除的节点时,我们需要考虑几种可能的情况:
- 当节点是树叶时,那么它可以被立即删除
- 如果节点有一个儿子,那么该节点可以在其父节点调整它的链以绕过该节点。
- 最复杂的情况是被删除节点有两个子节点,一般的删除策略是用其右子树的最小的数据代替该节点的数据并递归的删除那个节点(第二次删除比较容易,因为最小的数不可能有左儿子)。
一个实现的代码实例:
#include<iostream>
using namespace std;
class BST{
public:
struct BN{
int element;
BN *left;
BN *right;
BN(int ele,BN *l,BN *r){
element=ele;
left=l;
right=r;
}
};
BN *root;
bool contains(int x,BN *t){
if(t==NULL){
return false;
}
else if(x<t->element){
return contains(x,t->left);
}
else if(x>t->element){
return contains(x,t->right);
}
else{
return true;
}
}
BN * findMin(BN *t){
if(t == 0){
return 0;
}
if(t->left==0){
return t;
}
return findMin(t->left);
}
BN * findMax(BN *t){
if(t!=0){
while(t->right!=0){
t=t->right;
}
}
return t;
}
void insert(int x,BN * & t){
if(t==NULL){
t=new BN(x,NULL,NULL);
}
else if(x<t->element){
insert(x,t->left);
}
else if(x>t->element){
insert(x,t->right);
}
else{
//这里是重复元
}
}
void remove(int x,BN * & t){
if(t==NULL){
return;
}
if(x<t->element){
remove(x,t->left);
}
else if(x>t->element){
remove(x,t->right);
}
else if(t->left != NULL && t->right != NULL){
t->element=findMin(t->right)->element;
remove(t->element,t->right);
}
else{
BN *oldNode=t;
t=(t->left != NULL) ? t->left : t->right;
delete oldNode;
}
}
};
int main(){
BST test=BST();
test.insert(5,test.root);
test.insert(2,test.root);
test.insert(14,test.root);
test.insert(1,test.root);
test.insert(3,test.root);
test.insert(7,test.root);
test.insert(9,test.root);
cout<<test.findMin(test.root)->element<<endl;
cout<<test.findMax(test.root)->element<<endl;
test.remove(14,test.root);
cout<<test.findMax(test.root)->element<<endl;
return 0;
}
输出:
1
14
9
二叉树的遍历:
前序遍历:根 左 右
中序遍历:左 根 右
后序遍历:左 右 根
以下构造了一颗二叉查找树,并实现了三种遍历方式:
#include<iostream>
using namespace std;
class BST{
public:
struct BN{
int element;
BN *left;
BN *right;
BN(int ele,BN *l,BN *r){
element=ele;
left=l;
right=r;
}
};
BN *root;
void insert(int x,BN * & t){
if(t==NULL){
t=new BN(x,NULL,NULL);
}
else if(x<t->element){
insert(x,t->left);
}
else if(x>t->element){
insert(x,t->right);
}
else{
//这里是重复元
}
}
void front(BN *t){
if(t==0){
return;
}
cout<<t->element<<" ";
front(t->left);
front(t->right);
}
void middle(BN *t){
if(t==0){
return;
}
middle(t->left);
cout<<t->element<<" ";
middle(t->right);
}
void back(BN *t){
if(t==0){
return;
}
back(t->left);
back(t->right);
cout<<t->element<<" ";
}
};
int main(){
BST test=BST();
test.insert(5,test.root);
test.insert(2,test.root);
test.insert(14,test.root);
test.insert(1,test.root);
test.insert(3,test.root);
test.insert(7,test.root);
test.insert(9,test.root);
test.front(test.root);
cout<<endl;
test.middle(test.root);
cout<<endl;
test.back(test.root);
return 0;
}
输出:
5 2 1 3 14 7 9
1 2 3 5 7 9 14
1 3 2 9 7 14 5