【C++进阶3-二叉搜索树】强,但没貌似还不够?

今天,带来二叉搜索树的讲解。

文中不足错漏之处望请斧正!


是什么

二叉搜索树(Binary Search Tree)又称二叉排序树。

它可以是一棵空树,也可以是具有以下性质的二叉树:

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

实现

二叉搜索树有两种搜索模型——Key搜索模型和搜索模型。

Key搜索模型

Key模型的BST每个结点内存一个Key值,即用Key作为关键码,Key本身就是搜索需要找到的值。

结构

template<class K>
struct BSTreeNode
{
    BSTreeNode(const K& key)
    :_left(nullptr),
    _right(nullptr),
    _key(key)
    {}
    
    BSTreeNode<K>* _left = nullptr;
    BSTreeNode<K>* _right = nullptr;
    K _key;
};

template<class K>
class BSTree
{
    typedef BSTreeNode<K> Node;
public:
		Node* _root = nullptr;
};

Insert

思路:key小往左走,key大往右走。

参考代码

		//依照key值找插入位置(BST元素不重复)
    bool Insert(const K& key)
    {
        if(_root == nullptr)
        {
            _root = new Node(key);
            return true;
        }
        
        Node* cur = _root, *parent = nullptr;
        while(cur)
        {
            if(cur->_key < key)
            {
                parent = cur;
                cur = cur->_right;
            }
            else if(key < cur->_key)
            {
                parent = cur;
                cur = cur->_left;
            }
            else
                return false;
        }
        
        cur = new Node(key);
        if(parent->_key < key)
            parent->_right = cur;
        else
            parent->_left = cur;
        
        return true;
    }

Erase

思路:

1. 要删的是根/没有孩子的结点

【C++进阶3-二叉搜索树】强,但没貌似还不够?_第1张图片

:直接删

2. 要删的有左孩子/右孩子

【C++进阶3-二叉搜索树】强,但没貌似还不够?_第2张图片

:将左/右孩子托付给自己的父。

3. 要删的有左右孩子

【C++进阶3-二叉搜索树】强,但没貌似还不够?_第3张图片

【C++进阶3-二叉搜索树】强,但没貌似还不够?_第4张图片

替换法删除:找一个合适的结点替换删除。

找谁?

左子树最大 / 右子树最小都可以。

  • BST规则:左子树的全部结点都比右子树小,也可以说右子树的全部结点都比左子树的大。
  • 左子树最大的上来作根:比左子树的都大,比右子树的都小()
  • 右子树最小的上来作根:比右子树的都小,比左子树的都大(BST规则:左子树的全部都比右子树小)

参考代码

		bool Erase(const K& key) //删除 = 删除 + 链接
    {
        if(Empty()) return false;
        
        Node* cur = _root, *parent = nullptr;
        while(cur)
        {
            if(cur->_key < key)
            {
                parent = cur;
                cur = cur->_right;
            }
            else if(key < cur->_key)
            {
                parent = cur;
                cur = cur->_left;
            }
            else break;
        }
        
        if(cur == nullptr) return false;
        
        //走到这,cur即要删除的结点
        
        //要删的是根 / 要删的结点没有孩子都可以直接包含在这种条件内(复用代码)
        //2. 要删的结点有右孩子 ==> 将右子树托孤
        if(cur->_left == nullptr)
        {
            if(cur == _root)
            {
                _root = cur->_right;
            }
            else
            {
                if(cur == parent->_left)
                    parent->_left = cur->_right;
                else
                    parent->_right = cur->_right;
            }
            delete cur;
            return true;
        }
        //2. 要删的结点有左孩子 ==> 将左子树托孤
        else if(cur->_right == nullptr)
        {
            if(cur == _root)
            {
                _root = cur->_left;
            }
            else
            {
                if(cur == parent->_left)
                    parent->_left = cur->_left;
                else
                    parent->_right = cur->_left;
            }
            delete cur;
            return true;
        }
        //3. 要删的结点有左右孩子 ==> 替换法删除
        else
        {
            Node* minRight = cur->_right, *parent = cur;
            while(minRight->_left) //此分支内,minRight肯定不为空,可以直接解引用
            {
                parent = minRight;
                minRight = minRight->_left;
            }
            
						//覆盖掉要删的
            cur->_key = minRight->_key; 
            if(minRight == parent->_left)
                parent->_left = minRight->_right;
            else
                parent->_right = minRight->_right;
            //我已经去作老大了,这里得清理干净
            delete minRight;
            return true;
        }
        
        return false;
    }

