本篇内容以知识整理为主,会结合萨特吉-萨尼的数据结构书籍和网络上的一些知识整理做一下总结,语言使用c++,有问题请及时指正,欢迎交流。
1、二叉搜索树
1、二叉搜索树的定义
二叉搜索树(Binary Search Tree)是一棵可能为空的二叉树,一棵非空的二叉搜索树满足以下特征:
- 每个元素有一个关键值,所有的关键值都是唯一的。
- 根节点左子树的关键值(如果有的话)小于根节点的关键值。
- 根节点右子树的关键值(如果有的话)大于根节点的关键值。
- 根节点的左右子树也都是二叉搜索树。
有重复值的二叉搜索树
- 每个元素有一个关键值
- 根节点左子树的关键值(如果有的话)小于等于根节点的关键值
- 根节点右子树的关键值(如果有的话)大于等于根节点的关键值
- 根节点的左右子树也都是有重复值的二叉搜索树
带索引的二叉搜索树
在二叉搜索树的每个节点中添加一个LeftSize域,这个域的值是该节点左子树的元素个数加1。
2、二叉搜索树的查找
思想:要查找关键值为k的元素,那么先从根开始,如果根为空,那么搜索树不包含任何元素,查找失败,否则,将k与根的关键值进行比较;
如果k小于根节点的关键值,那么就不必要搜索右子树中的元素,只要在左子树中搜索即可;
如果k大于根节点的关键值,那么相反地,搜索右子树中的元素即可;
如果k等于根节点的关键值,那么查找成功,搜索结束;
在子树中的查找与上相同。
//二叉搜索树的查找
bool query(TreeNode* root, int val) {
if (root == nullptr) return false;
TreeNode* ptmp = root;
while (ptmp != nullptr&&ptmp->data != val) {
if (val < ptmp->data) ptmp = ptmp->leftchild;
if (val > ptmp->data) ptmp = ptmp->rightchild;
}
/*
while循环结束有两种可能,一种是搜索了整棵树也没找到关键值
另一种是关键值找到了
*/
if (ptmp == nullptr) return false;
return true;
}
3、二叉搜索树的插入
思想:在二叉搜索树中插入一个新元素e,首先要验证e的关键值与树中已有元素的关键值是否相同,这可以通过e的关键值对二叉树进行搜索来实现。
如果搜索不成功,那么新元素将被插入到搜索的中断点;
从根节点开始插入
如果要插入的值value小于等于当前节点的值,则在当前节点的左子树中插入;
如果要插入的值value大于当前节点的值,在当前节点的右子树中插入;
如果节点为空节点,则找到该结点的父结点在此建立新的结点并判断是父结点的左孩子还是右孩子
//二叉搜索树的插入
void nodeinsert(TreeNode* root, int val) {
if (root == nullptr) {
root = new TreeNode;
root->data = val;
return;
}
TreeNode* pre = nullptr;//搜索中断节点
TreeNode* tmp = root;
//找到要插入的中断点
while (tmp != nullptr) {
pre = tmp;
if (val == tmp->data) return;
else if (val < tmp->data) {
tmp = tmp->leftchild;
}
else tmp = tmp->rightchild;
}
//开始插入
if (val < pre->data) {
pre->leftchild = new TreeNode;
pre->leftchild->data = val;
}
else {
pre->rightchild = new TreeNode;
pre->rightchild->data = val;
}
}
3、二叉搜索树的删除
思想:考虑包含被删除元素的节点p的三种情况
-
p是树叶:如果p是树叶,则丢弃树叶节点即可;
-
p只有一个非空子树:
(1)如果p没有父节点(即p是根节点),则将p丢弃,p的唯一子树的根节点称为新的搜索树的根节点
(2)如果p有父节点pp,则修改pp的指针,使得pp指向p的唯一孩子,然后删除节点p -
如果p有两个非空子树,只需要将该元素替换为他的左子树中的最大元素或右子树中的最小元素
void nodedelete(TreeNode* root, int val) {
if (root == nullptr) return;
TreeNode* pre = nullptr;
TreeNode* tmp = root;
while (tmp != nullptr) {
pre = tmp;
if (val < tmp->data) {
tmp = tmp->leftchild;
}
if (val > tmp->data) {
tmp = tmp->rightchild;
}
else break;
}
if (tmp == nullptr) {
return;
}
//如果删除节点既有左子树又有右子树
if (tmp->leftchild != nullptr&&tmp->rightchild != nullptr) {
TreeNode* old = tmp;
tmp = tmp->rightchild;
while (tmp->leftchild != nullptr) {
tmp = tmp->leftchild;
}
old->data = tmp->data;
}
//如果删除节点只有左子树
if (tmp->leftchild != nullptr&&tmp->rightchild == nullptr) {
TreeNode* old = tmp;
tmp = tmp->leftchild;
while (tmp->rightchild != nullptr) {
tmp = tmp->rightchild;
}
old->data = tmp->data;
}
//如果删除节点只有右子树
if (tmp->leftchild == nullptr&&tmp->rightchild != nullptr) {
TreeNode* old = tmp;
tmp = tmp->rightchild;
while (tmp->leftchild != nullptr) {
tmp = tmp->leftchild;
}
old->data = tmp->data;
}
//如果删除节点既没有左子树又没有右子树
if (tmp->leftchild == nullptr&&tmp->rightchild != nullptr) {
delete tmp;
return;
}
delete tmp;
}
4、树的高度和节点数
2、AVL树
1、AVL树的定义及特征
平衡树(balanced tree):当确定搜索树的高度总是O(logn)时,能够保证每个搜索树操作所占用的时间为O(logn),高度为O(logn)的树称为平衡树。
AVL树:空二叉树是AVL树,如果T是一棵非空的二叉树,Tl和Tr是其左子树和右子树,那么当T满足以下条件是,T是一棵AVL树:
- Tl和Tr是AVL树
- |hl-hr|<=1,hl和hr分别是左子树和右子树的高度
如果用AVL树来描述字典并希望在对数时间内完成每一种字典操作,那么, AVL树必须具备下述特征:
- n 个元素(节点)的AVL树的高度是O(logn)。
- 一棵n 元素的AVL 搜索树能在O(高度) = O(logn) 的时间内完成搜索。
- 将一个新元素插入到一棵n元素的AVL搜索树中,可得到一棵n+1元素的AVL树,这种插入过程可以在O(logn)时间内完成。
- 从一棵n元素的AVL搜索树中删除一个元素,可得到一棵n-1元素的AVL树,这种删除过程可以在O(logn)时间内完成。
2、AVL树的描述
一般用链表方式来描述AVL树。
为简化插入和删除操作,为每个节点增加一个平衡因子bf。节点x 的平衡因子bf(x) 定义为:
x 的左子树的高度-x 的右子树的高度
从AVL树的定义可以知道,平衡因子的可能取值为-1,0和1
3、AVL树的搜索、插入、删除
1、AVL树的搜索
同普通二叉搜索树
2、AVL树的插入
将一个新元素插入到AVL树中时,若得到的新树中有一个或多个节点的平衡因子的值不是-1,0或1,那么就说新树是不平衡的。通过移动不平衡树的子树来恢复树的平衡。
分析由插入操作导致产生不平衡树的几种现象:
- 不平衡树中的平衡因子的值限于-2,-1,0,1和2。
- 平衡因子为2的节点在插入前平衡因子为1,与此类似,平衡因子为-2的,插入前为-1。
- 从根到新插入节点的路径上,只有经过的节点的平衡因子在插入后会改变。
- 假设A是新插入节点最近的祖先,它的平衡因子是-2或2,那么,在插入前从A到新插入节点的路径上,所有节点的平衡因子都是0。
例如:从根节点往下移动寻找插入新元素的位置时,能够确定节点A。
bf(A)在插入前的值既可以是-1,也可以是1。设X 是最后一个具有这样平衡因子的节点。插入32。
如果节点X不存在,那么从根节点至新插入节点途中经过的所有节点在插入前的平衡因子值都是0。由于插入操作只会使平衡因子增/减-1,0或1,并且只有从根节点至新插入节点途中经过的节点的平衡因子值才会被改变,所以插入后,树的平衡不会被破坏。
因此,如果插入后的树是不平衡的,那么X就一定存在。
结论:
当节点A已经被确定时,A的不平衡性可归类为L型不平衡(新插入节点在A的左子树中)或R型不平衡。通过确定A的哪一个孙节点在通往新插入节点的路径上,可以进一步细分不平衡类型。
根据上述细分不平衡类型的方法,A节点的不平衡类型将是LL(新插入节点在A节点的左子树的左子树中),LR(新插入节点在A节点的左子树的右子树中),RR和RL四种类型中的一种。
3、AVL搜索树的删除
由于平衡因子可以沿从q到根节点的路径改变,所以途中节点的平衡因子有可能为2或-2。
设A是第一个这样的节点,若要恢复A节点的平衡,需要确定其不平衡的类型。
如果删除发生在A的左子树,那么不平衡是L型;否则,不平衡就是R型。
如果删除后bf(A)=2,那么在删除之前bf(A)的值一定为1。因此,A有一棵以B为根的左子树。
根据bf(B)的值,可以把一个R型不平衡细分为R0,R1和R-1类型。
例如,R-1类型指的是这种情况:删除操作发生在A的右子树并且bf(B)=-1。类似的,L型不平衡也可以细分为L0,L1和L-1类型。
4、插入与删除的比较
LL与R1类型的旋转相同;LL与R0型旋转的区别仅在于A和B最后的平衡因子;而LR与R-1旋转也完全相同。
AVL树在执行每个插入操作时最多需要一次旋转,执行每个删除操作时最多需要O(logn)次旋转。
3、红黑树
1、红黑树的基础性质
红黑树是一种二叉搜索树,并且相对于二叉搜索树做了一定的改进。
红黑树具有下列五种性质:
- 根节点黑色。
- 每个节点为红色或黑色。
- 红色节点下两个节点必为黑色。
- 每一条从根至叶的路径上的黑节点数量相同。
- 每个叶节点都是黑的。
从五个性质可以得出的结论是,红黑树树内节点数n≥pow(2,h/2)−1
所以可以看出,红黑树相对于一个普通二叉树而言,对空间利用的更好。
另外红黑树还满足最长路径不能为最短路径的两倍或以上。
2、红黑树与AVL树的比较
AVL树是高度平衡的,频繁的插入和删除,会引起频繁的Rebalance,导致效率下降;红黑树不是高度平衡的,算是一种折中,插入最多两次旋转,删除最多三次旋转。所以红黑树在查找,插入、删除的性能都是O(logn),且性能稳定,所以stl里面很多结构包括map底层实现都是使用的红黑树。