一、复习
bst:搜索、插入、删除
二、二叉搜索树BST
特征:
1)每个元素有一个关键值,所有的关键值都唯一。
2)根节点左子树的关键值(如果有的话)小于根节点的关键值
3)根节点右子树的关键值(如果有的话)大于根节点的关键值
4)根节点的左右子树也都是二叉搜索树
一棵二叉树,如果其中序遍历的结果,得到的是关键值按升序排列的列表,则它是二叉搜索树
每个节点有一个索引值:左子树大小(结点个数)+1 (即节点在子树中从小到大的排名)
1.二叉搜索树与搜索插入
template<class E, class K>
class BSTree : public BinaryTree<E> //继承二叉树
{
public:
//将关键值为k的元素返回到e中;返回是否操作成功
bool Search(const K& k, E& e) const;
//将元素e插入到搜索树中
BSTree<E, K>& Insert(const E& e);
//删除关键值为k的元素并且将其返回到e中
BSTree<E, K>& Delete(const K& k, E& e);
//按照关键值的升序排列输出所有元素
void Ascend() { InOutput(); }//升序输出:中序遍历
};
//搜索:从根节点开始,左小右大
template<class E, class K>
bool BSTree<E, K>::Search(const K& k, E& e) const
{
BinaryTreeNode<E>* p = root;
while (p)
{
if (k > p->data)p = p->RightChild;
else if (k < p->data)p = p->LeftChild;
else
{
e = p->data;
return 1
}
}
return 0;
}
//插入
template<class E, class K>
BSTree<E, K>& BSTree<E, K>::Insert(const E& e)
{// Insert e if not duplicate.
BinaryTreeNode<E>* p = root, // search pointer
* pp = 0; // parent of p
// find place to insert
while (p) {// examine p->data
pp = p;
// move p to a child
if (e < p->data) p = p->LeftChild;
else if (e > p->data) p = p->RightChild;
else throw BadInput(); // duplicate
}
// get a node for e and attach to pp
BinaryTreeNode<E>* r = new BinaryTreeNode<E>(e);
if (root) {// tree not empty
if (e < pp->data) pp->LeftChild = r;
else pp->RightChild = r;
}
else // insertion into empty tree
root = r;
return *this;
}
2.bst删除
1.删除叶节点: 直接删除,让父节点指向它的指针变为0
2.p有且只有一个非空子树t,其根为q: 丢弃p,用q取代p的位置
3.p的两个子树都不为空:
1)p与某个节点q(满足情况2或1)交换(交换后应保持二叉搜索树性质)
2)删除q
#选择方法:左子树的最右节点或右子树的最左节点,排名恰与p相邻——之前或之后,即直接前驱或直接后继,一般用直接前驱(被删节点的左子树的最右节点)(最右就是一直选择向右走直到没有向右子节点)
//删除算法
template<class E, class K>
BSTree<E, K>& BSTree<E, K>::Delete(const K& k, E& e)
{
BinaryTreeNode<E>* p = root, * pp = 0;
//先搜索要删除的节点p
while (p && p->data != k)
{
pp = p;//p的双亲结点
if (k < p->data)
p = p->LeftChild;
else p = p->RightChild;
}
if (!p) throw BadInput(); // no element with key k
e = p->data;
//最坏情况,
//两个子树均不空
if (p->LeftChild && p->RightChild)
{
// 搜索左子树最右(大)节点
BinaryTreeNode<E>* s = p->LeftChild,
* ps = p; // 记录双亲结点,防止丢失
while (s->RightChild) {
ps = s;
s = s->RightChild;
}
// 交换p和其左子树的最右节点
p->data = s->data;
//这里第一次略有疑惑
p = s;
pp = ps;
}
//此后2个子树的情况已经变为1/0个子树的情况
//处理p只有1个孩子时,c指向孩子(p有0个孩子指向空指针)
BinaryTreeNode<E>* c;
if (p->LeftChild) c = p->LeftChild;
else c = p->RightChild;
// 删除p:即跨过p,让pp指向p的孩子
//p为根,此时pp为空
if (p == root) root = c;
//c要么为空,要么是删除的下一个(1孩子),让父节点指向空或下一个
else {// is p left or right child of pp?
if (p == pp->LeftChild)
pp->LeftChild = c;
else pp->RightChild = c;
}
delete p;
return *this;
}
3.复杂度分析
搜索、插入、删除的复杂性为O(h)
二叉搜索树的高度h,最坏情况为n
若搜索、插入、删除是随机的,平均情况为O(logn)
升序输出为O(n)
三、AVL树
概念与特性
AVL树——一种平衡树,提高二叉搜索树的最坏情况
如果T是一棵非空的二叉树,TL和TR分别是其左子树和右子树,T是一棵AVL树,满足:
1)TL和TR是AVL树
2)|hL-hR|≤1,hL和hR分别是左子树和右子树的高度
特性:
n个元素(节点)的AVL树的高度是O(logn)
插入、删除、搜索都是O(logn)
AVL树的高度
Fh表示高度为h的节点数最少的AVL树
左、右子树也是AVL树,高度一个为h-1,另一个为h-1或h-2
Fh节点数最少->子树中分别为Fh-1和Fh-2
高度为h的AVL树的最少节点数:|F(h)|=|F(h-1)|+|F(h-2)|+1 (菲波那契数列)
平衡因子域bf=左子树的高度-右子树的高度;
AVL搜索:和一般的二叉搜索树一致
AVL插入
思路:首先利用二叉搜索树的插入算法;可能出现不平衡的情况->调整结构
分析:
只有从根到新插入节点路径上的节点,其平衡因子才会在插入操作后发生改变。
A:假设A是离新插入节点最近的,平衡因子为-2或2的祖先节点,则在插入前,从A到新插入节点的路径上,所有节点的平衡因子都是0
X:在插入前bf(A)必然为-1/+1,x是满足条件的节点的最后一个
x不存在:bf全为0,插入不会导致不平衡
插入后X未变为A:插入后bf(X)=0;新元素插入原来较矮的子树
X变为A:新节点插入较高的子树
型号的判断只从被破坏节点开始判断两次
LL型:在被破坏节点的左边的左边插入而导致失衡;解决——以被破坏节点为基础进行右旋
RR型:以被破坏节点为基础进行左旋
LR型:左右型,在被破坏节点的左边的右边插入而导致失衡;
解决:以被破坏节点的左节点为基础先进行一次左旋,再以被破坏节点为基础进行右旋。
RL型:以被破坏节点的右节点为基础先进行一次右旋,再以被破坏节点为基础进行左旋。
算法:
1.从根节点开始搜索,确定插入位置,同时寻找最后的平衡因子为-1或1的节点,记为A
2.若没有A:直接插入后平衡,从根节点遍历,修改平衡因子。
3.若bf(A)=1且新节点插入A的右子树,或bf(A)=-1且新节点插入A的左子树:
->A 的新平衡因子是0,修改从A 到新节点路径中节点的平衡因子,然后终止
4.不平衡:执行旋转,修改平衡因子
typedef struct AVLNode
{
struct AVLNode* left, * right;
int val, height;
}*Node;
int getHeight(Node root)
{
if (root == NULL) return 0;
else return root->height;
}
Node rightrotate(Node root)//LL型旋转
{
Node tmp = root->left;
root->left = tmp->right;
tmp->right = root;
root->height = max(getHeight(root->left), getHeight(root->right)) + 1;
tmp->height = max(getHeight(tmp->left), getHeight(tmp->right)) + 1;
return tmp;
}
Node leftrotate(Node root)//RR型旋转
{
Node tmp = root->right;
root->right = tmp->left;
tmp->left = root;
root->height = max(getHeight(root->left), getHeight(root->right)) + 1;
tmp->height = max(getHeight(tmp->left), getHeight(tmp->right)) + 1;
return tmp;
}
Node rightleftrotate(Node root)//RL型旋转
{
root->right = rightrotate(root->right);
return leftrotate(root);
}
Node leftrightrotate(Node root)//LR型旋转
{
root->left = leftrotate(root->left);
return rightrotate(root);
}
AVL删除
1.执行bst删除,如不平衡则调整
删除的节点在右子树:
1.删除后被破坏节点的左节点的左边高度大于右边高度:相当于LL型,对“被破坏节点”进行右旋
2.删除后被破坏节点的左节点的左边高度小于右边高度:相当于LR型,先对“被破坏节点的左节点”左旋,再对“被破坏节点”右旋
3.删除后被破坏节点的左节点的左边高度等于右边高度:对“被破坏节点”进行右旋
删除的节点在左子树,对称处理
AVL非递归实现
【C++ 学习 ㉒】- 超详解 AVL 树的插入、平衡调整以及删除(含源代码)-CSDN博客
AVL的实现(递归)
平衡二叉树(AVL)的构造,插入与删除代码(利用递归)_wulizyzstc的博客-CSDN博客
#include<iostream>
#include<queue>
#include<algorithm>
using namespace std;
//树结点
struct TreeNode
{
int val;
int height;
TreeNode* left;
TreeNode*right;
TreeNode():val(0),left(nullptr),right(nullptr),height(1){}
TreeNode(int x):val(x),left(nullptr),right(nullptr),height(1){}
TreeNode(int x,TreeNode*left,TreeNode*right):val(x),height(1),left(left),right(right){}
};
//某树是不是完全二叉树
bool isbinarytree(TreeNode*root)
{
queue<TreeNode*>que;
que.push(root);
TreeNode *cur;
//遇到空结点
while((cur=que.front())!=nullptr)
{
que.pop();
que.push(cur->left);
que.push(cur->right);
}
//验证que里面还有没有数
while(!que.empty())
{
if (que.front()!=nullptr)
{
return false;
}
que.pop();
}
return true;
}
//层次遍历输出数组
vector<int> levelorder(TreeNode*root)
{
vector<int>arr;
if (root)
{queue<TreeNode*>que;
que.push(root);
while (!que.empty())
{
auto x=que.front();
arr.push_back(x->val);
que.pop();
if (x->left)que.push(x->left);
if (x->right)que.push(x->right);
}
}
return arr;
}
//getheight(递归)
int getheight(TreeNode* root)
{
if (!root)return 0;
return root->height;
}
//LL右单旋
TreeNode* rightrotate(TreeNode* root)
{
TreeNode* temp=root->left;
root->left=temp->right;
temp->right=root;
root->height=max(getheight(root->left),getheight(root->right))+1;
temp->height=max(getheight(temp->left),getheight(temp->right))+1;
return temp;
}
//RR左单旋
TreeNode* leftrotate(TreeNode* root)
{
TreeNode* temp=root->right;
root->right=temp->left;
temp->left=root;
root->height=max(getheight(root->left),getheight(root->right))+1;
temp->height=max(getheight(temp->left),getheight(temp->right))+1;
return temp;
}
//LR 对root->left做左单旋,再对root做右单旋
TreeNode* lerigrotate(TreeNode* root)
{
root->left=leftrotate(root->left);
return rightrotate(root);
}
//RL 对root-right做右单旋,再对root做左单旋
TreeNode* riglerotate(TreeNode* root)
{
root->right=rightrotate(root->right);
return leftrotate(root);
}
//把数据插入到avl树上
TreeNode* insert(TreeNode * root,int key)
{
if (!root)
{
root=new TreeNode(key);
return root;
}
else
{
if (key<root->val)
{
root->left=insert(root->left,key);
if (getheight(root->left)-getheight(root->right)==2)
{
if (key<root->left->val)root=rightrotate(root);
else root=lerigrotate(root);
}
}
else if(key>root->val)
{
root->right=insert(root->right,key);
if (getheight(root->right)-getheight(root->left)==2)
{
if (key>root->right->val)root=leftrotate(root);
else root=riglerotate(root);
}
}
}
root->height=max(getheight(root->left),getheight(root->right))+1;
return root;
}
bool isdelete=1;
//删除叶子结点的方法删除avl结点
TreeNode* deletekey(TreeNode* root,int key)
{
if (!root) {isdelete=0;return root;}//没成功删除
if (root->val==key)//删除叶子结点
{
if(!root->left&&!root->right){root=nullptr;return root;}
if (getheight(root->left)>=getheight(root->right))//替换并且使得要删除的元素变成叶子结点
{
//对左孩子找到最里面的右孩子,改变key,迭代删掉它
TreeNode* temp=root->left;
while (temp->right)
{
temp=temp->right;
}
root->val=temp->val;
key=temp->val;
root->left=deletekey(root->left,key);
if (getheight(root->right)-getheight(root->left)==2)root=leftrotate(root);
}
else
{
TreeNode* temp=root->right;
while (temp->left)
{
temp=temp->left;
}
root->val=temp->val;
key=temp->val;
root->right=deletekey(root->right,key);
if (getheight(root->left)-getheight(root->right)==2)root=rightrotate(root);
}
}
else if(key<root->val)
{
root->left=deletekey(root->left,key);
if (getheight(root->right)-getheight(root->left)==2)root=leftrotate(root);
}
else if(key>root->val)
{
root->right=deletekey(root->right,key);
if (getheight(root->left)-getheight(root->right)==2)root=rightrotate(root);
}
root->height=max(getheight(root->left),getheight(root->right))+1;
return root;
}
int main()
{
int n;
cin >> n;
TreeNode* root = NULL;
for(int i = 1; i <= n; i++)
{
int x;
cin >> x;
root = insert(root, x);
}
vector<int>arr=levelorder(root);
for (int i = 0; i < arr.size()-1; i++)cout<<arr[i]<<" ";
cout<<arr[arr.size()-1]<<endl;
if (isbinarytree(root))cout<<"Yes"<<endl;
else cout<<"No"<<endl;
int m;
cin>>m;
root=deletekey(root,m);
if (isdelete)cout<<"删除成功"<<endl;
else cout<<"删除失败"<<endl;
arr=levelorder(root);
for (int i = 0; i < arr.size()-1; i++)cout<<arr[i]<<" ";
cout<<arr[arr.size()-1]<<endl;
if (isbinarytree(root))cout<<"Yes"<<endl;
else cout<<"No"<<endl;
system ("pause");
return 0;
}
oj——创建AVL树并判断是否为完全二叉树
#include <queue>
#include <vector>
#include <iostream>
using namespace std;
typedef struct AVLNode
{
struct AVLNode* left, * right;
int val, height;
}*Node;
int getHeight(Node root)
{
if (root == NULL) return 0;
else return root->height;
}
Node rightrotate(Node root)//LL型旋转
{
Node tmp = root->left;
root->left = tmp->right;
tmp->right = root;
root->height = max(getHeight(root->left), getHeight(root->right)) + 1;
tmp->height = max(getHeight(tmp->left), getHeight(tmp->right)) + 1;
return tmp;
}
Node leftrotate(Node root)//RR型旋转
{
Node tmp = root->right;
root->right = tmp->left;
tmp->left = root;
root->height = max(getHeight(root->left), getHeight(root->right)) + 1;
tmp->height = max(getHeight(tmp->left), getHeight(tmp->right)) + 1;
return tmp;
}
Node rightleftrotate(Node root)//RL型旋转
{
root->right = rightrotate(root->right);
return leftrotate(root);
}
Node leftrightrotate(Node root)//LR型旋转
{
root->left = leftrotate(root->left);
return rightrotate(root);
}
Node insert(Node root, int key)
{
if (root == NULL)
{
root = new AVLNode();
root->val = key;
root->left = root->right = NULL;
root->height = 1;
return root;
}
else
{
if (key < root->val)
{
//这一步递归体会一下,正常的bst插入
root->left = insert(root->left, key);
if (getHeight(root->left) - getHeight(root->right) == 2)
{
if (key < root->left->val) root = rightrotate(root);
else root = leftrightrotate(root);
}
}
else if (key > root->val)
{
root->right = insert(root->right, key);
if (getHeight(root->right) - getHeight(root->left) == 2)
{
if (key > root->right->val) root = leftrotate(root);
else root = rightleftrotate(root);
}
}
}
root->height = max(getHeight(root->left), getHeight(root->right)) + 1;
return root;
}
//层次顺序遍历 和两个flag判断是否是完全二叉
void level_order_traverse(Node root)
{
queue<Node>q;
vector<int>ans;
q.push(root);
bool flag = false, flag2 = false;
while (!q.empty())
{
Node p = q.front();
q.pop();
ans.push_back(p->val);
if (p->left != NULL)
{
q.push(p->left);
if (flag) flag2 = true;
}
else flag = true;
if (p->right != NULL)
{
q.push(p->right);
if (flag) flag2 = true;
}
else flag = true;
}
for (int i = 0; i < ans.size(); i++)
{
if (i == ans.size() - 1) cout << ans[i] << endl;
else cout << ans[i] << ' ';
}
if (!flag2) cout << "Yes" << endl;
else cout << "No" << endl;
}
int main()
{
int n;
cin >> n;
Node root = NULL;
for (int i = 1; i <= n; i++)
{
int x;
cin >> x;
root = insert(root, x);
}
level_order_traverse(root);
return 0;
}
四、B树B+树
m叉搜索树
可以为空,若非空:
1.在相应的扩充搜索树中(用外部节点替换零指针),每个内部节点最多可以有m个子女和包含1~m-1个元素(外部节点不含元素和子女)
2.每个含p个元素的节点,有p+1个子女
3.和二叉搜索树类似,有大小关系
高度为h,元素个数:h~m^h-1
n个元素,高度:n~logm(n+1)
m阶B-树
是一棵m叉搜索树,若B-树非空,那么相应的扩充树满足下列特征:
1.根节点至少有2个孩子
2.除根节点外,所有内部节点至少有[m/2]个孩子(向上取整)
3.所有外部节点位于同一层上
B-树的高度
一棵高为h的m阶B-树,d=ceil(m/2),n为元素个数
n: 2d^(h-1)-1 ~ m^h-1 (外部节点的数量比元素的个数多1)?
h: logm(n+1)~logd((n+1)/2) +1
插入操作
1.插入节点元素数<m-1:直接插入
2.插入节点的元素数=m-1:该结点分成两半,将中间的关键字进行提升,加入到父亲结点中,不断向上回溯直到符合要求。
删除操作
1.删除叶节点
2.删除非叶节点:类似AVL,与叶节点交换
3.
元素数目>ceil(m/2)-1:直接删除
元素数目=ceil(m/2)-1:
1)借用兄弟节点多余元素:左(右)兄弟节点元素数>d-1,则将其最右(左)元素提升至父节点,父节点相应元素下降到删除节点 (6次磁盘操作)
2)若兄弟节点无则合并:删除后本节点元素(d-2个)+兄弟结点元素(d-1个)+父节点中介于两者之间的元素(1个)合并,为2d-2(<=m-1 ?why)个元素的新结点 (5次磁盘操作),父节点元素数减少,可能<d-1,再继续重复上述两种方法。
B+树
所有关键字都在叶节点中,按从小到大链接,上层非叶节点的关键字是子树中最大关键字的复写;
B+树插入
查找合适位置插入,若合法,结束;
非法:将该叶节点均匀分裂,更新父节点;递归检查是否合法直到结束。
B+树删除
首先在叶节点删除,满足最小元素要求,停止;
不满足:从兄弟结点借元素;借不了->合并兄弟节点;递归考察父节点
五、红黑树
参考:【精选】【数据结构】史上最好理解的红黑树讲解,让你彻底搞懂红黑树_小七mod的博客-CSDN博客
特殊的二叉搜索树,对扩充(外部节点)的二叉搜索树满足:
1.根节点和所有外部节点的颜色是黑的
2.根至外部节点的路径上没有连续红节点
3.所有根至外部节点的路径具有相同数目的黑节点
另一种边的定义方式:
1.从内部节点指向外部节点的指针是黑的
2.根至外部节点的路径上没有连续红指针
3.所有根至外部节点的路径具有相同数目的黑指针
黑指针指向的孩子节点是黑的;红指针指向的孩子节点是红的
节点的阶:从该节点到其子树中任一外部节点的路径上的黑色指针数目
特性:
1.设从根到外部节点的路径的长度(length)是该路径中指针的数量,若P、Q是红黑树中两条从根至外部节点的路径,那么length(P) ≤2length(Q)(根的阶为r,红指针数目最多为r,指针总数在r~2r之间)
2.设h是一棵红-黑树的高度(不包括外部节点),n是树中内部节点的数目,而r是根节点的阶,则有:h<=2r(由1.);n>=2^r-1(树高至少为r);h<=2log2(n+1) (结合前两个)
描述:
外部节点无需保存;每个节点保存颜色和两个指针的颜色
搜索:
与二叉搜索树相同,复杂度O(logn)
红黑树类似4阶B树:
ppt:
2节点(一个关键字,两个孩子)用二叉树结构表示;3节点、4节点进行转换:
红边表示的是2-3-4树的同节点关系;黑边表示父子关系
将所有的红色节点上移到和他们的父节点同一高度上,就会变为4阶B树的形态。可知:
- 黑色节点与它的红色子节点融合在一起,形成1个B树节点
- 红黑树的黑色节点个数 与 4阶B树的节点总个数相等
- 在所有的B树节点中,永远是黑色节点是父节点,红色节点是子节点。
红黑树的插入 :
1.二叉搜索树的插入
2.着色:新节点着红色(若插入黑色,该节点所在路径比其他路径多出一个黑色节点,调整较麻烦;着红色仅可能会出现两个连续的红色节点的情况,较好调整)
3.调整:
1)没有连续红边:无需处理,直接插入
2)LL插入:父节点为祖父节点的左节点,插入节点为父节点的左节点
以祖父右旋;父亲变为黑色,祖父变为红色
3)RR插入:父节点为祖父节点的右节点,插入节点为父节点的右节点
以祖父左旋;父亲变为黑色,祖父变为红色
4)RL插入:同AVL;父亲右旋,祖父左旋;插入节点变为黑色,父亲变为红色
5) LR插入:同AVL;父亲左旋,祖父右旋;插入节点变为黑色,父亲变为红色
6) 上溢情况:祖父的另一个孩子是红节点uncle(即转化为4阶B树形式后父节点有3个关键字)
parent、uncle染为黑色;grand染为红色,向上一层合并(若上溢继续递归,若上溢到根节点,将根节点染成黑色)
红黑树的删除 :
删除红色节点:直接删除
删除黑色节点:
1.拥有 2 个红色子节点的黑色节点:不可能被直接删除,因为会找它的子节点替代删除,因此不用考虑这种情况
2.拥有 1 个红色子节点的黑色节点: 1) 1)用删除节点的唯一子节点对其进行替代;
2)将替代节点染成黑色
3.黑色叶子节点:
删除节点为根节点:直接删除;
删除黑色叶子节点(删除节点的兄弟节点为黑色):
1)兄弟节点至少有一个红色子节点:(参考B删除)
删除黑色叶子节点->父亲下来->旋转(使符合左小右大规律)->旋转后,中心节点继承父节点颜色,左右节点为黑色
2)兄弟节点没有红色子节点:
父节点为红色:父节点向下与兄弟节点合并->将兄弟染成红色、父节点染成黑色;
父节点为黑色:处理后,将父节点当成删除节点,再处理
删除黑色叶子节点(删除节点的兄弟节点为红色):
转化为兄弟节点为黑色,将其染为黑色,父节点染为红色
根据兄弟节点的相对位置判断LL或RR,进行旋转之后继续用“兄弟节点为黑色”的办法