【数据结构】C++实现二叉搜索树

news2024/11/24 8:30:42

二叉搜索树的概念

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

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

在这里插入图片描述
由于二叉搜索树中,每个结点左子树上所有结点的值都小于该结点的值,右子树上所有结点的值都大于该结点的值,因此对二叉搜索树进行中序遍历后,得到的是升序序列也就不难理解了。

二叉搜索树的操作

在这里插入图片描述

int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};

二叉搜索树的查找

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

b、最多查找高度次,走到空,还没找到,这个值不存在

二叉搜索树的插入

插入的具体过程如下:

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

b. 树不空,按二叉搜索树性质查找插入位置,插入新节点

在这里插入图片描述

二叉搜索树的删除

首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情况:

a. 要删除的结点无孩子结点

b. 要删除的结点只有左孩子结点

c. 要删除的结点只有右孩子结点

d. 要删除的结点有左、右孩子结点

看起来有待删除节点有4中情况,实际情况a可以与情况b或者c合并起来,因此真正的删除过程如下:

情况b:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点–直接删除

情况c:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点–直接删除

情况d:在被删除的结点的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点中,再来处理该结点的删除问题–替换法删除

在这里插入图片描述

二叉搜索树的实现

结点类

要实现二叉搜索树,我们首先需要实现一个结点类:

  • 结点类当中包含三个成员变量:结点值、左指针、右指针。
  • 结点类当中只需实现一个构造函数即可,用于构造指定结点值的结点。
template<class K>
// 二叉树的结构
struct BSTreeNode {
    BSTreeNode<K> *_left; //左孩子指针
    BSTreeNode<K> *_right;//右孩子指针
    K _key;               //结点值
    
    //构造函数
    BSTreeNode(const K &key = 0)
        : _left(nullptr), _right(nullptr), _key(key) {
    }
};

BSTree类

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

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

public:
    BSTree() = default;//指定强制生成默认构造
	//默认构造
    BSTree();
    //拷贝构造
    BSTree(const BSTree<K> &t);
	//赋值重载
    BSTree<K> &operator=(BSTree<K> t);
	//析构函数
    ~BSTree();
	//插入key值
    bool Insert(const K &key);
    //查找key值
    bool Find(const K &key);
    //删除key值
    bool Erase(const K &key);
	//递归查找 
    bool FindR(const K &key);
	//递归插入
    bool InsertR(const K &key);
	//递归删除
    bool EraseR(const K &key);
   	//中序遍历
    void InOrder();
private:
    Node *_root = nullptr;// 二叉搜索树的根
};

构造函数

//构造一个空树
BSTree()
    : _root(nullptr) {
}

拷贝构造函数

拷贝构造函数也并不难,拷贝一棵和所给二叉搜索树相同的树即可。

Node *Copy(Node *root) {
    if (root == nullptr) {
        return nullptr;
    }
    //前序遍历copy树
    Node *newRoot = new Node(root->_key);
    newRoot->_left = Copy(root->_left);
    newRoot->_right = Copy(root->_right);

    return newRoot;
}

//构造树的深拷贝
BSTree(const BSTree<K> &t) {
    _root = Copy(t._root);
}

赋值运算符重载函数

对于赋值运算符重载函数,下面提供两种实现方法:

传统写法

//传统写法
const BSTree<K> &operator=(const BSTree<K> &t) {
    //避免自己拷贝自己
    if (this != &t) {
        _root = Copy(t._root);
    }
    return *this;//为了支持连续赋值
}

现代写法

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

赋值运算符重载函数的现代写法非常精辟,函数在接收右值时并没有使用引用进行接收,因为这样可以间接调用BSTree的拷贝构造函数完成拷贝构造。我们只需将这个拷贝构造出来的对象的二叉搜索树与this对象的二叉搜索树进行交换,就相当于完成了赋值操作,而拷贝构造出来的对象t会在该赋值运算符重载函数调用结束时自动析构。