InsertR

可以用递归实现一下,有个很妙的点。

我们插入和删除最大的难点就是链父,插入了,我的父是谁,删除了,我的父是谁?

在递归里,这样的场景我们可以用引用做参数——使得root是父亲的左/右孩子的引用

		bool _InsertR(Node*& root, const K& key)
    {
        //最大的问题是链父,root作引用,是父结点的left/right
        if(root == nullptr)
        {
            root = new Node(key); //root是父结点的left/right的引用,这一步赋值相当于链父了
            return true;
        }
        
        if(root->_key < key)
            return _InsertR(root->_right, key);
        else if(key < root->_key)
            return _InsertR(root->_left, key);
        else
            return false;
    }

EraseR

递归里我们不能用替换法直接覆盖删了,但是思路一样,还是需要替换,不过绕了个弯子。

我们可以替换,让左子树最大/右子树最小,也可以说是最左/最右结点作根,原本的根换下去。

有两个点:

  1. 最左结点/最右结点必然有一侧是空的,那就可以直接删除或托孤
  2. 替换后不符合BST规则:整体来说不符合,但是从局部来说还符合

局部符合有什么用?
【C++进阶3-二叉搜索树】强,但没貌似还不够?_第5张图片

局部符合我们就可以在局部删!

	bool _EraseR(Node*& root, const K& key)
    {
        if(root == nullptr) return false;
        
        if(root->_key < key)
            return _EraseR(root->_right, key);
        else if(key < root->_key)
            return _EraseR(root->_left, 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;
                //交换法:直接找key删,没法删,找替换节点交换,交换后可以删替换节点
                //(因为是最左/最右结点,一定有一侧是空的,也就可以托孤)
                swap(root->_key, minRight->_key);
                //交换后,root为根的子树不符合BST,无法递归,会找不到key
                //但是,因为找来的替换节点是右子树的最左节点,替换后右子树保持BST
                //就可以转换成到root->_right为根的子树找,而交换后,我们可以用托孤的方式删除minRight/maxLeft
                _EraseR(root->_right, key);
            }
            
            delete del;
            
            return true;
        }
    }

子函数

我们把这些需要传递私有成员的函数都作为子函数,或是弄一层函数重载——外面调用时是无法访问私有成员,所以无法传参。

*其实可以写一个GetRoot这种函数给外面提供,但是暴露底层了,不好

子函数:

		//外面可以直接调用
		void InOrder()
		{
			 _InOrder(_root); 
		}
		//封一层
		void _InOrder(Node* root)
    { ... }

函数重载:

		//给外面调用
		void InOrder()
		{
			 InOrder(_root); 
		}
		//给内部调用
		void _InOrder(Node* root)
    { ... }

还有一些拷贝构造和析构什么的,完善一下就好了。

*整体代码在文末


搜索模型

KV模型的BST每个结点内存一个pair键值对,即用pair中的Key作为关键码,Key对应的Value是需要找到的值。

关于键值对,它存放了一个键和相应值的对,键用于在树中进行查找,而值则相当于查找键所得到的结果。

C++中的pair类模板是一种将两个值组合成一个单元的数据结构。这对于需要将一对值作为一个单元处理的情况非常有用。pair模板具有两个模板参数,一个表示第一个值的类型,另一个表示第二个值的类型。例如,pair是一个由int和string类型组成的单元。我们此处用它来将key和value组合成一个单元,实现KV搜索模型。

实现

实现上我们从简,只玩儿精华,看到它和K模型的区别即可。

