Bootstrap

【C++】 二叉排序树BST(二叉搜索树)

目录

二叉搜索树的概念

二叉搜索树操作

基本框架

插入

二叉搜索树的删除(重点)

二叉搜索树的查找

拷贝构造

析构函数

operator =

遍历

递归构造搜索二叉树

插入

删除

查找

二叉树的应用

二叉搜索树的性能分析

代码示例

BSTree.h

Test.cpp


二叉搜索树的概念

二叉搜索树又称二叉排序树,它或者是一棵空树

搜索二叉树的性质:

  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值

  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值

  • 它的左右子树也分别为二叉搜索树

二叉搜索树操作

基本框架

#pragma once
#include <iostream>
using namespace std;
​
// 定义二叉搜索树的节点结构
template <class K>
struct BSTreeNode
{
    BSTreeNode<K> *_left;  // 左子节点指针
    BSTreeNode<K> *_right; // 右子节点指针
    K _key;                // 节点存储的键值
​
    // 构造函数
    BSTreeNode(const K &key)
        : _left(nullptr), _right(nullptr), _key(key)
    {}
};
​
// 定义二叉搜索树类
template <class K>
class BSTree
{
    typedef BSTreeNode<K> Node; // 节点类型别名
​
public:
    // 默认构造函数
    BSTree() : _root(nullptr) {}
​
private:
    Node *_root; // 根节点指针
};

default:强制编译器生成默认的构造——C++11的用法

BSTree()=default;

插入

插入的具体过程如下:

  • 树为空,则直接新增节点,赋值给root指针

  • 树不为空,按二叉搜索树性质查找插入的位置,插入新节点(记录父节点,判断插入的节点应该在父节点的左子树还是右子树)

/**
 * 插入节点到搜索二叉树中
 * @key 要插入的节点的键值
 * @return 如果成功插入节点,则返回true;如果节点已经存在相同值,则返回false。
 */
bool Insert(const K &key)
{
    // 如果树为空,直接创建根节点
    if (_root == nullptr)
    {
        _root = new Node(key);
        return true;
    }
​
    Node *parent = nullptr;
    Node *cur = _root;
    while (cur)
    {
        // 根据键值比较确定要插入的位置
        if (cur->_key < key)
        {
            parent = cur;
            cur = cur->_right;
        }
        else if (cur->_key > key)
        {
            parent = cur;
            cur = cur->_left;
        }
        else
            return false; // 节点已经存在相同值,返回false
    }
​
    // 创建新节点
    cur = new Node(key);
​
    // 将新节点链接到父节点的相应位置
    if (parent->_key < key)
        parent->_right = cur;
    else
        parent->_left = cur;
​
    return true;
}

二叉搜索树的删除(重点)

首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情 况: a. 要删除的结点无孩子结点 b. 要删除的结点只有左孩子结点 c. 要删除的结点只有右孩子结点 d. 要删除的结点有左、右孩子结点

看似删除节点有4种情况,但实际上a和b和c可以合并,这样就只有2种情况了: a:待删除的结点无孩子/只有一个孩子:删除结点并使父亲结点指向被删除结点的孩子结点(无孩子视为孩子是空结点,任意指向一个即可) b:待删除的结点有左右孩子:采用替换法,寻找删除结点右子树的最小结点(右子树最左结点),将最小结点的值和删除结点的值替换,然后删除最小结点(此时最小结点,要么没有孩子,要么只有一个孩子,符合a情况可以直接删除)

/**
 * 从搜索二叉树中删除指定键值的节点
 * @key 要删除的节点的键值
 * @return 如果成功删除节点,则返回true;如果未找到指定节点,则返回false。
 */
bool Erase(const K &key)
{
    Node *parent = nullptr;
    Node *cur = _root;
​
    while (cur)
    {
        // 根据键值比较确定要删除的位置
        if (cur->_key < key)
        {
            parent = cur;
            cur = cur->_right;
        }
        else if (cur->_key > key)
        {
            parent = cur;
            cur = cur->_left;
        }
        else
        {
            // 删除节点
            // 1. 左子树为空
            if (cur->_left == nullptr)
            {
                // 若待删除节点为根节点
                if (cur == _root)
                    _root = cur->_right;
                else
                {
                    // 更新父节点的指针,使其指向待删除节点的右子树
                    if (parent->_left == cur)
                        parent->_left = cur->_right;
                    else
                        parent->_right = cur->_right;
                }
            } 
            // 2. 右子树为空
            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;
                }
            }
            else
            {
                // 找到左子树中最右边的节点或右子树中最左边的节点,用于替换当前节点
                Node *pminRight = cur;
                Node *minRight = cur->_right;
                while (minRight->_left)
                {
                    pminRight = minRight;
                    minRight = minRight->_left;
                }
                // 将最小右子树或最大左子树的键值替换到当前节点
                cur->_key = minRight->_key;
                // 删除替换节点
                if (pminRight->_left == minRight)
                    pminRight->_left = minRight->_right;
                else
                    pminRight->_right = minRight->_right;
​
                delete minRight;
            }
            return true;
        }
    }
    // 未找到指定节点
    return false;
}