注意两种方法都是深拷贝,效率并没有什么区别

析构函数

析构函数完成对象中二叉搜索树结点的释放,注意释放时采用后序释放,当二叉搜索树中的结点被释放完后,将对象当中指向二叉搜索树的指针及时置空即可。

void Destroy(Node *&root) {
    if (root == nullptr) {
        return;
    }
    //后序遍历析构
    Destroy(root->_left);
    Destroy(root->_right);

    delete root;
    root = nullptr;
}

//析构函数
~BSTree() {
    Destroy(_root);
}

查找函数

根据二叉搜索树的特性,我们在二叉搜索树当中查找指定值的结点的方式如下:

  1. 若树为空树,则查找失败,返回nullptr。
  2. 若key值小于当前结点的值,则应该在该结点的左子树当中进行查找。
  3. 若key值大于当前结点的值,则应该在该结点的右子树当中进行查找。
  4. 若key值等于当前结点的值,则查找成功,返回对应结点的地址。

非递归实现

// 查找
bool Find(const K &key) {
    Node *cur = _root;
    while (cur) {
        if (key > cur->_key) {
            cur = cur->_right;
        } else if (key < cur->_key) {
            cur = cur->_left;
        } else {
            return true;
        }
    }
    return false;
}

递归实现

bool _FindR(Node *root, const K &key) {
    if (root == nullptr) {
        return false;
    }

    if (root->_key == key) {
        return true;
    }

    if (key > root->_key) {
        return _FindR(root->_right, key);
    } else {
        return _FindR(root->_left, key);
    }
}

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

插入函数

根据二叉搜索树的性质,其插入操作非常简单:

如果是空树,则直接将插入结点作为二叉搜索树的根结点。

如果不是空树,则按照二叉搜索树的性质进行结点的插入。

若不是空树,插入结点的具体操作如下:

  • 若待插入结点的值小于根结点的值,则需要将结点插入到左子树当中。
  • 若待插入结点的值大于根结点的值,则需要将结点插入到右子树当中。
  • 若待插入结点的值等于根结点的值,则插入结点失败。

如此进行下去,直到找到与待插入结点的值相同的结点判定为插入失败,或者最终插入到某叶子结点的左右子树当中(即空树当中)。

非递归实现

使用非递归方式实现二叉搜索树的插入函数时,我们需要定义一个parent指针,该指针用于标记待插入结点的父结点。这样一来,当我们找到待插入结点的插入位置时,才能很好的将待插入结点与其父结点连接起来。

在这里插入图片描述

但是需要注意在连接parent和cur时,需要判断应该将cur连接到parent的左边还是右边。

bool Insert(const K &key) {
    // 如果根一开始就为nullptr,那么就直接构建初始的根
    if (_root == nullptr) {
        _root = new Node(key);
        return true;
    }

    // 如果_root不为nullptr,那么就从根开始遍历,找适合的位置
    Node *parent = nullptr;// parent跟着cur遍历找到合适的位置,充当插入的父亲节点
    Node *cur = _root;
    while (cur) {
        //key < cur->_key 需要走左子树
        //key > cur->_key 需要走右子树
        if (key < cur->_key) {
            parent = cur;    //记录父亲结点
            cur = cur->_left;//走左树
        } else if (key > cur->_key) {
            parent = cur;
            cur = cur->_right;//走右数
        } else {
            // 如果key == cur->_key  那么就直接返回false,二叉搜索树的值不允许相同
            return false;
        }
    }
    // 找到后就开始链接
    cur = new Node(key);
    // 这里不知道cur最终走到了parent的左边还是右边,所以还要进行判断
    // key>parent->_key 链接右树
    // key<parent->_key 链接左树
    if (key > parent->_key) {
        parent->_right = cur;//右树
    } else if (key < parent->_key) {
        parent->_left = cur;//左树
    }
    return true;
}

递归实现