//KV查找模型:key value
namespace KV
{
template<class K, class V>
struct BSTreeNode
{
    BSTreeNode<K, V>* _left = nullptr;
    BSTreeNode<K, V>* _right = nullptr;
    K _key;
    V _val;
    
    BSTreeNode(const K& key, const V& val)
    :_left(nullptr),
    _right(nullptr),
    _key(key),
    _val(val)
    {}
};

template<class K, class V>
class BSTree
{
public:
    typedef BSTreeNode<K, V> Node;
    
    BSTree()
    :_root(nullptr)
    {}

    bool Insert(const K& key, const V& val)
    {
        if(_root == nullptr)
        {
            _root = new Node(key, val);
            return true;
        }
        
        Node* cur = _root, *parent = nullptr;
        while(cur)
        {
            if(cur->_key < key)
            {
                parent = cur;
                cur = cur->_right;
            }
            else if(key < cur->_key)
            {
                parent = cur;
                cur = cur->_left;
            }
            else
                return false;
        }
        
        cur = new Node(key, val);
        if(parent->_key < key)
            parent->_right = cur;
        else
            parent->_left = cur;
        
        return true;
    }
    
    void InOrder()
    {
        _InOrder(_root); //递归必须要传root:子函数内部传参,不想写GetRoot暴露root
        cout << endl;
    }
    
    Node* Find(const K& key)
    {
        Node* cur = _root;
        while(cur)
        {
            if(cur->_key == key)
                return cur;
            else if(cur->_key < key)
                cur = cur->_right;
            else
                cur = cur->_left;
        }
        return nullptr;
    }

    void _InOrder(Node* root)
    {
        if(root == nullptr) return;
        
        _InOrder(root->_left);
        
        cout << "["
                << root->_key
                << ", "
                << root->_val
            << "]" << endl;
        
        _InOrder(root->_right);
    }
private:
    
    Node* _root = nullptr;
};

BST性能分析

最优情况

是或接近完全二叉树,据key搜索一次需要比较logN次,时间复杂度为O(N)。

【C++进阶3-二叉搜索树】强,但没貌似还不够?_第6张图片
还是很不错的哦!

最差情况

是或接近单支树,据key搜索一次需要比较N次,时间复杂度为O(N)。

【C++进阶3-二叉搜索树】强,但没貌似还不够?_第7张图片
但是会退化,感觉不够强……


K模型BST的整体参考代码

整体参考代码

//Key搜索模型
template<class K>
struct BSTreeNode
{
    BSTreeNode(const K& key)
    :_left(nullptr),
    _right(nullptr),
    _key(key)
    {}
    
    BSTreeNode<K>* _left = nullptr;
    BSTreeNode<K>* _right = nullptr;
    K _key;
};

template<class K>
class BSTree
{
    typedef BSTreeNode<K> Node;
public:
    BSTree()
    :_root(nullptr)
    {}
    
    ~BSTree()
    {
        Destroy(_root);
    }
    
    BSTree(const BSTree<K>& t)
    {
        _root = Copy(t._root);
    }
    
    BSTree<K>& operator=(BSTree<K> t)
    {
        swap(_root, t._root);
        return *this;
    }
    
    //依照key值找插入位置(BST元素不重复)
    bool Insert(const K& key)
    {
        if(_root == nullptr)
        {
            _root = new Node(key);
            return true;
        }
        
        Node* cur = _root, *parent = nullptr;
        while(cur)
        {
            if(cur->_key < key)
            {
                parent = cur;
                cur = cur->_right;
            }
            else if(key < cur->_key)
            {
                parent = cur;
                cur = cur->_left;
            }
            else
                return false;
        }
        
        cur = new Node(key);
        if(parent->_key < key)
            parent->_right = cur;
        else
            parent->_left = cur;
        
        return true;
    }
    
    void InOrder()
    {
        _InOrder(_root); //递归必须要传root:子函数内部传参,不想写GetRoot暴露root
        cout << endl;
    }
    
    bool Find(const K& key)
    {
        Node* cur = _root;
        while(cur)
        {
            if(cur->_key == key)
                return true;
            else if(cur->_key < key)
                cur = cur->_right;
            else
                cur = cur->_left;
        }
        return false;
    }
    
