数据结构之二叉树
数据结构相关文章
前言
要了解二叉树,首先应该知道树是怎样的一种数据结构。
树的结点包含一个数据元素以及若干指向其子树的分支。结点拥有的子树的个数称为结点的度。度为0的结点称为叶子节点或终端结点;度不为0的结点称为非终端结点或分支结点。而整棵树的度是树中结点的度的最大值。
结点的子树的根称为该结点的孩子,相应的,结点称为孩子的双亲。而同一个双亲的孩子结点之前互称为兄弟。
结点的层次从根开始定义,根为第一层,根的孩子为第二层,以此类推。树中结点的的最大层次称为树的深度或者高度。
一、二叉树的定义
1.1 二叉树的定义
二叉树(Binary Tree)是n个结点的有限集合,该集合或为空集(称为空二叉树),或者由一个根结点和两棵互不相交的分别称为根结点的左子树和右子树的二叉树构成。
上图即为一棵二叉树。
1.2 二叉树的特点
- 二叉树的每个结点最多有两棵子树,所以二叉树中不存在度大于二的结点,注意不是只有两棵子树,而是最多有两棵子树,没有子树或者只有一棵子树都是满足的。
- 左子树和右子树是有顺序的,次序不能随意颠倒。即使树中结点只有一棵子树,也要区分它是左子树还是右子树。
1.3 特殊二叉树
- 斜树:顾名思义,斜树是斜的,但是往哪里斜还是有讲究的。所有的结点都只有左子树的二叉树叫做左斜树,同理所有的结点都只有右子树的二叉树叫做。同时斜树的深度和其结点个数相同。
- 满二叉树:在一棵树中,如果所有分支节点都存在左子树和右子树,并且所有的叶子结点都在同一层上,这样的二叉树称为满二叉树。
- 完全二叉树:对一棵具有n个结点的二叉树按层序编号,如果编号为i的结点与同样深度的满二叉树中编号为i的结点的位置完全相同,则这棵树称为完全二叉树。
完全二叉树的一些特点:
- 叶子结点只能出现在最下面两层。
- 最下层的叶子节点一定集中在左侧连续位置处。
- 倒数二层,若有叶子节点,一定都在右部连续位置。
- 如果结点的度为1,则该结点只有左孩子,即不存在只有右子树的情况。
- 同样结点数的二叉树,完全二叉树的深度最小。
二、二叉树的性质
性质一:在二叉树的第i层上最多有2^(i - 1)个结点(i >= 1).
性质二:深度为k的二叉树最多有2k - 1个结点(k >= 1).
性质三: 对任何一棵二叉树T,如果其终端结点数为n0,度为二的结点数为n2,则n0 = n2 + 1。
终端结点数其实就是叶子结点数,而一棵二叉树,除了叶子结点外,剩下的就是度为一和度为二的结点了。我们设总结点数为n,n = n1 + n2 +n0。同时n = n1 + 2 * n2 + 1(结点的度再加上根结点就是结点的总数) 将这两个式子合并化简就可以得出结论。
性质四:具有n个结点的完全二叉树的深度为log2n(向下取整) + 1
性质五:如果对一棵有n个结点的完全二叉树的结点按层序编号,则对任意结点i有:
- 如果i = 1,则结点i是二叉树的根,无双亲,如果i > 1,则其双亲是i / 2向下取整。
- 如果2i > n,则结点i无左孩子,否则其左孩子是结点2i。
- 如果2i + 1 > n,则结点i无右孩子,否则其右孩子是结点2i + 1。
三、二叉树的存储结构
3.1 二叉树的顺序存储结构
二叉树的顺序存储结构就是用一维数组存储二叉树中的结点,并且结点的存储位置,也就是数组下标要能体现结点之间的逻辑关系。
而根据二叉树性质五,我们可以得出数组下标和结点之间的逻辑关系。
如下图:
要注意,这里的下标是从一开始的,具体实现时应当稍加更改。
当然,这里用的例子是完全二叉树的例子,如果不是完全二叉树,只需要在把对应数组下标的位置上的内容加以区分即可。
3.2 二叉树的链式存储
二叉树的每个结点最多有两个孩子,所以我们可以为其设计一个数据域和两个指针域,这样的链表就叫做二叉链表。
以下是二叉链表结构定义的代码:
typedef char BTDataType;
typedef struct BinaryTreeNode
{
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
BTDataType data;
} BTNode;
如下图所示:
四、遍历二叉树
二叉树的遍历就是从根结点出发,按照某种次序依此访问二叉树中所有结点,使得每个结点被访问且仅被访问一次。
二叉树的遍历不同于线性结构,线性结构最多也就是从头至尾,循环、双向等简单的遍历方式。而二叉树的结点不存在唯一的前驱和后继关系,在访问一个结点后下一个被访问的结点面临着不同的选择。
4.1 前序遍历
前序遍历的规则是若二叉树为空则返回,否则先访问根结点,然后前序遍历左子树,再前序遍历右子树
代码如下:
void PreOrder(BTNode* root)
{
if (root == NULL)
{
return;
}
printf("%c ", root->data);
PreOrder(root->left);
PreOrder(root->right);
}
4.2 中序遍历
中序遍历的规则是若树为空,则返回,否则从根结点开始,先中序遍历左子树,再访问根结点,最后遍历右子树。
代码如下:
void InOrder(BTNode* root)
{
if (root == NULL)
{
return;
}
InOrder(root->left);
printf("%c ", root->data);
InOrder(root->right);
}
4.3 后序遍历
后序遍历规则是,若树为空,返回。否则先后续遍历左子树,再后序遍历右子树,最后访问根结点。
代码如下:
void PostOrder(BTNode* root)
{
if (root == NULL)
{
return;
}
PostOrder(root->left);
PostOrder(root->right);
printf("%c ", root->data);
}
4.4 层序遍历
层序遍历规则是若树为空,则返回。否则从树的第一层,也就是根结点开始访问,自上而下逐层访问,在同一层中,按从左到右的顺序。那层序遍历如何实现呢?不知道大家是否还记得队列这种数据结构,最先入队的会最先出队。因此,我们可以借助队列来实现层序遍历。
算法思想:
- 当根结点不为空时,入队。
- 当队列不为空时,队头元素出队。
- 若其不为空,左子树先入队,右子树再入队。
- 循环第二步和第三步。
代码如下:
void BreadthFirstOrder(BTNode* root)
{
QueueNode q;
QueueInit(&q);
if (root)
{
QueuePush(&q, root);
}
while (!QueueEmpty(&q))
{
BTNode* top = QueueFront(&q);
QueuePop(&q);
printf("%c ", top->data);
if (top->left)
{
QueuePush(&q,top->left);
}
if (top->right)
{
QueuePush(&q, top->right);
}
}
QueueDestroy(&q);
}
五、二叉搜索树
二叉搜索树又叫做二叉排序树,其特点是左子树的值都要小于根结点,右子树的值都要大于根结点。
5.1 二叉搜索树的查找
根据二叉搜索树的特性:
- 先比较根结点,如果相等则找到
- 如果大于根结点,去右子树查找
- 若小于根结点,去左子树查找
5.2 二叉搜索树的插入
具体过程如下:
- 若根结点为空,直接插入
- 如果大于根结点,去右子树插入
- 若小于根结点,去左子树插入
5.3 二叉搜索树的删除
针对二叉搜索树的删除,要分情况讨论:
- 要删除结点的左或右孩子为空
- 要删除结点的左右孩子不为空
针对第一种情况:
- 若左子树为空,让待删除结点的双亲结点(要先判断待删除结点是双亲结点的左孩子还是右孩子)指向其右子树,反之则指向其左子树。
- 若要删除的结点是根结点,让根结点指向对应位置。
针对第二种情况:
找到中序遍历下待删除结点的前驱或者后继,即待删除结点左子树最右结点或者右子树最左节点。 将其值赋给原本要删除的结点后,将其删除。
5.4 二叉搜索树的实现
#pragma once
namespace K
{
template<class K>
struct BSTreeNode
{
BSTreeNode(const K& key)
: _val(key)
, _left(nullptr)
, _right(nullptr)
{}
BSTreeNode<K>* _left;
BSTreeNode<K>* _right;
K _val;
};
template<class K>
class BSTree
{
typedef BSTreeNode<K> Node;
protected:
bool _Erase(Node*& root, const K& key)
{
if (root == nullptr)
{
return false;
}
if (root->_val > key)
{
return _Erase(root->_left, key);
}
else if (root->_val < key)
{
return _Erase(root->_right, key);
}
else
{
//找到了,左右孩子有空
if (root->_left == nullptr)
{
Node* temp = root;
root = root->_right;
delete temp;
return true;
}
if (root->_right == nullptr)
{
Node* temp = root;
root = root->_left;
delete temp;
return true;
}
//找左孩子最右结点
//Node* prev = root->_left;
//while (prev->_right)
//{
// prev = prev->_right;
//}
//const K value = prev->_val;
//_Erase(root->_left, value);
//root->_val = value;
//非递归
Node* prev = nullptr;
Node* cur = root->_left;
while (cur->_right != nullptr)
{
prev = cur;
cur = cur->_right;
}
Node* temp = cur;
if (prev == nullptr)
{
root->_left = cur->_left;
}
else
{
prev->_right = cur->_left;
}
root->_val = temp->_val;
delete temp;
return true;
}
}
Node* _Find(Node* root, const K& key)
{
if (root == nullptr)
{
return nullptr;
}
if (key > root->_val)
{
return _Find(root->_right, key);
}
else if (key < root->_val)
{
return _Find(root->_left, key);
}
else
{
return root;
}
}
bool _Insert(Node*& root, const K& key)
{
if (root == nullptr)
{
root = new Node(key);
return true;
}
if (key > root->_val)
{
return _Insert(root->_right, key);
}
else if (key < root->_val)
{
return _Insert(root->_left, key);
}
else
{
return false;
}
}
void _inOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_inOrder(root->_left);
std::cout << root->_val << " ";
_inOrder(root->_right);
}
void destory(Node* root)
{
if (root == nullptr)
{
return;
}
destory(root->_left);
destory(root->_right);
delete root;
}
Node* copy(Node* root)
{
if (root == nullptr)
{
return nullptr;
}
Node* copyNode = new Node(root->_val);
copyNode->_left = copy(root->_left);
copyNode->_right = copy(root->_right);
return copyNode;
}
public:
BSTree()
: _root(nullptr)
{}
BSTree(const BSTree<K>& t)
{
_root = copy(t._root);
}
BSTree<K>& operator=(BSTree<K> t)
{
swap(_root, t._root);
return *this;
}
~BSTree()
{
destory(_root);
_root = nullptr;
}
//非递归
bool insert(const K& key)
{
if (_root == nullptr)
{
_root = new Node(key);
}
else
{
Node* parents = nullptr;
Node* cur = _root;
while (cur)
{
if (key > cur->_val)
{
parents = cur;
cur = cur->_right;
}
else if (key < cur->_val)
{
parents = cur;
cur = cur->_left;
}
else
{
return false;
}
}
if (parents != nullptr)
{
cur = new Node(key);
if (key > parents->_val)
{
parents->_right = cur;
}
else
{
parents->_left = cur;
}
}
}
return true;
}
Node* find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (key > cur->_val)
{
cur = cur->_right;
}
else if (key < cur->_val)
{
cur = cur->_left;
}
else
{
return cur;
}
}
return nullptr;
}
bool erase(const K& key)
{
Node* cur = _root;
Node* prev = nullptr;
while (cur)
{
if (key > cur->_val)
{
prev = cur;
cur = cur->_right;
}
else if (key < cur->_val)
{
prev = cur;
cur = cur->_left;
}
else //找到了,删除
{
//如果不是根结点
if (cur->_left == nullptr)
{
if (prev != nullptr)
{
//判断要删除结点是左孩子还是右孩子
if (prev->_left == cur)
{
prev->_left = cur->_right;
}
else
{
prev->_right = cur->_right;
}
}
else
{
_root = cur->_right;
}
delete cur;
}
else if (cur->_right == nullptr)
{
if (prev != nullptr)
{
//判断要删除结点是左孩子还是右孩子
if (prev->_left == cur)
{
prev->_left = cur->_left;
}
else
{
prev->_right = cur->_left;
}
}
else
{
_root = cur->_left;
}
delete cur;
}
else //当左右孩子都不为空时
{
//找到当前结点左子树最右结点
Node* neighbor = cur->_left;
Node* neighborParents = nullptr;
while (neighbor->_right)
{
neighborParents = neighbor;
neighbor = neighbor->_right;
}
if (neighborParents != nullptr)
{
neighborParents->_right = neighbor->_left;
}
else
{
cur->_left = neighbor->_left;
}
cur->_val = neighbor->_val;
delete neighbor;
//递归法
//Node* neighbor = cur->_left;
//while (neighbor->_right)
//{
// neighbor = neighbor->_left;
//}
//K temp = neighbor->_val;
//erase(temp);
//cur->_val = temp;
}
return true;
}
}
return false;
}
//递归版
bool Erase(const K& key)
{
return _Erase(_root, key);
}
Node* Find(const K& key)
{
return _Find(_root, key);
}
bool Insert(const K& key)
{
return _Insert(_root, key);
}
void inOrder()
{
_inOrder(_root);
std::cout << std::endl;
}
protected:
Node* _root = nullptr;
};
}
//KV搜索
namespace KV
{
template<class K, class V>
struct BSTreeNode
{
BSTreeNode(const K& key, const V& val)
: _key(key)
, _val(val)
, _left(nullptr)
, _right(nullptr)
{}
BSTreeNode<K, V>* _left;
BSTreeNode<K, V>* _right;
K _key;
V _val;
};
template<class K, class V>
class BSTree
{
typedef BSTreeNode<K, V> Node;
protected:
bool _Erase(Node*& root, const K& key)
{
if (root == nullptr)
{
return false;
}
if (root->_key > key)
{
return _Erase(root->_left, key);
}
else if (root->_key < key)
{
return _Erase(root->_right, key);
}
else
{
//找到了,左右孩子有空
if (root->_left == nullptr)
{
Node* temp = root;
root = root->_right;
delete temp;
return true;
}
if (root->_right == nullptr)
{
Node* temp = root;
root = root->_left;
delete temp;
return true;
}
//找左孩子最右结点
//Node* prev = root->_left;
//while (prev->_right)
//{
// prev = prev->_right;
//}
//const K key = prev->_key;
//const V value = prev->_val;
//_Erase(root->_left, key);
//root->_key = key;
//root->_val = value;
//非递归
Node* prev = nullptr;
Node* cur = root->_left;
while (cur->_right != nullptr)
{
prev = cur;
cur = cur->_right;
}
Node* temp = cur;
if (prev == nullptr)
{
root->_left = cur->_left;
}
else
{
prev->_right = cur->_left;
}
root->_key = temp->_key;
root->_val = temp->_val;
delete temp;
return true;
}
}
Node* _Find(Node* root, const K& key)
{
if (root == nullptr)
{
return nullptr;
}
if (key > root->_key)
{
return _Find(root->_right, key);
}
else if (key < root->_key)
{
return _Find(root->_left, key);
}
else
{
return root;
}
}
bool _Insert(Node*& root, const K& key, const V& val)
{
if (root == nullptr)
{
root = new Node(key, val);
return true;
}
if (key > root->_key)
{
return _Insert(root->_right, key, val);
}
else if (key < root->_key)
{
return _Insert(root->_left, key, val);
}
else
{
return false;
}
}
void _inOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_inOrder(root->_left);
std::cout << root->_key << ":" << root->_val << std::endl;;
_inOrder(root->_right);
}
void destory(Node* root)
{
if (root == nullptr)
{
return;
}
destory(root->_left);
destory(root->_right);
delete root;
}
Node* copy(Node* root)
{
if (root == nullptr)
{
return nullptr;
}
Node* copyNode = new Node(root->_key, root->_val);
copyNode->_left = copy(root->_left);
copyNode->_right = copy(root->_right);
return copyNode;
}
public:
BSTree()
: _root(nullptr)
{}
BSTree(const BSTree<K, V>& t)
{
_root = copy(t._root);
}
BSTree<K, V>& operator=(BSTree<K, V> t)
{
swap(_root, t._root);
return *this;
}
~BSTree()
{
destory(_root);
_root = nullptr;
}
//非递归
bool insert(const K& key, const V& val)
{
if (_root == nullptr)
{
_root = new Node(key, val);
}
else
{
Node* parents = nullptr;
Node* cur = _root;
while (cur)
{
if (key > cur->_key)
{
parents = cur;
cur = cur->_right;
}
else if (key < cur->_key)
{
parents = cur;
cur = cur->_left;
}
else
{
return false;
}
}
if (parents != nullptr)
{
cur = new Node(key, val);
if (key > parents->_key)
{
parents->_right = cur;
}
else
{
parents->_left = cur;
}
}
}
return true;
}
Node* find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (key > cur->_key)
{
cur = cur->_right;
}
else if (key < cur->_key)
{
cur = cur->_left;
}
else
{
return cur;
}
}
return nullptr;
}
bool erase(const K& key)
{
Node* cur = _root;
Node* prev = nullptr;
while (cur)
{
if (key > cur->_key)
{
prev = cur;
cur = cur->_right;
}
else if (key < cur->_key)
{
prev = cur;
cur = cur->_left;
}
else //找到了,删除
{
//如果不是根结点
if (cur->_left == nullptr)
{
if (prev != nullptr)
{
//判断要删除结点是左孩子还是右孩子
if (prev->_left == cur)
{
prev->_left = cur->_right;
}
else
{
prev->_right = cur->_right;
}
}
else
{
_root = cur->_right;
}
delete cur;
}
else if (cur->_right == nullptr)
{
if (prev != nullptr)
{
//判断要删除结点是左孩子还是右孩子
if (prev->_left == cur)
{
prev->_left = cur->_left;
}
else
{
prev->_right = cur->_left;
}
}
else
{
_root = cur->_left;
}
delete cur;
}
else //当左右孩子都不为空时
{
//找到当前结点左子树最右结点
Node* neighbor = cur->_left;
Node* neighborParents = nullptr;
while (neighbor->_right)
{
neighborParents = neighbor;
neighbor = neighbor->_right;
}
if (neighborParents != nullptr)
{
neighborParents->_right = neighbor->_left;
}
else
{
cur->_left = neighbor->_left;
}
cur->_key = neighbor->_key;
cur->_val = neighbor->_val;
delete neighbor;
//递归法
//Node* neighbor = cur->_left;
//while (neighbor->_right)
//{
// neighbor = neighbor->_left;
//}
//K tempK = neighbor->_key;
//V tempV = neighbor->_val;
//erase(temp);
//cur->_key = tempK;
//cur->_val = tempV
}
return true;
}
}
return false;
}
//递归版
bool Erase(const K& key)
{
return _Erase(_root, key);
}
Node* Find(const K& key)
{
return _Find(_root, key);
}
bool Insert(const K& key, const V& val)
{
return _Insert(_root, key, val);
}
void inOrder()
{
_inOrder(_root);
std::cout << std::endl;
}
protected:
Node* _root = nullptr;
};
}
5.5 二叉搜索树的应用
对于二叉搜索树,延伸出两种搜索模型,分别是K模型和KV模型:
- K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。
- KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对;再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对。
void test3()
{
KV::BSTree<string, int> countTree;
string arr[] = { "苹果", "橘子", "西瓜", "苹果", "西瓜", "苹果", "苹果", "芒果", "芒果", "芒果" };
for (string& str : arr)
{
KV::BSTreeNode<string, int>* ret = countTree.Find(str);
if (ret == nullptr)
{
countTree.Insert(str, 1);
}
else
{
++ret->_val;
}
}
countTree.inOrder();
}
int main()
{
test3();
return 0;
}
5.6 二叉搜索树性能分析
在二叉搜索树中,插入和删除操作都需要以查找为前提。所以查找操作就代表了二叉搜索树的性能。
最优情况下,二叉搜索树为完全二叉树,其平均比较次数为: log2N
最差情况下,二叉搜索树退化为单支树,其平均比较次数为: N/2
六、AVL树
6.1 AVL树的概念
二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
一颗AVL树具有以下性质:
它的左右子树都是AVL树
左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在 ,搜索时
间复杂度O( )。
6.2 AVL树节点定义
template<class T>
struct AVLTreeNode
{
AVLTreeNode(const T& data)
: _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr)
, _data(data), _bf(0)
{}
AVLTreeNode<T>* _pLeft; // 该节点的左孩子
AVLTreeNode<T>* _pRight; // 该节点的右孩子
AVLTreeNode<T>* _pParent; // 该节点的双亲
T _data;
int _bf; // 该节点的平衡因子
};
6.3 AVL树的插入
AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么AVL树的插入过程可以分为两步:
- 按照二叉搜索树的方式插入新节点
- 调整节点的平衡因子
我们假设新插入节点为cur,其双亲节点为parent。在cur插入之前,parent的平衡因子可能为0,1,-1三种情况。根据cur插入方向的不同,可分为:
- 如果cur插入到parent的左侧,只需给parent的平衡因子-1即可
- 如果cur插入到parent的右侧,只需给parent的平衡因子+1即可
此时,parent的平衡因子可能有三种情况,0,±1,±2,我们进行逐个分析:
- 如果parent的平衡因子为0,说明插入之前parent的平衡因子为正负1,插入后被调整成0,此
时满足 AVL树的性质,插入成功。 - 如果parent的平衡因子为正负1,说明插入前parent的平衡因子一定为0,插入后被更新成正负
1,此 时以parent为根的树的高度增加,需要继续向上更新。 - 如果pParent的平衡因子为正负2,则pParent的平衡因子违反平衡树的性质,需要对其进行旋转
处理。
pair<Node*, bool> insert(const pair<K, V>& kv)
{
if (_root == nullptr)
{
_root = new Node(kv);
return make_pair(_root, true);
}
Node* parent = _root, * cur = _root;
while (cur)
{
if (kv.first > cur->_kv.first)
{
parent = cur;
cur = cur->_right;
}
else if (kv.first < cur->_kv.first)
{
parent = cur;
cur = cur->_left;
}
else
{
return make_pair(cur, true);
}
}
cur = new Node(kv);
Node* newnode = cur;
if (kv.first > parent->_kv.first)
{
parent->_right = cur;
cur->_parent = parent;
}
else
{
parent->_left = cur;
cur->_parent = parent;
}
cur->_bf = 0;
//更新父节点到根结点的平衡因子
while (parent)
{
if (cur == parent->_left)
{
--parent->_bf;
}
else
{
++parent->_bf;
}
if (parent->_bf == 0)
{
break;
}
else if (parent->_bf == 1 || parent->_bf == -1)
{
cur = parent;
parent = parent->_parent;
}
else if (parent->_bf == 2 || parent->_bf == -2)
{
//不平衡,需要旋转
if (parent->_bf == -2)
{
if (cur->_bf == -1)
{
rotateR(parent);
}
else
{
rotateLR(parent);
}
}
else
{
if (cur->_bf == 1)
{
rotateL(parent);
}
else
{
rotateRL(parent);
}
}
break;
}
else
{
//平衡因子大于2,插入之前就不平衡了,可能其他地方出错了
assert(false);
}
}
return make_pair(newnode, true);
}
6.4 AVL树的旋转
根据节点插入位置的不同,AVL树的旋转分为四种:
新节点插入较高左子树的左侧—左左:右单旋
//右单旋
void rotateR(Node* root)
{
//根结点左孩子和根结点左孩子的右孩子
Node* rootL = root->_left;
Node* rootLR = rootL->_right;
Node* grandParent = root->_parent;
//右旋
root->_left = rootLR;
if (rootLR)
{
rootLR->_parent = root;
}
rootL->_right = root;
root->_parent = rootL;
if (grandParent == nullptr)
{
_root = rootL;
rootL->_parent = nullptr;
}
else
{
if (grandParent->_left == root)
{
grandParent->_left = rootL;
}
else
{
grandParent->_right = rootL;
}
rootL->_parent = grandParent;
}
root->_bf = 0;
rootL->_bf = 0;
}
新节点插入较高右子树的右侧—右右:左单旋
//左单旋
void rotateL(Node* root)
{
Node* rootR = root->_right;
Node* rootRL = root->_right->_left;
Node* grandParent = root->_parent;
rootR->_left = root;
root->_parent = rootR;
root->_right = rootRL;
if (rootRL)
{
rootRL->_parent = root;
}
if (grandParent == nullptr)
{
_root = rootR;
}
else
{
if (grandParent->_left == root)
{
grandParent->_left = rootR;
}
else
{
grandParent->_right = rootR;
}
}
rootR->_parent = grandParent;
root->_bf = 0;
rootR->_bf = 0;
}
新节点插入较高左子树的右侧—左右:先左单旋再右单旋
//左右双旋
void rotateLR(Node* root)
{
Node* rootL = root->_left;
Node* rootLR = rootL->_right;
int bf = rootLR->_bf;
rotateL(rootL);
rotateR(root);
if (bf == -1)
{
rootLR->_bf = 0;
rootL->_bf = 0;
root->_bf = 1;
}
else if (bf == 1)
{
rootLR->_bf = 0;
rootL->_bf = -1;
root->_bf = 0;
}
else if (bf == 0)
{
rootLR->_bf = 0;
rootL->_bf = 0;
root->_bf = 0;
}
else
{
assert(false);
}
}
新节点插入较高右子树的左侧—右左:先右单旋再左单旋
//右左双旋
void rotateRL(Node* root)
{
Node* rootR = root->_right;
Node* rootRL = rootR->_left;
int bf = rootRL->_bf;
rotateR(rootR);
rotateL(root);
//
if (bf == -1)
{
root->_bf = 0;
rootR->_bf = 1;
rootRL->_bf = 0;
}
else if (bf == 1)
{
root->_bf = -1;
rootR->_bf = 0;
rootRL->_bf = 0;
}
else if (bf == 0)
{
root->_bf = 0;
rootR->_bf = 0;
rootRL->_bf = 0;
}
else
{
assert(false);
}
}
总的来说,假设以parent为根的子树不平衡,平衡因子为2或-2:
当平衡因子为2时:说明右子树高,根据parent右子树的平衡因子的的不同,进行旋转:
右子树平衡因子为1,说明新增节点在右子树的右子树上,进行左单旋
右子树平衡因子为-1,说明新增节点在右子树的左子树上,进行左右双旋
当平衡因子为-2时:说明左子树高,根据parent左子树的平衡因子的的不同,进行旋转:
左子树平衡因子为-1,说明新增节点在左子树的左子树上,进行右单旋
左子树平衡因子为1,说明新增节点在左子树的右子树上,进行右左双旋
6.5 AVL树的性能
AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即 。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。
七、红黑树
7.1 红黑树的概念
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
7.2 红黑树的性质
- 每个结点不是黑色就是红色。
- 根结点为黑色。
- 如果一个结点为红色,则其两个孩子都是黑色的。
- 每个叶结点(指的是空结点)都是黑色的。
- 对于每个结点,从该结点到其所有后代的叶结点的简单路径上,均包含相同数目的黑色结点。
7.3 红黑树的插入
首先,按照二叉搜索树的规则插入新结点。
插入新结点后,判断红黑树的性质是否被破坏,大致分为以下情形:
首先假设当前结点为N,父结点为P,祖父结点为GP,叔叔结点U。
情形1: 若N为根结点,直接涂黑。
情形2: 父结点为黑色,直接插入即可。
情形3: P和U都为红,将P和U都涂黑,同时向上递归:
情形4: P为红,U为黑
情形4.1: P和N在同一边(都为父结点的左子树或右子树)
都为左子树时:
此时以祖父节点(GP)为支点进行右旋;然后将P涂黑,将GP涂红。
旋转后,P涂黑是因为要涂为原GP的黑色(往上兼容GP的父节点);而GP涂红则是因为右旋后,经过U的路径的黑色节点数量+1,涂红进行数量平衡。
都为右子树时:
此时以祖父节点(GP)为支点进行左旋;将P涂黑,将GP涂红。
情形4.2: P和N不在同一边
当P左N右时(P为左子树,N为右子树):
此时,以父节点§进行左旋,旋转后,以P作为新的平衡节点N,转至 [情形4.1.1 父N同左] 处理。
当P右N左时:
此时,以父节点§进行右旋,旋转后,以P作为新的平衡节点,此时再进行【情形4.1.2 父N同右】处理。