二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};
二叉搜索树的查找
a、从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找。
b、最多查找高度次,走到到空,还没找到,这个值不存在。
二叉搜索树的插入
插入的具体过程如下:
a. 树为空,则直接新增节点,赋值给root指针
b. 树不空,按二叉搜索树性质查找插入位置,插入新节点
看起来有待删除节点有4中情况,实际情况a可以与情况b或者c合并起来,因此真正的删除过程
如下:
情况b:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点–直接删除
情况c:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点–直接删除
情况d:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点中,再来处理该结点的删除问题–替换法删除
跳转到下面—》代码区
K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。
比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:
KV模型:每一个关键码key,都有与之对应的值Value,即
// 改造二叉搜索树为KV结构
template<class K, class V>
struct BSTNode
{
BSTNode(const K& key = K(), const V& value = V())
: _pLeft(nullptr), _pRight(nullptr), _key(key), _Value(value)
{}
BSTNode<T>* _pLeft;
BSTNode<T>* _pRight;
K _key;
V _value
};
template<class K, class V>
class BSTree
{
typedef BSTNode<K, V> Node;
typedef Node* PNode;
public:
BSTree() : _pRoot(nullptr){}
PNode Find(const K& key);
bool Insert(const K& key, const V& value)
bool Erase(const K& key)
private:
PNode _pRoot;
}
插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。
对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。
但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:
问题:如果退化成单支树,二叉搜索树的性能就失去了。那能否进行改进,不论按照什么次序插入关键码,二叉搜索树的性能都能达到最优?
那么AVL树和红黑树就可以上场了。
二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:
如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在 O ( l o g 2 n ) O(log_2 n) O(log2n),搜索时间复杂度O( l o g 2 n log_2 n log2n)。
AVL树节点的定义:
采用的是三叉链结构
template<class T>
struct AVLTreeNode
{
T data;
AVLTreeNode<T>*_left;// 该节点的左孩子
AVLTreeNode<T>* _right;// 该节点的右孩子
AVLTreeNode<T>* _parent;// 该节点的双亲
int _bf;// 该节点的平衡因子----可以用右子树的高度-左子树的高度
AVLTreeNode(const T& data)
:_data(data), _left(nullptr), _right(nullptr), _parent(nullptr), _bf(0)
{}
};
AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。
那么AVL树的插入过程可以分为两步:
按照二叉搜索树的方式插入新节点
调整节点的平衡因子
(具体代码实现在看下面的代码)
跳转到下面–》代码区
如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使之平衡化。根据节点插入位置的不同,AVL树的旋转分为四种:
将双旋变成单旋后再旋转,即:先对30进行左单旋,然后再对90进行右单旋,旋转完成后再考虑平衡因子的更新。
一个简单的例子
参考右左双旋。
一个简单的例子:
总结:
假如以pParent为根的子树不平衡,即pParent的平衡因子为2或者-2,分以下情况考虑
pParent的平衡因子为2,说明pParent的右子树高,设pParent的右子树的根为pSubR
pParent的平衡因子为-2,说明pParent的左子树高,设pParent的左子树的根为pSubL
旋转完成后,原pParent为根的子树个高度降低,已经平衡,不需要再向上更新。
AVL树的代码这么复杂,那我们该如何确认我们写的代码是否有问题呢?
这时候我们可以写一个程序进行验证!
AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步:
验证其为二叉搜索树
如果中序遍历可得到一个有序的序列,就说明为二叉搜索树
验证其为平衡树
每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)
节点的平衡因子是否计算正确
bool IsBalanceTree(Node* root)
{
if (root == nullptr)//空树叶是AVL树
return true;
// 计算root节点的平衡因子:即root左右子树的高度差
int leftHeight = GetTreeHeight(root->_left);
int rightHeight = GetTreeHeight(root->_right);
int diff = rightHeight - leftHeight;
// 如果计算出的平衡因子与pRoot的平衡因子不相等,或者
// pRoot平衡因子的绝对值超过1,则一定不是AVL树
if (diff != root->_bf || diff>1 || diff<-1)
{
std::cout << root->_val << "->该节点平衡因子错误!" << "root->_bf:"
<< root->_bf << "实际_bf" << diff << std::endl;
return false;
}
// root的左和右如果都是AVL树,则该树一定是AVL树
return IsBalanceTree(root->_left) && IsBalanceTree(root->_right);
}
验证用例
可以结合上述代码按照以下的数据次序,自己动手画AVL树的创建过程,验证代码是否有漏洞。
AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即 l o g 2 ( N ) log_2 (N) log2(N)。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。
#pragma once
#include
#include
using namespace std;
namespace hdm
{
/*Binary Search Tree---二叉搜索树或二叉排序树*/
template<class T>
struct BSTreeNode
{
T _val;
BSTreeNode<T>* _left;//左孩子
BSTreeNode<T>* _right;//右孩子
BSTreeNode(const T& val = T())
:_val(val), _left(nullptr), _right(nullptr)
{}
};
template<class T>
class BSTree
{
public:
typedef BSTreeNode<T> Node;
Node* find(const T& x)
{
Node* cur = _root;
while (cur)
{
if (cur->_val > x)//比cur小往左子树找
{
cur = cur->_left;
}
else if (cur->_val < x)//比cur大往右子树找
{
cur = cur->_right;
}
else//相等即返回
{
return cur;
}
}
return nullptr;
}
bool insert(const T& x)
{
if (_root == nullptr)//_root==nullptr说明为空树
{
_root = new Node(x);
return true;
}
Node* cur = _root;
Node* parent = cur;//记录父节点用于连接新节点
while (cur)
{
//cur往子树走的同时记录子树的父节点
if (cur->_val > x)//比cur小往左子树找
{
parent = cur;
cur = cur->_left;
}
else if (cur->_val < x)//比cur大往右子树找
{
parent = cur;
cur = cur->_right;
}
else//进入else说明cur->_val==x,表示已经存在该节点,不需要插入,返回false表示插入失败
{
return false;
}
}
//程序走到这说明cur=nullptr,最后x就应该要插入在parent的下面,至于是插入左边还是右边
//具体看x与parent->_val 之间值的关系
if (parent->_val > x)//如果x的值小于parent就往左边插入
{
parent->_left = new Node(x);
}
else//如果x的值大于parent就往右边插入
{
parent->_right = new Node(x);
}
return true;
}
bool erase(const T& x)
{
if (!find(x))//如果该树不存在这节点就返回false表示删除失败
return false;
Node* cur = _root;
Node* parent = cur;
while (cur)
{
//cur往子树走的同时记录子树的父节点
if (cur->_val > x)//比cur小往左子树找
{
parent = cur;
cur = cur->_left;
}
else if (cur->_val < x)//比cur大往右子树找
{
parent = cur;
cur = cur->_right;
}
else//已找到--分情况删除
{
//1.左孩子不存在
//2.右孩子不存在
//3.左右孩子都存在--替换删
if (cur->_left == nullptr)//情况1:左孩子不存在
{
if (cur == _root)//如果_root刚好是要删除的节点的情况
{
_root = cur->_right;
}
//比较parent和cur的值,要分清cur的parent的那一个孩子
if (parent->_left == cur)
{
parent->_left = cur->_right;
}
else
{
parent->_right = cur->_right;
}
delete cur;
cur = nullptr;
}
else if (cur->_right == nullptr)//情况2.右孩子不存在
{
if (cur == _root)//跟上面一样_root刚好是要删除的节点的情况
{
_root = cur->_left;
}
//同样要判断cur是parent的哪一边
if (parent->_left == cur)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
delete cur;
cur = nullptr;
}
else if (cur->_left!=nullptr && cur->_right!=nullptr)//情况3.左右孩子都存在--替换删
{
//这里找的是右子树的最小节点(就是右子树的最左节点)
Node* min_right = cur->_right;
Node* min_right_parent = cur;//记录右子树最小节点的父节点,用于后续连接
while (min_right->_left)
{
min_right_parent = min_right;
min_right = min_right->_left;
}
swap(cur->_val, min_right->_val);//可以交换也可以覆盖原来要删除的节点
//cur->_val = min_right->_val;//这个是覆盖
//要删除min_right,把它的右孩子要连上
if (min_right_parent->_left == min_right)
{
min_right_parent->_left = min_right->_right;
}
else
{
min_right_parent->_right = min_right->_right;
}
delete min_right;
min_right = nullptr;
break;
}
else
{
//如果进入了else,说明都没有出现上面的情况,就是程序哪里写错了,直接断言报错
assert(false);
}
}
}
return true;
}
void InorderTree()
{
_InorderTree(_root);
cout << endl;
}
Node* findNonR(const T& x)//查找的递归版本
{
return findNonR(_root, x);
}
bool insertNonR(const T& x)//插入的递归版本
{
return insertNonR(_root, x);
}
bool eraseNonR(const T& x)//删除的递归版本
{
return eraseNonR(_root, x);
}
private:
bool eraseNonR(Node*& root, const T& x)
{
if (root == nullptr)
return false;
if (root->_val > x)
{
return eraseNonR(root->_left, x);
}
else if (root->_val < x)
{
return eraseNonR(root->_right, x);
}
else
{
//1.左孩子不存在
//2.右孩子不存在
//3.左右孩子都存在
Node* del = root;
if (root->_left == nullptr)
{
root = root->_right;
}
else if (root->_right == nullptr)
{
root = root->_left;
}
else if (root->_left && root->_right)
{
Node* right_min = root->_right;
Node* parent_min = root;
while (right_min->_left)
{
parent_min = right_min;
right_min = right_min->_left;
}
swap(root->_val, right_min->_val);
return eraseNonR(root->_right, x);//然后再让root->_right去递归删除交换后的x
}
else
{
//未知错误
assert(false);
}
delete del;
del = nullptr;
return true;
}
}
bool insertNonR(Node*& root,const T& x)//插入的递归版本
{
if (root == nullptr)
{
root = new Node(x);
return true;
}
if (root->_val > x)
{
return insertNonR(root->_left, x);
}
else if (root->_val < x)
{
return insertNonR(root->_right, x);
}
else
{
return false;
}
}
Node* findNonR(Node* root,const T& x)//查找的递归版本
{
if (root == nullptr)
return root;
if (root->_val > x)
return findNonR(root->_left, x);
else if (root->_val < x)
return findNonR(root->_right, x);
else
return root;
}
void _InorderTree(Node* root)
{
if (root == nullptr)
return;
_InorderTree(root->_left);
cout << root->_val << " ";
_InorderTree(root->_right);
}
private:
Node* _root = nullptr;
};
}
#pragma once
#include
namespace hdm
{
template<class T>
struct AVLTreeNode
{
T _val;
AVLTreeNode<T>*_left;// 该节点的左孩子
AVLTreeNode<T>* _right;// 该节点的右孩子
AVLTreeNode<T>* _parent;// 该节点的双亲
int _bf;// 该节点的平衡因子
AVLTreeNode(const T& data)
:_val(data), _left(nullptr), _right(nullptr), _parent(nullptr), _bf(0)
{}
};
template<class T>
class AVLTree
{
public:
typedef AVLTreeNode<T> Node;
bool insert(const T& x)
{
if (_root == nullptr)
{
_root = new Node(x);
return true;
}
Node* cur = _root;
Node* parent = cur;//记录父节点用于连接新节点
while (cur)
{
//cur往子树走的同时记录子树的父节点
if (cur->_val > x)//比cur小往左子树找
{
parent = cur;
cur = cur->_left;
}
else if (cur->_val < x)//比cur大往右子树找
{
parent = cur;
cur = cur->_right;
}
else//进入else说明cur->_val==x,表示已经存在该节点,不需要插入,返回false表示插入失败
{
return false;
}
}
//程序走到这说明cur=nullptr,最后x就应该要插入在parent的下面,至于是插入左边还是右边
//具体看x与parent->_val 之间值的关系
cur = new Node(x);
if (parent->_val > x)//如果x的值小于parent就往左边插入
{
parent->_left = cur;
}
else//如果x的值大于parent就往右边插入
{
parent->_right = cur;
}
//注意这里是三叉链结构,要把父子关系连上
cur->_parent = parent;
//更新平衡因子
while (parent)//parent为空就结束,也就是更新到根的位置就停止
{
//新增在右,parent->_bf++
//新增在左,parent->_bf--
if (parent->_left == cur)
{
parent->_bf--;
}
else
{
parent->_bf++;
}
/*判断是否更新的依据:子树的高度是否变化
1.parent->_bf==0,说明parent->_bf是1或者-1,也就是说明之前是一边高一个边低
这次插入填上矮的那边,parent所在子树高度不变,
现在插入的节点刚好让它平衡了,就不需要向上更新了
2.parent->_bf==1 或 parent->_bf==-1,说明之前parent->_bf=0,插入前是平衡的,插入之后
导致一边高一边低,这个时候就要继续向上更新
3.parent->_bf==2 或 parent->_bf==-2 ,说明之前parent->_bf==1 或 -1的,现在插入的节点
导致严重不平衡,违反了规则,就要进行就地处理--旋转
*/
//旋转:
//1.让这棵子树左右高度不超过1
//2.旋转过程中进行保持它是搜索树
//3.更新调整孩子节点的高度平衡因子
//3.让这颗子树的高度跟插入前保持一致
if (parent->_bf == 0)
{
break;
}
else if (parent->_bf == -1 || parent->_bf == 1)
{
cur = parent;
parent = cur->_parent;
}
else if (parent->_bf == 2 || parent->_bf == -2)
{
//旋转
if (parent->_bf == -2 && cur->_bf == -1)
{
//右单旋
RotateR(parent);
}
else if (parent->_bf == 2 && cur->_bf == 1)
{
//左单旋
RotateL(parent);
}
else if (parent->_bf == -2 && cur->_bf == 1)
{
//左右旋
RotateLR(parent);
}
else if (parent->_bf == 2 && cur->_bf == -1)
{
//右左旋
RotateRL(parent);
}
else
{
//未知错误(比如程序写错)
assert(false);
}
break;//旋转完了之后要break
}
else
{
//未知错误
assert(false);
}
}
return true;
}
Node* find(const T& x)
{
Node* cur = _root;
while (cur)
{
if (cur->_val > x)//比cur小往左子树找
{
cur = cur->_left;
}
else if (cur->_val < x)//比cur大往右子树找
{
cur = cur->_right;
}
else//相等即返回
{
return cur;
}
}
return nullptr;
}
void InorderTree()
{
InorderTree(_root);
std::cout << std::endl;
}
bool IsBalanceTree()//检验平衡二叉树
{
return IsBalanceTree(_root);
}
private:
bool IsBalanceTree(Node* root)
{
if (root == nullptr)//空树叶是AVL树
return true;
// 计算root节点的平衡因子:即root左右子树的高度差
int leftHeight = GetTreeHeight(root->_left);
int rightHeight = GetTreeHeight(root->_right);
int diff = rightHeight - leftHeight;
// 如果计算出的平衡因子与pRoot的平衡因子不相等,或者
// pRoot平衡因子的绝对值超过1,则一定不是AVL树
if (diff != root->_bf || diff>1 || diff<-1)
{
std::cout << root->_val << "->该节点平衡因子错误!" << "root->_bf:"
<< root->_bf << "实际_bf" << diff << std::endl;
return false;
}
// root的左和右如果都是AVL树,则该树一定是AVL树
return IsBalanceTree(root->_left) && IsBalanceTree(root->_right);
}
int GetTreeHeight(Node* root)
{
if (root == nullptr)
return 0;
int left = GetTreeHeight(root->_left);
int right = GetTreeHeight(root->_right);
return left > right ? left + 1 : right + 1;
}
void InorderTree(Node* root)
{
if (root == nullptr)
return;
InorderTree(root->_left);
std::cout << root->_val << " ";
InorderTree(root->_right);
}
void RotateR(Node* parent)//右单旋
{
/*最要注意的点就是:这个是三叉链结构,要注意处理parent节点*/
Node* subL = parent->_left;
Node* subLR = subL->_right;//左子树的右子树
Node* pparent = parent->_parent;
if (subLR)//subLR 不为空就要修改它的parent
subLR->_parent = parent;
parent->_left = subLR;
subL->_right = parent;
parent->_parent = subL;
if (pparent == nullptr)//pparent为空就表示parent为_root,就要修改整棵树的根
{
_root = subL;
_root->_parent = nullptr;
}
else//不是根节点就要判断是位于pparent的那一边
{
if (pparent->_left == parent)
{
pparent->_left = subL;
}
else
{
pparent->_right = subL;
}
subL->_parent = pparent;
}
//更新平衡因子
subL->_bf = parent->_bf = 0;
}
void RotateL(Node* parent)//左单旋
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
Node* pparent = parent->_parent;
if (subRL)//subRL不为空就要连接它的parent
subRL->_parent = parent;
parent->_right = subRL;
subR->_left = parent;
parent->_parent = subR;
if (pparent == nullptr)//pparent为空就表示parent为_root,就要修改整棵树的根
{
_root = subR;
_root->_parent = nullptr;
}
else//不是根节点就要判断是位于pparent的那一边
{
if (pparent->_left == parent)
{
pparent->_left = subR;
}
else
{
pparent->_right = subR;
}
subR->_parent = pparent;
}
//更新平衡因子
subR->_bf = parent->_bf = 0;
}
void RotateLR(Node* parent)//左右旋
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
int oldBf = subLR->_bf;//记录旧的平衡因子,这样才知道是在哪插入的新节点
//先左单旋,然后再右单旋
RotateL(parent->_left);
RotateR(parent);
subLR->_bf = 0;
if (oldBf == -1)//就是在原节点的左子树插入
{
parent->_bf = 1;
subL->_bf = 0;
}
else if(oldBf == 1)//右子树新增
{
subL->_bf = -1;
parent->_bf = 0;
}
else if (oldBf == 0)//自己就是新增的节点
{
subL->_bf = parent->_bf = 0;
}
else
{
//都不是上述情况只能是有些地方写错了
assert(false);
}
}
void RotateRL(Node* parent)//右左旋
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
int oldBf = subRL->_bf;//记录旧的平衡因子
RotateR(parent->_right);
RotateL(parent);
//更新平衡因子
subRL->_bf = 0;
if (oldBf == -1)
{
parent->_bf = 0;
subR->_bf = 1;
}
else if (oldBf == 1)
{
parent->_bf = -1;
subR->_bf = 0;
}
else if (oldBf == 0)
{
parent->_bf = subR->_bf = 0;
}
else
{
//未知错误
assert(false);
}
}
private:
Node* _root = nullptr;
};
void AVLTreeTest1()
{
int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 ,1,2,4,3,4,4,4,4,4};
AVLTree<int> avl;
for (auto e: a)
{
avl.insert(e);
}
avl.InorderTree();
cout << avl.IsBalanceTree() << endl;
}
}