递归实现二叉搜索树的插入操作也是很简单的,但是要特别注意的一点就是,递归插入函数的子函数接收参数root时,必须采用引用接收,因为只有这样我们才能将二叉树当中的各个结点连接起来。

bool _InsertR(Node *&root, const K &key) {
    if (root == nullptr) {
        root = new Node(key);
        return root;
    }

    if (key > root->_key) {
        return _InsertR(root->_right, key);
    } else if (key < root->_key) {
        return _InsertR(root->_left, key);
    } else {
        return false;
    }
}

删除函数

二叉搜索树的删除函数是最难实现的,若是在二叉树当中没有找到待删除结点,则直接返回false表示删除失败即可,但若是找到了待删除结点,此时就有以下三种情况:

  1. 待删除结点的左子树为空(待删除结点的左右子树均为空包含在内)。
  2. 待删除结点的右子树为空。
  3. 待删除结点的左右子树均不为空。

下面我们分别对这三种情况进行分析处理:

1、待删除结点的左子树为空

若待删除结点的左子树为空,那么当我们在二叉搜索树当中找到该结点后,只需先让其父结点指向该结点的右孩子结点,然后再将该结点释放便完成了该结点的删除,进行删除操作后仍保持二叉搜索树的特性。

在这里插入图片描述

在这里插入图片描述

2、待删除结点的右子树为空

若待删除结点的右子树为空,那么当我们在二叉搜索树当中找到该结点后,只需先让其父结点指向该结点的左孩子结点,然后再将该结点释放便完成了该结点的删除,进行删除操作后仍保持二叉搜索树的特性。

在这里插入图片描述

3、待删除结点的左右子树均不为空

若待删除结点的左右子树均不为空,那么当我们在二叉搜索树当中找到该结点后,可以使用替换法进行删除。

可以将让待删除结点左子树当中值最大的结点,或是待删除结点右子树当中值最小的结点代替待删除结点被删除(下面都以后者为例),然后将待删除结点的值改为代替其被删除的结点的值即可。而代替待删除结点被删除的结点,必然左右子树当中至少有一个为空树,因此删除该结点的方法与前面说到的情况一和情况二的方法相同。

注意只能是待删除结点左子树当中值最大的结点,或是待删除结点右子树当中值最小的结点代替待删除结点被删除,因为只有这样才能使得进行删除操作后的二叉树仍保持二叉搜索树的特性。

在这里插入图片描述

非递归函数

// 删除
bool Erase(const K &key) {
    Node *parent = nullptr;
    Node *cur = _root;
    while (cur) {
        if (key > cur->_key) {
            parent = cur;
            cur = cur->_right;
        } else if (key < cur->_key) {
            parent = cur;
            cur = cur->_left;
        } else {
            // 开始删除
            // 1.如果要删除的cur左边是nullptr,那么我们就进行判断,判断cur在parent的左子树还是右子树,
            // 如果是左子树,那么就由parent的left指向cur的右子树,如果是右子树,就由parent的right指向cur的右子树
            if (cur->_left == nullptr) {
                // if (parent == nullptr)
                if (cur == _root) {
                    _root = cur->_right;
                } else {
                    if (parent->_left == cur) {
                        parent->_left = cur->_right;
                    } else {
                        parent->_right = cur->_right;
                    }
                }

                delete cur;
            } else if (cur->_right == nullptr) {// 2.cur的右边为nullptr
                // if (parent == nullptr)
                if (cur == _root) {
                    _root = cur->_left;
                } else {
                    if (parent->_left == cur) {
                        parent->_left = cur->_left;
                    } else {
                        parent->_right = cur->_left;
                    }
                }

                delete cur;
            } else {
                // 都不为nullptr,替代法,用被删除的cur的左子树的最大节点,右子树的最大节点替换
                // 找cur右子树的最大节点
                Node *pminRight = cur;
                Node *minRight = cur->_right;
                // 找右子树,右子树的最小位置在右子树的左边
                while (minRight->_left) {
                    pminRight = minRight;
                    minRight = minRight->_left;
                }
                // 找到最小的值赋值给cur
                cur->_key = minRight->_key;

                // pminRight->_left==minRight 那么左边已经是最小了,所以minRight的左子树肯定为空了
                // 那么可能minRight还有右子树,所以需要pinRight来领养
                if (pminRight->_left == minRight) {
                    pminRight->_left = minRight->_right;
                } else {
                    // 如果不是,比如删除根节点,那么就需要将pminRight->_right指向minRight->right(最小值左边一定为NULL。不需要领养)
                    //minRight是其父结点的右孩子
                    pminRight->_right = minRight->_right;
                }

                delete minRight;
            }
            return true;
        }
    }
    return false;
}

