手撕二叉搜索树(排序树)


二叉搜索树的概念

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:

  1. 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  2. 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  3. 它的左右子树也分别为二叉搜索树

在这里插入图片描述
以上则是一个二叉搜索树,左子树的值都小于根节点,右子树的值都大于根节点


一、二叉搜索树的查找和插入

查找:
a,从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找。
b,最多查找高度次,走到到空,还没找到,这个值不存在。
在这里插入图片描述

我们要在这颗树查找是否有13这个数,13比8大,则进入到根节点8的右子树,13比10大,则进入到根节点10的右子树,13比14小,则进入到根节点14的左子树,这时我们找到了13,返回true;

查找的代码实现:

//树的节点
template<class K>
struct BTSNood
{
    BTSNood(const K& key = K())
    :_key(key)
    ,_left(nullptr)
    ,_right(nullptr)
    {
        
    }
    BTSNood<K>* _left;
    BTSNood<K>* _right;
    K _key;
};

template <class T>
class BTSTree
{
    typedef BTSNood<T> Node;
public:
//非递归版本,查找
    bool Find(const T& key)
    {
        Node* cur = _root;
        while(cur)
        {
        //key比根节点要大,则进入根节点的右子树继续查找
            if(cur->_key > key)
            {
                cur = cur->_left;
            }
        //key比根节点要小,则进入根节点的左子树继续查找
            else if(cur->_key < key)
            {
                cur = cur->_right;
            }
          //找到了返回true
            else
            {
                return true;
            }
        }
        //循环找完还没有找到就不存在,返回false
        return false;
    }
private:
Node* _root;
}

插入:
a,当树刚开始为空时,则直接新增一个节点,赋值给root指针
b,树不空时,按二叉搜索树的性质,查找到插入位置,链接上
在这里插入图片描述

插入2时,2比根节点8要小,则进入根节点8的左子树,2比根节点3要小,则进入根节点3的左子树,2比根节点1要大则进入右子树,右子树刚好为空则插入2;

插入18时,18比根节点8要大,则进入根节点8的右子树,18比根节点10要大,则进入根节点10的右子树,18比根节点14要大则进入右子树,右子树刚好为空则插入18;

插入代码实现:

//树的节点
template<class K>
struct BTSNood
{
    BTSNood(const K& key = K())
    :_key(key)
    ,_left(nullptr)
    ,_right(nullptr)
    {
        
    }
    BTSNood<K>* _left;
    BTSNood<K>* _right;
    K _key;
};

template <class T>
class BTSTree
{
    typedef BTSNood<T> Node;
public:
//非递归版本,插入
    bool Insert(const T& key)
    {
    //如果树一开始为空,则直接创建新节点,给_root指针赋值
        if(_root == nullptr)
        {
            _root = new Node(key);
            return true;
        }
        //因为新节点要跟parent节点链接,所以我们在查找插入的节点时
        //需要记住parent节点
        Node* cur = _root;
        Node* parent = nullptr;
        while(cur)
        {
            parent = cur;
            if(cur->_key > key)
            {
                cur = cur->_left;
            }
            else if(cur->_key < key)
            {
                cur = cur->_right;
            }
            //也有可能插入失败,目前我们这个版本的二叉搜索树不允许相同的值出现
            else
            {
                return false;
            }
        }
        cur = new Node(key);
        //找到可插入的位置后,需要判断一下是在parent的左节点,还是在右节点
        //再进行链接
        if(parent->_key > key)
        {
            parent->_left = cur;
        }
        else
        {
            parent->_right = cur;
        }
        return true;
    }
private:
Node* _root;
}

在这里插入图片描述

二、二叉树的删除

首先要查找要删除的节点是否存在,如果不存在直接返回false,存在的话,删除分下面的四种情况:

  1. 要删除的节点无左右结点
  2. 要删除的节点只有右节点
  3. 要删除的节点只有左节点
  4. 要删除的节点有左右节点

实际1可以与2和3合并为一种情况,最后分为三种情况:

  1. 如果是情况2,要删除的节点只有右节点,则修改parent父节点指向该节点的右树,直接删除该节点。
  2. 如果是情况3,要删除的节点只有左节点,则修改parent父节点指向该节点的左树,直接删除该节点。
  3. 可以在该节点的左子树找最大的值,或右子树找最小的值,跟要删除的节点替换,最后删除被替换的节点即可。

在这里插入图片描述

代码实现:

