二叉树进阶

目录

二叉搜索树

二叉搜索树概念

​编辑

二叉搜索树操作

二叉搜索树的查找

二叉搜索树的插入

二叉搜索树的删除

二叉搜索树的实现

声明

[1 构造函数](#1 构造函数)

[2 析构函数](#2 析构函数)

[3 查找节点](#3 查找节点)

[4 插入节点](#4 插入节点)

[5 删除节点](#5 删除节点)

[6 中序遍历](#6 中序遍历)

完整代码

二叉搜索树的应用

性能分析

二叉树进阶面试题


二叉搜索树

二叉搜索树概念

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

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

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

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

二叉搜索树操作

cpp 复制代码
int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};

二叉搜索树的查找

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

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

二叉搜索树的插入

插入的具体过程如下:

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

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

二叉搜索树的删除

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

况:

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

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

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

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

看似删除节点有4种情况,但实际上a和b和c可以合并,这样就只有2种情况了:

a:待删除的结点无孩子/只有一个孩子:删除结点并使父亲结点指向被删除结点的孩子结点(无孩子视为孩子是空结点,任意指向一个即可)

b:待删除的结点有左右孩子:采用替换法,寻找删除结点右子树的最小结点(右子树最左结点),将最小结点的值和删除结点的值替换,然后删除最小结点(此时最小结点,要么没有孩子,要么只有一个孩子,符合a情况可以直接删除)

二叉搜索树的实现

声明

cpp 复制代码
template<class T>
struct BSTNode
{
    BSTNode(const T& data = T())
        : _pLeft(nullptr), _pRight(nullptr), _data(data)
    {}
    BSTNode<T>* _pLeft;
    BSTNode<T>* _pRight;
    T _data;
};

template<class T>
class BSTree
{
    typedef BSTNode<T> Node;
    typedef Node* PNode;
public:
    BSTree();                               // 构造函数
    ~BSTree();                              // 析构函数
    PNode Find(const T& data);              // 查找值为data的节点
    bool Insert(const T& data);             // 插入节点
    bool Erase(const T& data);              // 删除节点
    void InOrder();                         // 中序遍历(打印)
private:
    PNode _pRoot;
    void _Destroy(PNode& root);             // 递归销毁
    void _InOrder(PNode root);              // 递归中序遍历
};

1 构造函数

初始化根节点为空

cpp 复制代码
template<class T>
BSTree<T>::BSTree()
    : _pRoot(nullptr)
{}

2 析构函数

递归释放所有节点,防止内存泄漏

cpp 复制代码
template<class T>
void BSTree<T>::_Destroy(PNode& root)
{
    if (root == nullptr)
        return;
    _Destroy(root->_pLeft);
    _Destroy(root->_pRight);
    delete root;
    root = nullptr;
}

template<class T>
BSTree<T>::~BSTree()
{
    _Destroy(_pRoot);
}

3 查找节点

根据二叉搜索树性质,从根开始比较,小于向左,大于向右,相等则返回节点指针

cpp 复制代码
template<class T>
typename BSTree<T>::PNode BSTree<T>::Find(const T& data)
{
    PNode cur = _pRoot;
    while (cur)
    {
        if (data < cur->_data)
            cur = cur->_pLeft;
        else if (data > cur->_data)
            cur = cur->_pRight;
        else
            return cur;   // 找到
    }
    return nullptr;       // 未找到
}

4 插入节点

空树则直接插入根节点;否则找到合适位置(叶子节点下方),插入新节点

cpp 复制代码
template<class T>
bool BSTree<T>::Insert(const T& data)
{
    if (_pRoot == nullptr)
    {
        _pRoot = new Node(data);
        return true;
    }
    PNode cur = _pRoot;
    PNode parent = nullptr;
    // 查找插入位置
    while (cur)
    {
        parent = cur;
        if (data < cur->_data)
            cur = cur->_pLeft;
        else if (data > cur->_data)
            cur = cur->_pRight;
        else
            return false;   // 已存在,插入失败
    }
    // 创建新节点并连接到父节点
    PNode newNode = new Node(data);
    if (data < parent->_data)
        parent->_pLeft = newNode;
    else
        parent->_pRight = newNode;
    return true;
}

5 删除节点

先查找待删除节点,若不存在则返回false。删除分三种情况:

  • 只有左孩子或无孩子:让父节点指向左孩子,然后删除该节点

  • 只有右孩子:让父节点指向右孩子,然后删除该节点

  • 左右孩子都存在:找到右子树的最小节点(或左子树的最大节点),用其值覆盖待删除节点,再递归删除该替代节点(转变为前两种情况)

cpp 复制代码
template<class T>
bool BSTree<T>::Erase(const T& data)
{
    if (_pRoot == nullptr)
        return false;

    // 1. 查找待删除节点及其父节点
    PNode cur = _pRoot;
    PNode parent = nullptr;
    while (cur)
    {
        if (data == cur->_data)
            break;
        parent = cur;
        if (data < cur->_data)
            cur = cur->_pLeft;
        else
            cur = cur->_pRight;
    }
    if (cur == nullptr)      // 未找到
        return false;

    // 2. 情况1:只有右孩子或为叶子(处理叶子时右孩子为空,左孩子也为空)
    if (cur->_pLeft == nullptr)
    {
        // 若删除的是根节点,特殊处理
        if (parent == nullptr)
            _pRoot = cur->_pRight;
        else if (cur == parent->_pLeft)
            parent->_pLeft = cur->_pRight;
        else
            parent->_pRight = cur->_pRight;
        delete cur;
    }
    // 情况2:只有左孩子
    else if (cur->_pRight == nullptr)
    {
        if (parent == nullptr)
            _pRoot = cur->_pLeft;
        else if (cur == parent->_pLeft)
            parent->_pLeft = cur->_pLeft;
        else
            parent->_pRight = cur->_pLeft;
        delete cur;
    }
    // 情况3:左右孩子都存在
    else
    {
        // 找右子树中最小的节点(最左)
        PNode minParent = cur;
        PNode minNode = cur->_pRight;
        while (minNode->_pLeft)
        {
            minParent = minNode;
            minNode = minNode->_pLeft;
        }
        // 覆盖值
        cur->_data = minNode->_data;
        // 删除minNode(它一定没有左孩子,按情况1处理)
        if (minParent->_pLeft == minNode)
            minParent->_pLeft = minNode->_pRight;
        else
            minParent->_pRight = minNode->_pRight;
        delete minNode;
    }
    return true;
}

6 中序遍历

递归遍历(左-根-右),可得到升序序列

cpp 复制代码
template<class T>
void BSTree<T>::_InOrder(PNode root)
{
    if (root == nullptr)
        return;
    _InOrder(root->_pLeft);
    std::cout << root->_data << " ";
    _InOrder(root->_pRight);
}

template<class T>
void BSTree<T>::InOrder()
{
    _InOrder(_pRoot);
    std::cout << std::endl;
}

完整代码

cpp 复制代码
#include <iostream>
using namespace std;

template<class T>
struct BSTNode
{
    BSTNode(const T& data = T())
        : _pLeft(nullptr), _pRight(nullptr), _data(data)
    {}
    BSTNode<T>* _pLeft;
    BSTNode<T>* _pRight;
    T _data;
};

template<class T>
class BSTree
{
    typedef BSTNode<T> Node;
    typedef Node* PNode;
public:
    BSTree();
    ~BSTree();
    PNode Find(const T& data);
    bool Insert(const T& data);
    bool Erase(const T& data);
    void InOrder();
private:
    PNode _pRoot;
    void _Destroy(PNode& root);
    void _InOrder(PNode root);
};

// 构造函数
template<class T>
BSTree<T>::BSTree()
    : _pRoot(nullptr)
{}

// 析构辅助函数
template<class T>
void BSTree<T>::_Destroy(PNode& root)
{
    if (root == nullptr)
        return;
    _Destroy(root->_pLeft);
    _Destroy(root->_pRight);
    delete root;
    root = nullptr;
}

// 析构函数
template<class T>
BSTree<T>::~BSTree()
{
    _Destroy(_pRoot);
}

// 查找
template<class T>
typename BSTree<T>::PNode BSTree<T>::Find(const T& data)
{
    PNode cur = _pRoot;
    while (cur)
    {
        if (data < cur->_data)
            cur = cur->_pLeft;
        else if (data > cur->_data)
            cur = cur->_pRight;
        else
            return cur;
    }
    return nullptr;
}

// 插入
template<class T>
bool BSTree<T>::Insert(const T& data)
{
    if (_pRoot == nullptr)
    {
        _pRoot = new Node(data);
        return true;
    }
    PNode cur = _pRoot;
    PNode parent = nullptr;
    while (cur)
    {
        parent = cur;
        if (data < cur->_data)
            cur = cur->_pLeft;
        else if (data > cur->_data)
            cur = cur->_pRight;
        else
            return false;
    }
    PNode newNode = new Node(data);
    if (data < parent->_data)
        parent->_pLeft = newNode;
    else
        parent->_pRight = newNode;
    return true;
}

// 删除
template<class T>
bool BSTree<T>::Erase(const T& data)
{
    if (_pRoot == nullptr)
        return false;

    PNode cur = _pRoot;
    PNode parent = nullptr;
    while (cur)
    {
        if (data == cur->_data)
            break;
        parent = cur;
        if (data < cur->_data)
            cur = cur->_pLeft;
        else
            cur = cur->_pRight;
    }
    if (cur == nullptr)
        return false;

    // 只有右孩子或叶子
    if (cur->_pLeft == nullptr)
    {
        if (parent == nullptr)
            _pRoot = cur->_pRight;
        else if (cur == parent->_pLeft)
            parent->_pLeft = cur->_pRight;
        else
            parent->_pRight = cur->_pRight;
        delete cur;
    }
    // 只有左孩子
    else if (cur->_pRight == nullptr)
    {
        if (parent == nullptr)
            _pRoot = cur->_pLeft;
        else if (cur == parent->_pLeft)
            parent->_pLeft = cur->_pLeft;
        else
            parent->_pRight = cur->_pLeft;
        delete cur;
    }
    // 左右孩子都有
    else
    {
        PNode minParent = cur;
        PNode minNode = cur->_pRight;
        while (minNode->_pLeft)
        {
            minParent = minNode;
            minNode = minNode->_pLeft;
        }
        cur->_data = minNode->_data;
        if (minParent->_pLeft == minNode)
            minParent->_pLeft = minNode->_pRight;
        else
            minParent->_pRight = minNode->_pRight;
        delete minNode;
    }
    return true;
}

// 中序遍历辅助
template<class T>
void BSTree<T>::_InOrder(PNode root)
{
    if (root == nullptr)
        return;
    _InOrder(root->_pLeft);
    cout << root->_data << " ";
    _InOrder(root->_pRight);
}

// 中序遍历
template<class T>
void BSTree<T>::InOrder()
{
    _InOrder(_pRoot);
    cout << endl;
}

// 测试
int main()
{
    BSTree<int> bst;
    bst.Insert(5);
    bst.Insert(3);
    bst.Insert(7);
    bst.Insert(1);
    bst.Insert(4);
    bst.Insert(6);
    bst.Insert(8);

    cout << "中序遍历: ";
    bst.InOrder();   // 1 3 4 5 6 7 8

    cout << "查找4: " << (bst.Find(4) ? "找到" : "未找到") << endl;
    cout << "查找9: " << (bst.Find(9) ? "找到" : "未找到") << endl;

    bst.Erase(5);
    cout << "删除5后: ";
    bst.InOrder();   // 1 3 4 6 7 8

    bst.Erase(1);
    cout << "删除1后: ";
    bst.InOrder();   // 3 4 6 7 8

    return 0;
}

二叉搜索树的应用

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

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

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

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

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

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

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

性能分析

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

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

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

最好情况

如果二叉搜索树接近完全二叉树,那么树高大约是 log₂N,平均比较次数也是 log₂N。这时 BST 很高效。

最坏情况

如果插入顺序很差,比如有序插入:1, 2, 3, 4, 5。那 BST 会退化成一条链(因为 BST 不自平衡 )。这时平均比较次数接近 N/2

也就是说:本来希望像二分查找一样快,结果退化后,接近顺序查找

这就是 BST 最大的问题。能不能不管按什么顺序插入,都让性能保持接近最优?答案就是:

AVL 树,红黑树

它们的作用就是:在插入删除后,尽量维持树的平衡,避免退化。

二叉树进阶面试题

  1. 二叉树创建字符串。606. 根据二叉树创建字符串 - 力扣(LeetCode)

  2. 二叉树的分层遍历1。102. 二叉树的层序遍历 - 力扣(LeetCode)

  3. 二叉树的分层遍历2。107. 二叉树的层序遍历 II - 力扣(LeetCode)

  4. 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先 。236. 二叉树的最近公共祖先 - 力扣(LeetCode)

  5. 二叉树搜索树转换成排序双向链表。二叉搜索树与双向链表_牛客题霸_牛客网

  6. 根据一棵树的前序遍历与中序遍历构造二叉树。 105. 从前序与中序遍历序列构造二叉树 - 力扣(LeetCode)

  7. 根据一棵树的中序遍历与后序遍历构造二叉树。106. 从中序与后序遍历序列构造二叉树 - 力扣(LeetCode)

  8. 二叉树的前序遍历,非递归迭代实现 。144. 二叉树的前序遍历 - 力扣(LeetCode)

  9. 二叉树中序遍历 ,非递归迭代实现。94. 二叉树的中序遍历 - 力扣(LeetCode)

  10. 二叉树的后序遍历 ,非递归迭代实现。145. 二叉树的后序遍历 - 力扣(LeetCode)

相关推荐
无限码力1 小时前
华为非AI方向笔试真题 - 容器镜像平均大小统计
算法·华为·华为非ai方向笔试真题·华为笔试真题·华为非ai笔试真题·华为0612非ai笔试真题
hairenwangmiao1 小时前
c++排序(第一章----桶排序与sort排序)
数据结构·c++·排序
郝学胜-神的一滴1 小时前
[简化版 GAMES 101] 计算机图形学 13:从光栅化到着色——赋予三维像素光影灵魂
c++·计算机视觉·unity·godot·图形渲染·opengl·unreal
无限码力1 小时前
华为非AI方向0612笔试真题-循环异或加密器(详细思路+多语言题解)
算法·华为·华为非ai方向笔试真题·华为笔试真题·华为0612笔试真题
凌波粒1 小时前
LeetCode--1584. 连接所有点的最小费用(最小生成树/Prim算法/Kruskal算法)
算法·leetcode·职场和发展
xieliyu.1 小时前
Java数据结构:从0开始手搓Hash桶
java·数据结构·哈希算法
simidagogogo1 小时前
生产环境推荐系统最隐蔽的坑:Training-Serving Skew 详解与实战
算法·spark·推荐算法
Shadow(⊙o⊙)1 小时前
信号2.0,深入信号三张表block pending handlers,core文件的使用,信号执行逻辑:CPU虚拟内存物理内存,时钟源,软中断。
linux·运维·服务器·开发语言·c++
不吃土豆的马铃薯1 小时前
高并发服务器数据库连接池设计详解
服务器·网络·数据库·c++·mysql