递归函数

递归实现二叉搜索树的删除函数的思路如下:

  1. 若树为空树,则结点删除失败,返回false。
  2. 若所给key值小于树根结点的值,则问题变为删除左子树当中值为key的结点。
  3. 若所给key值大于树根结点的值,则问题变为删除右子树当中值为key的结点。
  4. 若所给key值等于树根结点的值,则根据根结点左右子树的存在情况不同,进行不同的处理。
bool _EraseR(Node *&root, const K &key) {
    if (root == nullptr)
        return false;

    if (key > root->_key) {
        return _EraseR(root->_right, key);
    } else if (key < root->_key) {
        return _EraseR(root->_left, key);
    } else {
        Node *del = root;
        //开始准备删除,root谁上层root->_left/_right的引用
        if (root->_right == nullptr) {
            //root是上层的左右子树
            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);

            //转换在子树去删除
            //这里不能传maxleft,maxleft是局部变量
            return _EraseR(root->_left, key);
        }
        delete del;
        return true;
    }
}

完整代码

//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 = 0)
        : _left(nullptr), _right(nullptr), _key(key) {
    }
};

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

public:
    BSTree() = default;//指定强制生成默认构造
    //默认构造,构造一个空树

    //构造树的深拷贝
    BSTree(const BSTree<K> &t) {
        _root = Copy(t._root);
    }

    //传统写法
    const BSTree<K> &operator=(const BSTree<K> &t) {
        //避免自己拷贝自己
        if (this != &t) {
            _root = Copy(t._root);
        }
        return *this;//为了支持连续赋值
    }

    //现代写法,接收一个t的拷贝,临时对象,然后进行交换
    BSTree<K> &operator=(BSTree<K> t) {
        swap(_root, t._root);
        return *this;
    }

    //析构函数
    ~BSTree() {
        Destroy(_root);
    }

    bool Insert(const K &key) {
        // 如果根一开始就为nullptr,那么就直接构建初始的根
        if (_root == nullptr) {
            _root = new Node(key);
            return true;
        }

        // 如果_root不为nullptr,那么就从根开始遍历,找适合的位置
        Node *parent = nullptr;// parent跟着cur遍历找到合适的位置,充当插入的父亲节点
        Node *cur = _root;
        while (cur) {
            //key < cur->_key 需要走左子树
            //key > cur->_key 需要走右子树
            if (key < cur->_key) {
                parent = cur;    //记录父亲结点
                cur = cur->_left;//走左树
            } else if (key > cur->_key) {
                parent = cur;
                cur = cur->_right;//走右数
            } else {
                // 如果key == cur->_key  那么就直接返回false,二叉搜索树的值不允许相同
                return false;
            }
        }
        // 找到后就开始链接
        cur = new Node(key);
        // 这里不知道cur最终走到了parent的左边还是右边,所以还要进行判断
        // key>parent->_key 链接右树
        // key<parent->_key 链接左树
        if (key > parent->_key) {
            parent->_right = cur;//右树
        } else if (key < parent->_key) {
            parent->_left = cur;//左树
        }
        return true;
    }

    // 查找
    bool Find(const K &key) {
        Node *cur = _root;
        while (cur) {
            if (key > cur->_key) {
                cur = cur->_right;
            } else if (key < cur->_key) {
                cur = cur->_left;
            } else {
                return true;
            }
        }
        return false;
    }

    // 删除
    bool Erase(const K &key) {
        Node *parent = nullptr;
        Node *cur = _root;
        while (cur) {
            if (key > cur->_key) {
                parent = cur;
                cur = cur->_right;
            } else if (key < cur->_key) {
                parent = cur;
                cur = cur->_left;
            } else {
                // 开始删除
                // 1.如果要删除的cur左边是nullptr,那么我们就进行判断,判断cur在parent的左子树还是右子树,
                // 如果是左子树,那么就由parent的left指向cur的右子树,如果是右子树,就由parent的right指向cur的右子树
                if (cur->_left == nullptr) {
                    // if (parent == nullptr)
                    if (cur == _root) {
                        _root = cur->_right;
                    } else {
                        if (parent->_left == cur) {
                            parent->_left = cur->_right;
                        } else {
                            parent->_right = cur->_right;
                        }
                    }

                    delete cur;
                } else if (cur->_right == nullptr) {// 2.cur的右边为nullptr
                    // if (parent == nullptr)
                    if (cur == _root) {
                        _root = cur->_left;
                    } else {
                        if (parent->_left == cur) {
                            parent->_left = cur->_left;
                        } else {
                            parent->_right = cur->_left;
                        }
                    }

                    delete cur;
                } else {
                    // 都不为nullptr,替代法,用被删除的cur的左子树的最大节点,右子树的最大节点替换
                    // 找cur右子树的最大节点
                    Node *pminRight = cur;
                    Node *minRight = cur->_right;
                    // 找右子树,右子树的最小位置在右子树的左边
                    while (minRight->_left) {
                        pminRight = minRight;
                        minRight = minRight->_left;
                    }
                    // 找到最小的值赋值给cur
                    cur->_key = minRight->_key;

                    // pminRight->_left==minRight 那么左边已经是最小了,所以minRight的左子树肯定为空了
                    // 那么可能minRight还有右子树,所以需要pinRight来领养
                    if (pminRight->_left == minRight) {
                        pminRight->_left = minRight->_right;
                    } else {
                        // 如果不是,比如删除根节点,那么就需要将pminRight->_right指向minRight->right(最小值左边一定为NULL。不需要领养)
                        //minRight是其父结点的右孩子
                        pminRight->_right = minRight->_right;
                    }

                    delete minRight;
                }
                return true;
            }
        }
        return false;
    }

    bool _FindR(Node *root, const K &key) {
        if (root == nullptr) {
            return false;
        }

        if (root->_key == key) {
            return true;
        }

        if (key > root->_key) {
            return _FindR(root->_right, key);
        } else {
            return _FindR(root->_left, key);
        }
    }

    Node *Copy(Node *root) {
        if (root == nullptr) {
            return nullptr;
        }
        //前序遍历copy树
        Node *newRoot = new Node(root->_key);
        newRoot->_left = Copy(root->_left);
        newRoot->_right = Copy(root->_right);

        return newRoot;
    }

    void Destroy(Node *&root) {
        if (root == nullptr) {
            return;
        }
        //后序遍历析构
        Destroy(root->_left);
        Destroy(root->_right);

        delete root;
        root = nullptr;
    }

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

    bool _InsertR(Node *&root, const K &key) {
        if (root == nullptr) {
            root = new Node(key);
            return root;
        }

        if (key > root->_key) {
            return _InsertR(root->_right, key);
        } else if (key < root->_key) {
            return _InsertR(root->_left, key);
        } else {
            return false;
        }
    }

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

    bool _EraseR(Node *&root, const K &key) {
        if (root == nullptr)
            return false;

        if (key > root->_key) {
            return _EraseR(root->_right, key);
        } else if (key < root->_key) {
            return _EraseR(root->_left, key);
        } else {
            Node *del = root;
            //开始准备删除,root谁上层root->_left/_right的引用
            if (root->_right == nullptr) {
                //root是上层的左右子树
                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);

                //转换在子树去删除
                //这里不能传maxleft,maxleft是局部变量
                return _EraseR(root->_left, key);
            }
            delete del;
            return true;
        }
    }

    bool EraseR(const K &key) {
        return _EraseR(_root, key);
    }
    // 一般调用为t.InOrder()  不传参数,所以这里进行了封装
    void InOrder() {
        _InOrder(_root);
        cout << endl;
    }

    void _InOrder(Node *root) {
        if (root == nullptr)
            return;

        _InOrder(root->_left);
        cout << root->_key << " ";
        _InOrder(root->_right);
    }

