【BSTree】深入理解二叉搜索树

一、概念

二叉搜索树又称为二叉排序树,主要满足以下性质的二叉树则成为二叉搜索树:

  1. 若它的左子树不为空,则左子树上所有结点的值都小于根结点的值。
  2. 若它的右子树不为空,则右子树上所有结点的值都大于根结点的值。
  3. 它的左右子树都是二叉搜索树。
  4. 空树为二叉搜索树。

二、二叉搜索树的操作

(1)二叉搜索树的查找

二叉搜索树的查找既是从一条路径一直向下,直到找到所需要的结点值的位置,或者为NULL值。

根据二叉搜索树的性质,我们知道任何结点的左边结点的值一定小于该结点的值,右边的结点的值一定大于该结点的值,因此我们便可采用如下方法实现二叉搜索树的查找。

首先我们需要判断该结点是否为空

不为空则判断如下情况:

  1. 如果该结点key==需要查找的结点的key,则返回true;
  2. 如果该结点key < 需要查找的结点的key,则在该结点的右子树中查找;
  3. 如果该结点key > 需要查找的结点的key,则在该结点的左子树中查找;

否则返回true;

这里写图片描述

代码实现如下:

Node* pCur = _pRoot;
while (pCur)
{
    if (pCur->_key < key)
        pCur = pCur->_pRight;
    else if (pCur->_key > key)
        pCur = pCur->_pLeft;
    else if (pCur->_key == key)
        return true;
}
return false;

(2)二叉搜素树插入

二叉搜索树的插入可以分为迭代的解法和非迭代的解法,实现原理如下。

首先如果向二叉搜索树中插入新的元素,必须先检查这个元素是否在二叉搜索树中已经存在,如果已经存在,则不进行插入,如果不存在,则将新的元素插入到搜索停止的地方。

非递归版本插入的实现:

  1. 首先判断根结点是否为空,如果为空则直接构造一个结点,并且把此结点作为根结点。
  2. 接着采用二叉搜索树中的查找结点的方法,查找所需要插入元素的结点是否存在于二叉搜索树中。
  3. 判断如果二叉搜索树中已经存在了该值的结点,则返回false,插入失败,如果没有找到结点,则在此位置构造一个结点即可。

二叉搜索树插入的图示

树为空树时

这里写图片描述

树不为空时

这里写图片描述

实现代码如下

bool Insert(const k& key, const v& value)
{
    if (_pRoot == NULL)
        _pRoot = new Node(kev, value);
    Node* pCur = _pRoot;
    while (pCur)
    {
        if (pCur->_key < key)
            pCur = pCur->_pRight;
        else if (pCur->_key > key)
            pCur = pCur->_pLeft;
        else if (pCur->_key == key)
            return false;
    }
    pCur = new Node(key, value);
    return true;
}

递归版本插入的实现:

  1. 判断传入的结点是否为空,如果为空构造该结点。
  2. 如果该节点的key小于需要插入结点的key,则传入该节点的右边。
  3. 如果该结点的key大于需要插入结点的key,则传入该节点的左边。

实现代码如下:

bool InsertR(Node*& _pRoot, const K& key)
{
    if (NULL == _pRoot)
    {
        _pRoot = new Node(key);
        return true;
    }
    if (_pRoot->_key < key)
        return InsertR(_pRoot->pRight,key);
    if (_pRoot->_key > key)
        return InsertR(_pRoot->_pLeft, key);
    //右指针的别名
}

(3)二叉搜索树的删除

二叉搜索树的删除情况比较复杂,分为了集中情况,因此我便用图示法讲解:

这里写图片描述

方法如下:
先查找元素是否在二叉搜索树中,如果不存在,则删除失败,返回false,否则结点会有以下几种情况。

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

对于上述四种情况我们的删除方法如下:

  1. 直接删除结点。
  2. 删除该结点,并且把它的双亲结点指向该节点的左孩子结点。
  3. 删除该结点,并且把它的双亲结点指向该结点的右孩子结点。
  4. 在它的右子树中寻找中序遍历下的第一个结点,用它的值和需要删除结点的值进行替换,最后删除这个遍历的结点即可。(或者找左子树最大的结点一样可行)

