平衡搜索树
在原来的二叉搜索树我们提到,如果对普通的二叉搜索树连续有序地插入会导致二叉搜索树退化成链表,操作复杂的从O(log N)退化到O(N)。而AVL树和红黑树这些平衡二叉树就是避免这种最坏情况的出现,对左右孩子子树高度差大于2时进行重新调整,让搜索树始终保持一个健康的状态,保证相关操作都在O(log N)。同时关于STL,哈希表<unordered_map>(原为<hash_map>,已弃用)和平衡搜索树的区别有如下:
1).当我们想要根据关键字进行查找、插入和删除时,我们认为散列技术在性能方面超过了平衡搜索树,因为散列技术最好(平均)情况为O(1)
2).当我们要进行字典操作,而且操作时间给出了限制,我们选择平衡搜索树,因为它对最坏情况有保证
3).如果对于按照名次或者范围进行的查找、删除等操作,以及那些不按精确关键字匹配的模糊匹配进行的操作,我们选择平衡搜索树,因为散列技术不保证元素是有序的
AVL树和红黑树都是用“旋转”来保持平衡。AVL树对每个插入操作最多需要一次旋转,原因是它在旋转后的高度和插入前高度相同,但是对于删除操作,因为它不保证删除前后子树高度是否变化,因此它最多会进行O(height)==O(logN)次旋转。而对于红黑树,它的每个插入和删除都只需要一次旋转。
本文给出AVL树的定义和实现。
AVL树
定义
一棵空的二叉树是AVL树;如果T是一棵非空的二叉树,T(L)和T®分别是它的左右子树,那么当T满足以下条件时,T是一个棵AVL树:
1).T( L )和T( R )是AVL树
2).T( L )和T( R )的高度差的绝对值小于等于1
旋转
当某一节点的左右子树高度差出现2或者-2时,意味着将要对它进行旋转。其中,左子树高度-右子树高度为2时,包含了两种旋转方式:LL旋转和LR旋转。左子树的右孩子节点为空时,需要进行LL旋转,而左子树的左孩子节点为空时,则需要进行LR旋转。其中LR旋转也即先对左子树的左孩子节点做RR旋转,再对左子树根节点做LL旋转。当左子树的左右孩子都不为空时,两种旋转方式都可以对他进行平衡调整。左右子树高度差为-2时的情况与2的情况对称。
具体实现C++
本文包含二叉搜索树的常见操作。其中插入、删除操作同时实现了递归和非递归的方法,非递归方法需要使用到栈来模拟递归,因此使用到了以前编写的栈容器,当然也可以用标准库的栈来代替。其余涉及到的思考和想法见注释。
#pragma once
#ifndef AVLTREE_H
#define AVLTREE_H
#include <iostream>
using std::cout; using std::ends; using std::endl;
#include "../..//..//ch08/Stack/Stack/stackArray.h"
//AVL树节点类
template <typename T>
struct AVLNode
{
T value;
int height;//节点子树的高度
AVLNode* left;
AVLNode* right;
AVLNode(const T& _value, int _height = 1, AVLNode * _left = nullptr, AVLNode * _right = nullptr) :
value(_value), height(_height), left(_left), right(_right) {
}
};
//AVL树
template <typename T>
class AVLTree {
public:
AVLTree() :root(nullptr), treeSize(0) {
}
~AVLTree() {
destroy(root); }
bool empty()const {
return treeSize == 0; }
size_t size()const {
return treeSize; }
T* find(const T& _value)const;
void insert(const T& _value);
void erase(const T& _value);
void insertByRecursion(const T& _value) {
recusiveInsert(root, _value); }
void eraseByRecursion(const T& _value) {
recusiveErase(root, _value); }
void accengdingOutput()const {
inOrderOutput(root); }
size_t height()const {
return getSubtreeHeight(root); }
void clear() {
destroy(root); root = nullptr; treeSize = 0; }
private:
AVLNode<T>* root;
size_t treeSize;
private:
void destroy(AVLNode<T>* _node); //删除给定节点的树
void inOrderOutput(AVLNode<T>* _node)const;//搜索树的中序遍历 也就是将数据升序输出
//四种旋转调整
AVLNode<T>* LL(AVLNode<T>* _node);
AVLNode<T>* RR(AVLNode<T>* _node);
AVLNode<T>* LR(AVLNode<T>* _node);
AVLNode<T>* RL(AVLNode<T>* _node);
int getSubtreeHeight(AVLNode<T>* _node) const {
//返回给定节点作为树的高度 如果为空指针则返回0
return (_node == nullptr) ? 0 : _node->height;
}
//注意:递归实现则必须传指针的引用(或者二级指针),因为旋转后必须通过改变上级递归(父节点)的指针地址来得到新的根节点
AVLNode<T>* recusiveInsert(AVLNode<T>*& _node, const T& _value);//对给定节点的子树进行插入的递归实现
AVLNode<T>* recusiveErase(AVLNode<T>*& _node, const T& _value);//对给定节点的子树进行删除的递归实现
};
template <typename T>
void AVLTree<T>::destroy(AVLNode<T>* _node) {
//二叉树的递归删除都应该选择后序遍历
if (_node == nullptr)
return;
destroy(_node->left);
destroy(_node->right);
delete _node;
}
template <typename T>
void AVLTree<T>::inOrderOutput(AVLNode<T>* _node) const {
if (_node == nullptr)
return;
inOrderOutput(_node->left);
cout << _node->v