private:
    Node *_root = nullptr;// 二叉搜索树的根
};

测试代码

#include "BSTree.h"

void test_BStree1() {
    int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};
    BSTree<int> t;
    for (auto e: a) {
        t.Insert(e);
    }

    t.Erase(7);
    t.Erase(14);
    t.Erase(3);
    t.Erase(8);

    t.InOrder();
}

void test_BSTree2() {
    int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};
    BSTree<int> t;
    for (auto e: a) {
        t.InsertR(e);
    }

    t.EraseR(7);
    t.EraseR(14);
    t.EraseR(3);
    t.EraseR(8);
    t.EraseR(6);

    t.InOrder();
}

void test_BSTree3() {
    int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};
    BSTree<int> t1;
    for (auto e: a) {
        t1.InsertR(e);
    }
    t1.InOrder();

    BSTree<int> t2(t1);
    t2.InOrder();
}

int main() {
    test_BSTree3();
    return 0;
}

二叉搜索树的应用

  1. K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值

    比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:

    • 以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树

    • 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。

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

    • 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文**<word,chinese>**就构成一种键值对;

    • 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数**,单词与其出现次数就是就构成一种键值对。**

KV结构的二叉搜索树

#include <iostream>
#include <string>
using namespace std;
// 改造二叉搜索树为KV结构s
template<class K, class V>
struct BSTNode {
    BSTNode(const K &key = K(), const V &value = V())
        : _pLeft(nullptr), _pRight(nullptr), _key(key), _Value(value) {}
    BSTNode<K, V> *_pLeft;
    BSTNode<K, V> *_pRight;
    K _key;
    V _value
};