针对以上情况有以下事例

3.1删除节点只有右孩子

这里写图片描述

以下情况可把根结点指向它的右边。

这里写图片描述

3.2 删除结点只有左孩子

这里写图片描述

以下情况可把根结点指向它的左边。

这里写图片描述

3.3 删除结点左右孩子都存在

此时有两种方法可以实现这个步骤。

方法一:

交换右树中序遍历第一个结点,然后删除此节点

这里写图片描述

方法二:

交换左子树key值最大的结点,然后删除此结点

这里写图片描述

代码实现

同二叉搜索树的插入,我们也来实现二叉搜索树的普通版本的删除和递归版本的删除

普通版本的删除代码:

bool Remove(Node* _Root, const K& key)
{
    Node* parent = NULL;
    if (_pRoot == NULL)
        return false;
    if (_pRoot->_key == key)
        delete(_pRoot);
    Node* cur = _pRoot;
    while (cur)
    {
        if (key > cur->_key)
        {
            parent = cur;
            cur = cur->_pRight;
        }
        else if (key < cur->_key)
        {
            parent = cur;
            cur = cur->_pLeft;
        }
        else
            break;
    }
    //此处找到要删除结点,或者为空
    if (cur)
    {
        //结点有右孩子没有左孩子
        if (cur->_pRight&&cur->_pLeft == NULL)
        {
            //此处需要判断结点是双亲的左子树和结点是双亲的右子树
            //同时判断根结点
            if (parent->_pLeft==cur)
            {
                parent->_pLeft = cur->_pRight;
            }
            else if (parent->_pRight == cur)
            {
                parent->_pRight = cur->_pRight;
            }
            else//根结点
            {
                _pRoot = cur->_pRight;
            }
            delete cur;
            return true;
        }
        //结点有左孩子没有右孩子
        if (cur->_pLeft&&cur->_pRight == NULL)
        {
            if (parent->_pLeft == cur)
            {
                parent->_pLeft = cur->_pLeft;
            }
            else if (parent->_pRight == cur)
                parent->_pRight = cur->_pLeft;
            else
                _pRoot = cur->_pLeft;
            delete cur;
            return true;
        }
        //左右孩子都存在
        //找到当前结点与中序遍历下的第一个结点ptr,
        //与删除结点进行交换,之后删除ptr
        else if (cur->_pLeft&&cur->_pRight)
        {
            Node* ptr = NULL;
            if (parent->_pLeft == cur)
            {
                ptr = Inorder(cur->_pLeft);
                cur->_key = ptr->_key;
                cur->_value = ptr->_value;
                delete ptr;
            }
            else if (parent->_pRight == cur)
            {
                ptr = Inorder(cur->_pRight);
                cur->_key = ptr->_key;
                cur->_value = ptr->_value;
                delete ptr;
            }
        }
    }
    return false;
}

递归版本的删除代码

bool RemoveR(Node*& _pRoot, const K& key)//此处是引用
{
    if (_pRoot == NULL)
        return false;
    if (_pRoot->_key < key)
        return RemoveR(_pRoot->_pRight, key);
    else if (_pRoot->_key > key)
        return RemoveR(_pRoot->_pLeft, key);
    else 
    {
        if (_pRoot->_pLeft == NULL)
            _pRoot = _pRoot->_pRight;
        else if (_pRoot->_pRight == NULL)
            _pRoot = _pRoot->_pLeft;
    }
}

三、完整代码