二叉搜索树的查找

  • 从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找。

  • 最多查找高度次,走到空,还没找到,则这个值不存在。

bool 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 true;
    }
    return false;
}

拷贝构造

BSTree(const BSTree<K>& t)
{
    _root=Copy(t._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;
    }
  

析构函数

~BSTree()
{
    Destory(_root);    
}

void Destory(Node*& root)
{
    if (root==nullptr)
        return;
    Destory(root->_left);        
    Destory(root->_right);        
    delete root;
    root=nullptr;
}

operator =

BSTree<K>& operator=(BSTree<K> t)
{
    swap(_root,t._root);
    return *this;
}

遍历

void InOrder()
{
    _InOrder(_root);
    cout<<endl<<"==="<< endl;
}
​
void _InOrder(Node *root)
{
    if (root == nullptr)
        return;
    // 中序遍历
    _InOrder(root->_left);
    cout << root->_key << " ";
    _InOrder(root->_right);
}

递归构造搜索二叉树

插入

bool InsertR(const K& key)
{
    // 调用递归插入函数,从根节点开始插入
    return _InsertR(_root, key);
}
​
/**
 * 递归插入节点到搜索二叉树中
 * @param root 当前子树的根节点(通过引用传递,可以修改指针)
 * @param key 要插入的键值
 * @return 如果成功插入节点,则返回true;如果节点已存在,则返回false。
 */
bool _InsertR(Node*& root, const K& 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 EraseR(const K& key)
{
    // 调用递归删除函数,从根节点开始删除
    return _EraseR(_root, key);
}
​
/**
 * 递归删除搜索二叉树中指定键值的节点
 * @param root 当前子树的根节点(通过引用传递,可以修改指针)
 * @param key 要删除的键值
 * @return 如果成功删除节点,则返回true;如果节点不存在,则返回false。
 */
bool _EraseR(Node*& root, const K& key)
{
    // 如果当前节点为空,无法删除
    if (root == nullptr)
        return false;
​
    // 根据键值比较确定要删除的节点位置
    if (root->_key < key)
        return _EraseR(root->_right, key); // 在右子树中递归删除
    else if (root->_key > key)
        return _EraseR(root->_left, key); // 在左子树中递归删除
    else
    {
        // 找到了要删除的节点
        Node* del = root;
        if (root->_right == nullptr) // 如果右子树为空,直接删除当前节点并调整指针
            root = root->_left;
        else if (root->_left == nullptr) // 如果左子树为空,直接删除当前节点并调整指针
            root = root->_right;
        else
        {
            // 如果左右子树都不为空,找到左子树中的最大节点来替换当前节点
            Node* maxLeft = root->_left;
            while (maxLeft->_right)
                maxLeft = maxLeft->_right;
            // 将左子树中的最大节点的键值与当前节点的键值交换
            swap(root->_key, maxLeft->_key);
            // 在左子树中递归删除已经被交换的节点
            return _EraseR(root->_left, key);
        }
        // 删除当前节点
        delete del;
        return true;
    }
    return true;
}

查找

//递归
bool FindR(const K& key)
{   
    return _FindR(_root, key);
}
​
bool _FindR(Node* root,const K& key)
{
    if(root==nullptr)
        return false;
    if(root->_key == key)
        return true;
    if(root->_key<key)
        return _FindR(root->_right,key);
    else
        return _FindR(root->_left,key);
}

二叉树的应用

  1. K模型 :K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。 比如:给一个单词word,判断该单词是否拼写正确,具体方式如下: 以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。

  2. KV模型 :每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方 式在现实生活中非常常见: 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对; 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出 现次数就是<word, count>就构成一种键值对。

二叉搜索树的性能分析

插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。

对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。

但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:

 

最优情况下:二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为:log(N)

最差情况下:二叉搜索树退化为单支树(或者类似单支),其平均比较次数为 N

如果退化为单支树,二叉搜索树的性能就失去了。那能否进行改进?无论按照什么次序插入关键码,都能达到最优?这就需要AVL树和红黑树了。

代码示例

BSTree.h

#pragma once
#include <iostream>
using namespace std;

template <class K>
struct BSTreeNode
{
    BSTreeNode<K> *_left;
    BSTreeNode<K> *_right;
    K _key;

    // 构造函数
    BSTreeNode(const K &key)
        : _left(nullptr), _right(nullptr), _key(key)
    {
    }
};

template <class K>
class BSTree
{
    typedef BSTreeNode<K> Node;

public:
    BSTree() : _root(nullptr) {}

    bool Insert(const K &key)
    {
        if (_root == nullptr)
        {
            _root = new Node(key);
            return true;
        }

        Node *parent = nullptr;
        Node *cur = _root;
        while (cur)
        {
            if (cur->_key < key)
            {
                parent = cur;
                cur = cur->_right;
            }
            else if (cur->_key > key)
            {
                parent = cur;
                cur = cur->_left;
            }
            else
                return false; // 已经存在相同值
        }
        cur = new Node(key); // new节点

        // 链接-链接新节点
        if (parent->_key < key)
            parent->_right = cur;
        else
            parent->_left = cur;

        return true;
    }
    bool 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 true;
        }
        return false;
    }

    // 删除
    bool Erase(const K &key)
    {
        Node *parent = nullptr;
        Node *cur = _root;

        while (cur)
        {
            if (cur->_key < key)
            {
                parent = cur;
                cur = cur->_right;
            }
            else if (cur->_key > key)
            {
                parent = cur;
                cur = cur->_left;
            }
            else
            {
                /*
                  若右/左子树为NUll,则可直接用左/右的第一个节点当头节点
                  或用左子树最右(大)节点or左子树最左(小)节点
                  */
                // 删除
                // 1.左为空
                if (cur->_left == nullptr)
                {
                    if (cur == _root)
                        _root = cur->_right;
                    else
                    {
                        if (parent->_left == cur)
                            parent->_left = cur->_right;
                        else
                            parent->_right = cur->_right;
                    }
                } // 2.右为空
                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;
                    }
                }
                else
                {
                    // 找左子树最右(大)节点or左子树最左(小)节点
                    Node *pminRight = cur;
                    Node *minRight = cur->_right;
                    while (minRight->_left)
                    {
                        pminRight = minRight;
                        minRight = minRight->_left;
                    }
                    cur->_key = minRight->_key;
                    if (pminRight->_left == minRight)
                        pminRight->_left = minRight->_right;
                    else
                        pminRight->_right = minRight->_right;

                    delete minRight;
                }

                return true;
            }
        }
        return false;
    }

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

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

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

    void InOrder()
    {
        _InOrder(_root);
        cout << endl
             << "===" << endl;
    }

    // BSTree(){}
    // BSTree()=default;

    BSTree(const BSTree<K> &t)
    {
        _root = Copy(t._root);
    }

    ~BSTree()
    {
        Destory(_root);
    }

    BSTree<K> &operator=(BSTree<K> t)
    {
        swap(_root, t._root);
        return *this;
    }