//树的节点
template<class K>
struct BTSNood
{
    BTSNood(const K& key = K())
    :_key(key)
    ,_left(nullptr)
    ,_right(nullptr)
    {
        
    }
    BTSNood<K>* _left;
    BTSNood<K>* _right;
    K _key;
};

template <class T>
class BTSTree
{
    typedef BTSNood<T> Node;
public:
//非递归版本,删除
    bool Erase(const T& key)
    {
    //首先先跟查找一样,找到要删除的值
        Node* cur = _root;
        Node* parent = nullptr;
        while(cur)
        {
            if(cur->_key > key)
            {
                parent = cur;
                cur = cur->_left;
            }
            else if(cur->_key < key)
            {
                parent = cur;
                cur = cur->_right;
            }
            else
            {
            //找到后分三种情况处理
                //左为空,托孤,让parent节点,指向cur->_right
                if(cur->_left == nullptr)
                {
                    //如果这个值是根节点,没办法托孤,需要单独处理
                    if(cur == _root)
                    {
                        _root = cur->_right;
                    }
                    else
                    {
						//需要注意要判断parent的左节点是cur还是右节点
                        if(parent->_left == cur)
                        {
                            parent->_left = cur->_right;
                        }
                        else
                        {
                            parent->_right = cur->_right;
                        }
                    }
                    delete cur;
                }
                //右为空,托孤,让parent节点,指向cur->_left
                else if(cur->_right == nullptr)
                {
                    //如果这个值是根节点,没办法托孤,需要单独处理
                    if(cur == _root)
                    {
                        _root = cur->_left;
                    }
                    else
                    {
                        if(parent->_left == cur)
                        {
                            parent->_left = cur->_left;
                        }
                        else
                        {
                            parent->_right = cur->_left;
                        }
                    }
                    delete cur;
                }
                //左右不为空,则需要找替换删除
                else
                {
                    //找到右边最小的值,跟要删除的值替换
                    //或找到左边最大的值,跟要删除的值替换
                    Node* parent = cur;
                    Node* minRight = cur->_right;
                    while(minRight->_left)
                    {
                        parent = minRight;
                        minRight = minRight->_left;
                    }
                    cur->_key = minRight->_key;
                    if(parent->_left == minRight)
                    {
                        parent->_left = minRight->_right;
                    }
                    else
                    {
                        parent->_right = minRight->_right;
                    }
                    delete minRight;
                }
                return true;
            }
        }
        return false;
    }
private:
Node* _root;
}

1.插入,查找,删除的递归版本

代码如下:

	void InOrder()
    {
        _InOrder(_root);
        std::cout << std::endl;
    }
    bool EraseR(const T& key)
    {
        return _EraseR(_root,key);
    }
    
    bool InsertR(const T& key)
    {
        return _InsertR(_root,key);
    }
    //递归版本
    bool FindR(const T& key)
    {
        return _FindR(_root,key);
    }
private:
bool _EraseR(Node*& root,const T& key)
    {
        //查找的代码
        if(root == nullptr)
        {
            return false;
        }
        if(root->_key > key)
        {
            return _EraseR(root->_left, key);
        }
        else if(root->_key < key)
        {
            return _EraseR(root->_right, key);
        }
        else
        {
            Node* del = root;
            //找到后分三种情况处理
            if(root->_left == nullptr)
            {
                root = root->_right;
            }
            else if(root->_right == nullptr)
            {
                root = root->_left;
            }
            else
            {
                //如果左右子树不为空,则找右边最小的数替换
                //然后递归替换后节点的右子树删除
                Node* minRight = root->_right;
                while(minRight->_left)
                {
                    minRight = minRight->_left;
                }
                std::swap(root->_key,minRight->_key);
                return _EraseR(root->_right, key);
            }
            delete  del;
            return true;
        }
    }

    
    bool _InsertR(Node*& root,const T& key)
    {
        if(root == nullptr)
        {
            root = new Node(key);
            return true;
        }
        if(root->_key < key)
        {
            return _InsertR(root->_right, key);
        }
        else if(root->_key > key)
        {
            return _InsertR(root->_left, key);
        }
        else
        {
            return false;
        }
        
    }
    
    bool _FindR(Node* root,const T& key)
    {
        if(root == nullptr)
        {
            return false;
        }
        if(root->_key > key)
        {
            return _FindR(root->_left,key);
        }
        else if(root->_key < key)
        {
            return _FindR(root->_right, key);
        }
        else
        {
            return true;
        }
    }

2.二叉搜索树的应用场景

KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方式在现实生活中非常常见:

  1. 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对;
  2. 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对

改造搜索二叉树