template<class K, class V>
class BSTree {
    typedef BSTNode<K, V> Node;
    typedef Node *PNode;

public:
    BSTree() : _pRoot(nullptr) {}
    PNode Find(const K &key);
    bool Insert(const K &key, const V &value);
    bool Erase(const K &key);

private:
    PNode _pRoot;
};

void TestBSTree3() {
    // 输入单词,查找单词对应的中文翻译
    BSTree<string, string> dict;
    dict.Insert("string", "字符串");
    dict.Insert("tree", "树");
    dict.Insert("left", "左边、剩余");
    dict.Insert("right", "右边");
    dict.Insert("sort", "排序");
    // 插入词库中所有单词
    string str;
    while (cin >> str) {
        BSTNode<string, string> *ret = dict.Find(str);
        if (ret == nullptr) {
            cout << "单词拼写错误,词库中没有这个单词:" << str << endl;
        } else {
            cout << str << "中文翻译:" << ret->_value << endl;
        }
    }
}
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();
}

二叉搜索树的性能分析

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

对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。 但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:

在这里插入图片描述

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

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

问题:如果退化成单支树,二叉搜索树的性能就失去了。那能否进行改进,不论按照什么次序插入关键码,二叉搜索树的性能都能达到最优?答案就是使用AVL树和红黑树

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1015513.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

问道管理:注册制对涨幅的限制?

注册制作为中国证券商场变革的重中之重&#xff0c;其本质是将发行商场转移到审阅商场&#xff0c;实现商场准入的有效管控。与此同时&#xff0c;注册制关于股票商场的涨幅也有必定的约束。 首先&#xff0c;注册制能够经过严厉的审阅来制约公司发行股票的数量和节奏。传统IP…