#include <iostream>
using namespace std;
typedef int DataType;
template<class k,class v>
struct BSTreeNode
{
    struct BSTreeNode* _pLeft;
    struct BSTreeNode* _pRight;
    k _key;
    v _value;
    BSTreeNode()
        :_key(NULL)
        , _value(NULL)
        , _pLeft(NULL)
        , _pRight(NULL)
    {}
};
template<class k,class v>
class BSTree
{
    typedef BSTreeNode<k, v> Node;
public:
    BSTree()
        :_pRoot(NULL)
    {}
    bool Insert(const k& key, const v& value)
    {
        if (_pRoot == NULL)
            _pRoot = new Node(kev, value);
        Node* pCur = _pRoot;
        while (pCur)
        {
            if (pCur->_key < key)
                pCur = pCur->_pRight;
            else if (pCur->_key > key)
                pCur = pCur->_pLeft;
            else if (pCur->_key == key)
                return false;
        }
        pCur = new Node(key, value);
        return true;
    }
    //递归版本插入
    bool InsertR(Node*& _pRoot, const K& key)
    {
        if (NULL == _pRoot)
        {
            _pRoot = new Node(key);
            return true;
        }
        if (_pRoot->_key < key)
            return InsertR(_pRoot->pRight,key);
        if (_pRoot->_key > key)
            return InsertR(_pRoot->_pLeft, key);
        //右指针的别名
    }
    bool Remove(Node* _Root, const K& key)
    {
        Node* parent = NULL;
        if (_pRoot == NULL)
            return false;
        if (_pRoot->_key == key)
            delete(_pRoot);
        Node* cur = _pRoot;
        while (cur)
        {
            if (key > cur->_key)
            {
                parent = cur;
                cur = cur->_pRight;
            }
            else if (key < cur->_key)
            {
                parent = cur;
                cur = cur->_pLeft;
            }
            else
                break;
        }
        //此处找到要删除结点,或者为空
        if (cur)
        {
            //结点有右孩子没有左孩子
            if (cur->_pRight&&cur->_pLeft == NULL)
            {
                //此处需要判断结点是双亲的左子树和结点是双亲的右子树
                //同时判断根结点
                if (parent->_pLeft==cur)
                {
                    parent->_pLeft = cur->_pRight;
                }
                else if (parent->_pRight == cur)
                {
                    parent->_pRight = cur->_pRight;
                }
                else//根结点
                {
                    _pRoot = cur->_pRight;
                }
                delete cur;
                return true;
            }
            //结点有左孩子没有右孩子
            if (cur->_pLeft&&cur->_pRight == NULL)
            {
                if (parent->_pLeft == cur)
                {
                    parent->_pLeft = cur->_pLeft;
                }
                else if (parent->_pRight == cur)
                    parent->_pRight = cur->_pLeft;
                else
                    _pRoot = cur->_pLeft;
                delete cur;
                return true;
            }
            //左右孩子都存在
            //找到当前结点与中序遍历下的第一个结点ptr,
            //与删除结点进行交换,之后删除ptr
            else if (cur->_pLeft&&cur->_pRight)
            {
                Node* ptr = NULL;
                if (parent->_pLeft == cur)
                {
                    ptr = Inorder(cur->_pLeft);
                    cur->_key = ptr->_key;
                    cur->_value = ptr->_value;
                    delete ptr;
                }
                else if (parent->_pRight == cur)
                {
                    ptr = Inorder(cur->_pRight);
                    cur->_key = ptr->_key;
                    cur->_value = ptr->_value;
                    delete ptr;
                }
            }
        }
        return false;
    }
    Node* Inorder(Node* Root)
    {
        Node* cur = Root;
        if (!cur)
        {
            return NULL;
        }
        if (cur->_pLeft)
            cur = cur->_pLeft;
        return cur;
    }
    //递归版本的删除
    bool RemoveR(Node*& _pRoot, const K& key)//此处是引用
    {
        if (_pRoot == NULL)
            return false;
        if (_pRoot->_key < key)
            return RemoveR(_pRoot->_pRight, key);
        else if (_pRoot->_key > key)
            return RemoveR(_pRoot->_pLeft, key);
        else 
        {
            if (_pRoot->_pLeft == NULL)
                _pRoot = _pRoot->_pRight;
            else if (_pRoot->_pRight == NULL)
                _pRoot = _pRoot->_pLeft;
        }
    }

private:
    Node* _pRoot;
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值