//节点增加一个模版参数
template<class K,class V>
    struct BSTreeNode
    {
        BSTreeNode<K,V>* _left;
        BSTreeNode<K,V>* _right;
        K _key;
        V _value;
        BSTreeNode(const K& key,const V& value)
        :_key(key)
        ,_value(value)
        ,_left(nullptr)
        ,_right(nullptr)
        {
            
        }
    };

    template<class K, class V>
    class BSTree
    {
    typedef BSTreeNode<K, V> Node;
    public:
    bool Insert(const K& key, const V& value)
    {
        if(_root == nullptr)
        {
            _root = new Node(key,value);
            return true;
        }
        Node* cur = _root;
        Node* parent = nullptr;
        while(cur)
        {
            parent = cur;
            if(key > cur->_key)
            {
                cur = cur->_right;
            }
            else if (key < cur->_key)
            {
                cur = cur->_left;
            }
            else
            {
                return false;
            }
        }
        if(parent->_key < key)
        {
            parent->_right = new Node(key,value);
        }
        else
        {
            parent->_left = new Node(key,value);
        }
        return true;
    }
    Node* Find(const K& key)
    {
        Node* cur = _root;
        while(cur)
        {
            if(cur->_key < key)
            {
                cur = cur->_right;
            }
            else if(cur->_key > key)
            {
                cur = cur->_left;
            }
            else
            {
                return cur;
            }
        }
        return nullptr;
    }
        
    bool Erase(const K& key)
    {
        Node* cur = _root;
        Node* parent = nullptr;
        while(cur)
        {
            if(cur->_key < key)
            {
                parent = cur;
                cur = cur->_right;
            }
            else if(cur->_key > key)
            {
                parent = cur;
                cur = cur->_left;
            }
            else
            {
                //找到这个值了,需要进行处理,有三种情况
                //左为空,或右为空都可以进行托孤处理
                //左右不为空则需要寻找替换值处理
                //如果左为空,则进行托孤处理
                if(cur->_left == nullptr)
                {
                    //如果这个值是根节点需要单独处理
                    if(_root == cur)
                    {
                        _root = cur->_right;
                    }
                    else
                    {
                        //需要知道,cur是parent的左树还是右树
                        if(parent->_left == cur)
                        {
                            parent->_left = cur->_right;
                        }
                        else
                        {
                            parent->_right = cur->_right;
                        }
                    }
                    delete cur;
                }
                else if(cur->_right == nullptr)
                {
                    //如果这个值是根节点需要单独处理
                    if(_root == cur)
                    {
                        _root = cur->_left;
                    }
                    else
                    {
                        //需要知道,cur是parent的左树还是右树
                        if(parent->_left == cur)
                        {
                            parent->_left = cur->_left;
                        }
                        else
                        {
                            parent->_right = cur->_left;
                        }
                    }
                    delete cur;
                }
                else
                {
                    //要找到左树最大的值,或右树最小的值进行替换删除
                    Node* parent = cur;
                    Node* minRight = cur->_right;
                    while(minRight->_right)
                    {
                        parent = minRight;
                        minRight = minRight->_right;
                    }
                    cur->_key = minRight->_key;
                    if(parent->_left == minRight)
                    {
                        parent->_left = minRight->_left;
                    }
                    else
                    {
                        parent->_right = minRight->_left;
                    }
                    delete minRight;
                }
            }
        }
    }
    void _InOrder(Node* root)
    {
        if(root == nullptr)
        {
            return ;
        }
        _InOrder(root->_left);
        std::cout << root->_key << ":" << root->_value << std::endl;
        _InOrder(root->_right);
    }
    void InOrder()
    {
        _InOrder(_root);
    }
    private:
    Node* _root = nullptr;
    };
    
//测试用例
void TestBSTree4()
{
// 统计水果出现的次数
string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
"苹果", "香蕉", "苹果", "香蕉" };
BSTree<string, int> countTree;
for (const auto& str : arr)
{
// 先查找水果在不在搜索树中
// 1、不在,说明水果第一次出现,则插入<水果, 1>
// 2、在,则查找到的节点中水果对应的次数++
//BSTreeNode<string, int>* ret = countTree.Find(str);
auto ret = countTree.Find(str);
if (ret == NULL)
{
countTree.Insert(str, 1);
}
else
{
ret->_value++;
}
}
countTree.InOrder();
}

测试结果
在这里插入图片描述


总结

例如:以上就是今天要讲的内容,本文仅仅介绍二叉搜索树的查找,插入,删除的代码实现,和一些细节注意。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值