    bool Empty() { return _root == nullptr;}
    
    bool Erase(const K& key) //删除 = 删除 + 链接
    {
        if(Empty()) return false;
        
        Node* cur = _root, *parent = nullptr;
        while(cur)
        {
            if(cur->_key < key)
            {
                parent = cur;
                cur = cur->_right;
            }
            else if(key < cur->_key)
            {
                parent = cur;
                cur = cur->_left;
            }
            else break;
        }
        
        if(cur == nullptr) return false;

        if(cur->_left == nullptr)
        {
            if(cur == _root)
            {
                _root = cur->_right;
            }
            else
            {
                if(cur == parent->_left)
                    parent->_left = cur->_right;
                else
                    parent->_right = cur->_right;
            }
            delete cur;
            return true;
        }
        else if(cur->_right == nullptr)
        {
            if(cur == _root)
            {
                _root = cur->_left;
            }
            else
            {
                if(cur == parent->_left)
                    parent->_left = cur->_left;
                else
                    parent->_right = cur->_left;
            }
            delete cur;
            return true;
        }
        else
        {
            Node* minRight = cur->_right, *parent = cur;
            while(minRight->_left) //此分支内,minRight肯定不为空
            {
                parent = minRight;
                minRight = minRight->_left;
            }
            
            cur->_key = minRight->_key; //左子树最大 / 右子树最小都符合
            if(minRight == parent->_left)
                parent->_left = minRight->_right;
            else
                parent->_right = minRight->_right;
            
            delete minRight;
            return true;
        }
        
        return false;
    }
    

    
    bool InsertR(const K& key)
    {
        return _InsertR(_root, key);
    }
    
    bool EraseR(const K& key)
    {
        return _EraseR(_root, key);
    }

    bool FindR(const K& key)
    {
        return _FindR(_root, key);
    }

private:
    void Destroy(Node* root)
    {
        if(root == nullptr) return;
        
        Destroy(root->_left);
        Destroy(root->_right);
        
        delete root;
    }
    
    Node* Copy(Node* root)
    {
        if(root == nullptr) return nullptr;
        
        Node* newRoot = new Node(root->_key);
        newRoot->_left = Copy(root->_left);
        newRoot->_right = Copy(root->_right);
        
        return newRoot;
    }
    
    void _InOrder(Node* root)
    {
        if(root == nullptr) return;
        
        _InOrder(root->_left);
        cout << root->_key << " ";
        _InOrder(root->_right);
    }
    
    bool _InsertR(Node*& root, const K& key)
    {
        //最大的问题是链父,root作引用,是父结点的left/right
        if(root == nullptr)
        {
            root = new Node(key); //root是父结点的left/right的引用,这一步赋值相当于链父了
            return true;
        }
        
        if(root->_key < key)
            return _InsertR(root->_right, key);
        else if(key < root->_key)
            return _InsertR(root->_left, key);
        else
            return false;
    }
    
    bool _EraseR(Node*& root, const K& key)
    {
        if(root == nullptr) return false;
        
        if(root->_key < key)
            return _EraseR(root->_right, key);
        else if(key < root->_key)
            return _EraseR(root->_left, 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;
                //交换法:直接找key删,没法删,找替换节点交换,交换后可以删替换节点
                //(因为是最左/最右结点,一定有一侧是空的,也就可以托孤)
                swap(root->_key, minRight->_key);
                _EraseR(root->_right, key);
            }
            
            delete del;
            
            return true;
        }
    }
    
    bool _FindR(Node*& root, const K& key)
    {
        if(root == nullptr) return false;
        
        if(root->_key < key)
            return _FindR(root->_right, key);
        else if(key < root->_key)
            return _FindR(root->_left, key);
        else
            return true;
    }
    
    
    Node* _root = nullptr;
};

今天的分享就到这里了,感谢您能看到这里。

这里是培根的blog,期待与你共同进步!

你可能感兴趣的:(C++,c++,算法,开发语言)