private:
    // Node*& root [*&] 利用引用进行链接
    bool _InsertR(Node *&root, const K &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 _EraseR(Node *&root, const K &key)
    {
        if (root == nullptr)
            return false;

        if (root->_key < key)
            return _EraseR(root->_right, key);
        else if (root->_key > key)
            return _EraseR(root->_left, key);
        else
        {
            Node *del = root;
            // 删除
            if (root->_right == nullptr)
                root = root->_left;
            else if (root->_left == nullptr)
                root = root->_right;
            else
            {
                Node *maxleft = root->_left;
                while (maxleft->_right)
                    maxleft = maxleft->_right;
                swap(root->_key, maxleft->_key);

                return _EraseR(root->_left, key);
            }
            delete del;

            return true;
        }

        return true;
    }

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

    void _InOrder(Node *root)
    {
        if (root == nullptr)
            return;
        // 中序遍历
        _InOrder(root->_left);
        cout << root->_key << " ";
        _InOrder(root->_right);
    }

    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 Destory(Node *&root)
    {
        if (root == nullptr)
            return;
        Destory(root->_left);
        Destory(root->_right);
        delete root;
        root = nullptr;
    }

private:
    // Node *_root = nullptr;
    Node *_root;
};

Test.cpp

#include "BSTree.h"

void TestBSTree1()
{
    int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 19};

    BSTree<int> t1;
    for (auto e : a)
        t1.Insert(e);
    t1.InOrder();
}

void TestBSTree2()
{
    int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 19};

    BSTree<int> t1;
    for (auto e : a)
        t1.Insert(e);
    t1.InOrder();

    t1.Erase(10);
    t1.Erase(14);
    t1.Erase(13);
    t1.InOrder();

    for (auto e : a)
    {
        t1.Erase(e);
        t1.InOrder();
    }
    //t1.InOrder();
}

void TestBSTree3()
{
    int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 19};

    BSTree<int> t1;
    for (auto e : a)
        t1.InsertR(e);
    t1.InOrder();

    t1.EraseR(10);
    t1.EraseR(14);
    t1.Erase(13);
    t1.InOrder();

    for (auto e : a)
    {
        t1.Erase(e);
        t1.InOrder();
    }
    //t1.InOrder();
}
void TestBSTree4()
{
    int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 19};

    BSTree<int> t1;
    for (auto e : a)
        t1.InsertR(e);
    t1.InOrder();
    
    cout<<"===COPY==="<<endl;
    BSTree<int> copy(t1);
    BSTree<int> copy2=t1;

    // t1.EraseR(10);
    // t1.EraseR(14);
    // t1.Erase(13);
    copy.InOrder();
    copy2.InOrder();

    // for (auto e : a)
    // {
    //     t1.Erase(e);
    //     t1.InOrder();
    // }
    //t1.InOrder();
}

int main()
{
    TestBSTree4();

    return 0;
}

;