通俗易懂的图解二叉搜索树、AVL树和红黑树

搜索树(查找树)

搜索是一种很常见的需求,当数据较多时,如何找到目标对象时,即需要搜索查找。
那么如何快速找到目标呢,最朴素的思想就是暴力查找,一个一个对比。
显然,当数据量增大时,我们需要优化查找方法获取更快的查找速度,我们可以通过维护一个有序数组,从而实现二分查找,查找速度从O(n)降为O(longn)。
但是数组并不便于插入和删除,链表便成为了最好的选择。
既然是采用二分查找,那么不如直接按照查找的步骤维护一种数据结构,于是二叉搜索树出现了。
请添加图片描述

二叉搜索树

二叉搜索树的性质为:
若左子树不为空,则左子树上所有节点值都小于根结点值;
若右子树不为空,则右子树上所有节点值都大于根结点值;
左右子树依然具备二叉树的以上性质。
总结起来即是–左小右大。

二叉搜索树(Binary Search Tree)的实现

实现二叉搜索树的关键在于插入节点和删除节点。
对于插入节点,我们需要先从根节点开始为它找到一个空位,然后插入。
请添加图片描述

struct Node
{
    Node(int val): _val(val), left(nullptr), right(nullptr)
    {
    }
    Node* left;
    Node* right;
    int _val;
};

class BSTree
{
public:
    BSTree():root(nullptr)
    {}
    void addNode(Node* node)
    {
        if(!root)
        {
            root = node;
            return;
        }
        Node* tmp_node = root;
        while(tmp_node)
        {
            if(node->_val == tmp_node->_val)
            {
                return;
            }
            else if(node->_val < tmp_node->_val)
            {
                if(!tmp_node->left)
                    tmp_node->left = node;
                else
                    tmp_node = tmp_node->left;
            }
            else{
                if(!tmp_node->right)
                    tmp_node->right = node;
                else
                    tmp_node = tmp_node->right;
            }
        }
    }
    //层序输出
    void print()
    {
        queue<Node*> node_q;
        if(root)
            node_q.push(root);
        while(!node_q.empty())
        {
            Node* tmp_node = node_q.front();
            node_q.pop();
            printf("%d ", tmp_node->_val);
            if(tmp_node->left)
                node_q.push(tmp_node->left);
            if(tmp_node->right)
                node_q.push(tmp_node->right);
        }
        printf("\n");
    }
public:
    Node* root;
};
int main()
{
    BSTree bsTree;
    vector<int> num{5, 3, 9, 1, 6, 12};
    for(auto n:num)
    {
        Node* cur_node = new Node(n);
        bsTree.addNode(cur_node);
    }
    bsTree.print();
    bsTree.addNode(new Node(4));
    bsTree.print();
}

层序打印的结果:
请添加图片描述
对于删除节点,需要考虑:
1.该节点为叶子节点,即无子节点,直接删除即可;
2.该节点具有子节点,只有左子节点,则将左子节点的右子节点移动到删除位置,只有右子节点,将右子节点的左子节点移到删除位置;
3.同时具有左右子节点,可以选择左子树的最右节点移到删除位置,也可以选择右子树的最左;请添加图片描述

//找到并删除
    void deleteNode(int val)
    {
        Node* tmp_node = root;
        Node* parent = nullptr;
        while (tmp_node->_val != val)
        {
            parent = tmp_node;
            if(val < tmp_node->_val)
                tmp_node = tmp_node->left;
            else
                tmp_node = tmp_node->right;
        }
        if(tmp_node->left)
        {
            Node** back_node = &(tmp_node->left);
            while((*back_node)->right)
            {
                back_node = &((*back_node)->right);
            }
            swap(tmp_node->_val, (*back_node)->_val);
            *back_node = nullptr;
        }
        else if(tmp_node->right)
        {
            Node** back_node = &(tmp_node->right);
            while((*back_node)->left)
            {
                back_node = &((*back_node)->left);
            }
            swap(tmp_node->_val, (*back_node)->_val);
            *back_node = nullptr;
        }
        else{
            if(parent == nullptr)
            {
            	delete root;
                root = nullptr;
                return;
            }
            if(parent->left->_val == val)
            {
                delete  parent->left;
                parent->left = nullptr;
            }
            else{
            	delete  parent->right;
                parent->right = nullptr;
            }
        }
    }

AVL树

简介

新事物的出现必然是为了解决旧事物的缺点,AVL树是在二叉搜索树的基础上进化而来的。
前面提到为了和二分查找的步骤一致,将链表转换为了二叉树,如果在创建二叉搜索树的时候,输入的序列是有序的,那么二叉搜索树也就退化为了链表,查找性能也将变为O(N)。
怎么防止这种情况发生,如果二叉搜索树在有退化为链表的趋势(左右子树高度差较大)时可以自行调整就好了。请添加图片描述
于是,AVL树出现了,AVL树限定左右子树的高度差不能超过1,如果超过了1,就进行旋转操作从而自平衡。
在节点内部引入一个记录左右子树高度差的值,称为平衡因子(BF),在平衡情况下,其值在(-1,1)之间请添加图片描述
当插入一个新节点时,向上更新平衡因子,如果平衡因子为2或-2,则需要旋转操作,重新平衡。

AVL的旋转

AVL树保持平衡的关键即是旋转操作,那么如何旋转呢?
分析不平衡的情况即可得知,不平衡的情况被归纳为四种,也就对应了四种旋转操作。

左单旋和右单旋

先看一种简单的情况,右侧持续插入节点,4节点的BF=-2,显然需要

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值