Linux 多线程( 进程VS线程 | 线程控制 )

文章目录 Linux进程 VS 线程进程的多个线程共享 进程和线程的关系线程创建 pthread_create获取线程ID pthread_self线程等待 pthread_join终止线程进程分离线程ID及进程地址空间布局 Linux进程 VS 线程 进程是资源分配的基本单位。线程是OS调度的基本单位。 线程共享进程数据…

Michael.W基于Foundry精读Openzeppelin第34期——MerkleProof.sol

Michael.W基于Foundry精读Openzeppelin第34期——MerkleProof.sol 0. 版本0.1 MerkleProof.sol 1. 目标合约2. 代码精读2.1 processProof(bytes32[] memory proof, bytes32 leaf) && processProofCalldata(bytes32[] calldata proof, bytes32 leaf)2.2 verify(bytes32[…

图像处理之频域滤波DFT

摘要&#xff1a;傅里叶变换可以将任何满足相应数学条件的信号转换为不同系数的简单正弦和余弦函数的和。图像信号也是一种信号&#xff0c;只不过是二维离散信号&#xff0c;通过傅里叶变换对图像进行变换可以图像存空域转换为频域进行更多的处理。本文主要简要描述傅里叶变换…

Heap及其应用

目录 堆的相关知识 什么是堆&#xff1f; 堆的性质&#xff1a; 堆的实现&#xff1a; 堆的结构&#xff1a; &#xff08;一&#xff09;堆的插入 向上调整法&#xff1a; 寻找父节点 循环结束条件 代码&#xff1a; &#xff08;二&#xff09;堆的删除 删除根节点…

Huggingface:免费开源AI人工智能API工具平台

| 【产品介绍】 • 名称 Huggingface • 成立/上线时间 2016年 • 具体描述 HuggingFace是一个开源的自然语言处理AI工具平台&#xff0c;它为NLP的开发者和研究者提供了一个简单、快速、高效、可靠的解决方案&#xff0c;让NLP变得更加简…

R绘制箱线图

代码大部分来自boxplot()函数的帮助文件&#xff0c;可以通过阅读帮助文件&#xff0c;调整代码中相应参数看下效果&#xff0c;进而可以理解相应的作用&#xff0c;帮助快速掌握barplot()函数的用法。 语法 Usage(来自帮助文件) barplot(height, ...)## Default S3 method: …

lua环境搭建数据类型

lua作为一门计算机语言&#xff0c;从语法角度个人感觉还是挺简洁的接下来我们从0开始学习lua语言。 1.首先我们需要下载lua开发工具包 在这里我们使用的工具是luadist 下载链接为&#xff1a;https://luadist.org/repository/下载后的压缩包解压后就能用。 2.接下来就是老生…

听GPT 讲Istio源代码--istioctl

在 Istio 项目的 istioctl 目录中&#xff0c;有一些子目录&#xff0c;每个目录都有不同的作用和功能。以下是这些子目录的详细介绍&#xff1a; /pkg: pkg 目录包含了 istioctl 工具的核心代码和库。这些代码和库提供了与 Istio 控制平面交互的功能&#xff0c;例如获取和修改…

postgresql 内核源码分析 btree索引插入分析,索引页面分裂流程,多举措进行并发优化,对异常进行保护处理

Btree索引插入流程分析 ​专栏内容&#xff1a; postgresql内核源码分析手写数据库toadb并发编程 ​开源贡献&#xff1a; toadb开源库 个人主页&#xff1a;我的主页 管理社区&#xff1a;开源数据库 座右铭&#xff1a;天行健&#xff0c;君子以自强不息&#xff1b;地势坤&a…

Swing程序设计详解(一)

【今日】 “若你决定灿烂&#xff0c;山无遮&#xff0c;海无拦” 目录 初识Swing 一 Swing简述 二 Swing常用窗体 2.1 JFrame窗体 2.2 JDialog对话框 2.3JOptionPane小型对话框 (1)通知框 (2)确认框 (3)输入框 (4)自定义对话框 三 常用布局管理器 3.1 绝…

JWT生成与解析/JWT令牌前端存储

第一步&#xff1a;创建项目 添加Maven依赖&#xff1a; <dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.62</version> </dependency> <dependency><groupId>org.s…

【C++】深拷贝和浅拷贝 ② ( 默认拷贝构造函数是浅拷贝 | 代码示例 - 浅拷贝造成的问题 )

文章目录 一、默认拷贝构造函数是浅拷贝1、默认拷贝构造函数2、默认拷贝构造函数是浅拷贝机制 二、代码示例 - 浅拷贝造成的问题 一、默认拷贝构造函数是浅拷贝 1、默认拷贝构造函数 如果 C 类中 没有定义拷贝构造函数 , C 编译器会自动为该类提供一个 " 默认的拷贝构造函…

GeoJSON转STL:地形3D打印

我们通过将 GeoJSON 形状坐标提取到点云中并使用 Open3d 应用泊松重建&#xff0c;从 GeoJSON 数据重建 STL 网格。 推荐&#xff1a;用 NSDT编辑器 快速搭建可编程3D场景 我对打印 GeoJSON 山丘的第一次尝试深感不满&#xff0c;因此想出了一个三步流程&#xff0c;仅使用开源…

Acwing 828. 模拟栈

Acwing 828. 模拟栈 题目要求思路讲解代码展示 题目要求 思路讲解 栈&#xff1a;先进后出 队列&#xff1a;先进先出 代码展示 #include <iostream>using namespace std;const int N 100010;int m; int stk[N], tt;int main() {cin >> m;while (m -- ){string o…

【JVM】经典垃圾收集器

文章目录 说明新生代收集器Serial收集器ParNew收集器Parallel Scavenge收集器 老年代收集器Serial Old收集器Parallel Old收集器CMS收集器 Garbage First收集器需要解决的问题运作过程CMS和G1的区别 说明 Java中有许多垃圾收集器&#xff08;Garbage Collector&#xff0c;GC&…

Spring Cloud Alibaba系列之nacos:(5)源码本地环境搭建

传送门 Spring Cloud Alibaba系列之nacos&#xff1a;(1)安装 Spring Cloud Alibaba系列之nacos&#xff1a;(2)单机模式支持mysql Spring Cloud Alibaba系列之nacos&#xff1a;(3)服务注册发现 Spring Cloud Alibaba系列之nacos&#xff1a;(4)配置管理 为什么要搭建本地…

范文展示,如何三步写出一篇满意的论文

第一步&#xff1a;输入文章关键信息 文章标题&#xff0c;写论文的话即为拟定的论文标题&#xff0c;例如这篇范文中的题目为“阳明心学研究” 关键词&#xff0c;可以写出多个论文主题相关的关键词&#xff0c;用逗号分开&#xff0c;例如这篇范文中只写了一个关键词“王阳…

CentOS 7.6使用mysql-8.0.31-1.el7.x86_64.rpm-bundle.tar安装Mysql 8.0

https://downloads.mysql.com/archives/community/是社区版的官网&#xff0c;可以选择版本下载。 cat /etc/redhat-release可以看到系统版本是CentOS Linux release 7.6.1810 (Core)&#xff0c;uname -r可以看到版本是3.10.0-957.el7.x86_64。 yum remove -y mysql-libs把…

计算机硬件基本组成和各硬件工作原理

计算机硬件基本组成和各硬件工作原理 计算机硬件基本组成早期冯若依曼机的结构冯若依曼机的特点 现代计算机的结构思维导图 各硬件工作原理主存储器运算器控制器I/O 计算机硬件基本组成 计算机硬件基本组成可分两大类 1.早期冯若依曼机的结构 2.现代计算机的结构 早期冯若依曼机…