C++ 二叉搜索树

news2024/12/25 1:03:09

1. 内容安排说明

二叉树在前面 C 数据结构阶段已经讲过,本节取名二叉树进阶是因为:
1. map set 特性需要 先铺垫二叉搜索树,而二叉搜索树也是一种树形结构
2. 二叉搜索树的特性了解,有助于更好的理解 map set 的特性
3. 二叉树中部分面试题稍微有点难度 ,在前面讲解 大家不容易接受 ,且时间长容易忘
4. 有些 OJ 题使用 C 语言方式实现比较麻烦,比如有些地方要返回动态开辟的二维数组,非常麻
烦。
因此本节借二叉树搜索树,对二叉树部分进行收尾总结。

2. 二叉搜索树

2.1 二叉搜索树概念

二叉搜索树又称二叉排序树,它或者是一棵空树 ,或者是具有以下性质的二叉树:
  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 它的左右子树也分别为二叉搜索树

 

 2.2 二叉搜索树操作

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

 

1. 二叉搜索树的查找
a 、从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找。
b 、最多查找高度次,走到到空,还没找到,这个值不存在。
2. 二叉搜索树的插入
插入的具体过程如下:
a. 树为空,则直接新增节点,赋值给 root 指针
b. 树不空,按二叉搜索树性质查找插入位置,插入新节点

1. ** 二叉搜索树的删除
首先查找元素是否在二叉搜索树中,如果不存在,则返回 , 否则要删除的结点可能分下面四种情
况:
a. 要删除的结点无孩子结点
b. 要删除的结点只有左孩子结点
c. 要删除的结点只有右孩子结点
d. 要删除的结点有左、右孩子结点
看起来有待删除节点有 4 中情况,实际情况 a 可以与情况 b 或者 c 合并起来,因此真正的删除过程
如下:
情况 b :删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点 -- 直接删除
情况 c :删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点 -- 直接删除
情况 d :在它的右子树中寻找中序下的第一个结点 ( 关键码最小 ) ,用它的值填补到被删除节点
中,再来处理该结点的删除问题 -- 替换法删除

 

 

 

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

template <class T>
struct BSTNode
{

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

template <class T>
class BSTTree
{
    typedef BSTNode<T> Node;
    typedef Node *PNode;

public:
    BSTTree() : _pRoot(nullptr) {}
    ~BSTTree()
    {
        if (_pRoot)
        {

            delete _pRoot;
            _pRoot = nullptr;
        }
    }
    //根据二叉树的性质查找,找到值为data的结点在二叉搜索树的位置
    PNode Find(const T &data)
    {

        PNode pCur = _pRoot;
        // PNode pParent=nullptr;
        while (pCur)
        {

            if (data == pCur->data)
                return pCur;

            else if (data < pCur->_data)
                pCur = pCur->_pLeft;

            else
                pCur = pCur->_pRight;
        }

        if (nullptr == pCur)
            return nullptr;
    }

    bool Insert(const T &data)
    {

        if (nullptr == _pRoot)
        {
            _pRoot = new Node(data);
            return true;
        }
        //按照二叉搜索树的性质查找data 树中的位置
        PNode pCur = _pRoot;
        //记录pCur 的双亲,最终新元素插在PCur双亲左右孩子的位置
        PNode pParent = nullptr;

        while (pCur)
        {

            pParent = pCur;
            if (data < pCur->_data)
            {
                pCur = pCur->_pLeft;
            }

            else if (data > pCur->_data)
                pCur = pCur->_pRight;

            else
                return false;
        }

        //插入元素
        pCur = new Node(data);
        if (data < pParent->_data)
            pParent->_pLeft = pCur;

        else
            pParent->_pRight = pCur;
        return true;
    }

    bool Erase(const T &data)
    {

        //如果树是空,那么就删除失败
        if (nullptr == _pRoot)
            return false;

        //查找data 在树中的位置
        Node *pCur = _pRoot; // pNode 即使是Node *
        PNode pParent = nullptr;

        while (pCur)
        {

            if (data == pCur->_data)
                break;

            else if (data < pCur->_data)
            {
                pParent = pCur;
                pCur = pCur->_pLeft;
            }

            else
            {
                pParent = pCur;
                pCur = pCur->_pRight;
            }
        }
        // data 不再二叉搜索树中,无法删除。
        if (nullptr == pCur)
            return false;

        //删除又分几种情况删除
        // 1.左右都为空
        if (nullptr == pCur->_pRight && nullptr == pCur->_pLeft)
        {
            delete pCur;
            pCur = nullptr;
        }
        //只有右节点,用右结点替换要删除的结点
        else if (nullptr == pCur->_pLeft)
        {
            pCur->_data = pCur->_pLeft->_data;
            Node *temp = pCur->_pRight;
            pCur->_pLeft = temp->_pLeft;
            pCur->_pRight = temp->_pRight;

            //  Node * temp=pCur;
            //  pCur=pCur->_pRight;
            //  delete temp;
        }

        //只有左节点
        else if (nullptr == pCur->_pRight)
        {
            pCur->_data = pCur->_pLeft->_data;
            Node *temp = pCur->_pLeft;
            pCur->_pLeft = temp->_pLeft;
            pCur->_pRight = temp->_pRight;
            // pCur=pCur->_pLeft;
            // delete temp;
        }
        else
        {
            // 如果要删除的结点有两个字结点,找到右子树最小结点,用该结点替换要删除的结点
            Node *minNode = findMinNode(pCur->_pRight);
            PNode temp = pCur->_pRight;
            pCur->_data = minNode->_data;

            pCur->_pRight = temp->_pRight;
            // Node->right=pCur->_pRight;
            delete minNode;
            minNode = nullptr;
        }

        return true;
    }

    PNode findMinNode(Node *node)
    {

        while (node->_pLeft)
        {
            node = node->_pLeft;
        }

        return node;
    }

private:
    PNode _pRoot;
};

int main()
{
    BSTTree<int> bs;

    bs.Insert(8);
    bs.Insert(3);
    bs.Insert(10);
    bs.Insert(1);
    bs.Insert(6);
    bs.Insert(14);
    bs.Insert(4);
    bs.Insert(13);

    bs.Erase(8);

    // bs.Erase(7);
    // bs.Erase(14);
    // bs.Erase(3);
    // bs.Erase(8);

    cout << endl;

    system("pause");
    return 0;
}

2.4 二叉搜索树的应用

        

1. K 模型: K 模型即只有 key 作为关键码,结构中只需要存储 Key 即可,关键码即为需要搜索到
的值
比如: 给一个单词 word ,判断该单词是否拼写正确 ,具体方式如下:以词库中所有单词集合中的每个单词作为key ,构建一棵二叉搜索树在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
2. KV 模型:每一个关键码 key ,都有与之对应的值 Value ,即 <Key, Value> 的键值对 。该种方
式在现实生活中非常常见:比如英汉词典就是英文与中文的对应关系 ,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese> 就构成一种键值对;再比如统计单词次数 ,统计成功后,给定单词就可快速找到其出现的次数, 单词与其出 现次数就是 <word, count> 就构成一种键值对。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

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<T> *_pLeft;
    BSTNode<T> *_pRight;

    k _key;
    v _value;
};

template <class K, class V>

class BSTree
{

    typedef BSTNode<K, V> Node;
    typedef Node *PNode;

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

private:
    PNode _pRoot;
};

void TestBSTree3()
{

    BSTree<string, string> dict;
    dict.Insert("string", "zifuchuan");
    dict.Insert("tree", "shu");
    dict.Insert("left", "zuobian,shengyu");
    dict.Insert("right", "youbian");
    dict.Insert("sort", "paixu");

    string str;
    while (cin >> str)
    {

        BSTNode<string, string> *ret = dict.Find(str);
        if (ret == nullptr)
        {
            cout << "error" << str << endl;
        }
        else
        {
            cout << str << "translate:" << str << 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();
}

int main()
{

    system("pause");
    return 0;
}

2.5 二叉搜索树的性能分析

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

 

 

最优情况下,二叉搜索树为完全二叉树 ( 或者接近完全二叉树 ) ,其平均比较次数为: $log_2 N$
最差情况下,二叉搜索树退化为单支树 ( 或者类似单支 ) ,其平均比较次数为: $\frac{N}{2}$
问题:如果退化成单支树,二叉搜索树的性能就失去了。那能否进行改进,不论按照什么次序插
入关键码,二叉搜索树的性能都能达到最优?那么我们后续章节学习的 AVL 树和红黑树就可以上
场了。
3. 二叉树进阶面试题
这些题目更适合使用 C++ 完成,难度也更大一些
1. 二叉树创建字符串。 力扣
2. 二叉树的分层遍历 1 力扣
3. 二叉树的分层遍历 2 力扣
4. 给定一个二叉树 , 找到该树中两个指定节点的最近公共祖先 。 力扣
5. 二叉树搜索树转换成排序双向链表。 二叉搜索树与双向链表_牛客题霸_牛客网
6. 根据一棵树的前序遍历与中序遍历构造二叉树。 力扣
7. 根据一棵树的中序遍历与后序遍历构造二叉树。 力扣
8. 二叉树的前序遍历,非递归迭代实现 。 力扣
9. 二叉树中序遍历 ,非递归迭代实现。 力扣
10. 二叉树的后序遍历 ,非递归迭代实现。 力扣

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

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

相关文章

在JDK17尝鲜Flink1.17

在JDK17尝鲜Flink1.17 前言 还没玩明白老版本&#xff0c;Flink1.17就来了&#xff01;&#xff01;&#xff01;总还是要向前看的。。。 根据官网文档&#xff1a;https://nightlies.apache.org/flink/flink-docs-release-1.17/docs/try-flink/local_installation/ Flink r…

【Excel技巧】如何将一堆文字快速整理成一列表格数据?

在平时的工作中&#xff0c;我们有时候需要把很多零散的分布的内容&#xff08;比如姓名&#xff09;&#xff0c;复制到Excel工作表的单元格内&#xff0c;变成一列。如果一个个复制粘贴&#xff0c;显然太过繁琐。 如何批量快速的完成这一操作呢&#xff1f;只需要下面简单几…

关于排查springboot启动时页面出现404

今天在进行开发时&#xff0c;Contronller代码没有问题&#xff0c;前端html也没问题&#xff0c;发现当浏览器输入localhost:8080时404&#xff0c;于是进行排查发现&#xff0c;SpringbootWebApplication文件放到了子目录下。 springboot的启动文件必须放在父目录下才可以检测…

【Git原理与使用】-- 远程操作

目录​​​​​​​ 理解分布式版本控制系统 远程仓库 新建远程仓库 lssue 与 Pull Request模板文件 知识铺垫 lssue 模板文件 Pull Request模板文件 克隆远程仓库 使用 HTTPS 方式 使用 SSH 方式 第一步&#xff1a;创建SSH Key 向远程仓库推送 过程梳理 实操 …

Java安全——安全提供者

Java安全 安全提供者 在Java中&#xff0c;安全提供者&#xff08;Security Provider&#xff09;是一种实现了特定安全服务的软件模块。它提供了一系列的加密、解密、签名、验证和随机数生成等安全功能。安全提供者基础设施在Java中的作用是为开发人员提供一种扩展和替换标准…

vue中使用Drawflow连线插件,并对端口进行命名

效果如图 场景:项目中需要拖拽模块并连线,有输入端和输出端之分,不同模块不同端口才能相连 文档相关 点击前往------->原项目git地址 点击前往------->提供端口既可输出又可输出方案 点击前往----->查阅发现原项目无法对端口命名 public文件夹下创建drawflow文件夹…

myCobot 280 2023机械臂全新功能,手柄控制、自干涉检测

引言 机械臂是一种可编程的、自动化的机械系统&#xff0c;它可以模拟人类的动作&#xff0c;完成各种任务&#xff0c;例如装配、喷涂、包装、搬运、焊接、研磨等。由于其高度灵活性和多功能性&#xff0c;机械臂在现代社会中已经得到了广泛的应用。 myCobot 280 M5Stack 20…

在服务器部署前后端分离的项目(前后都有), 并使用nginx配置跨域

怎样部署自己的项目呢 先准备一个服务器(小系统最便宜的轻量级服务器就行, 如果不需要给人访问的话)安装宝塔面板 (宝塔面板, 可视化界面, 操作简单, 使用非常方便, 上手也很容易, 如果只是学习, 虚拟机也行没必要花钱, 我使用的CentOS7系统,安装宝塔面板)软件: MySQL, Tomcat…

【问题记录】多线程环境下,使用 std::cout 输出内容会显示混乱

环境 Windows 11 家庭中文版Microsoft Visual Studio Community 2022 (64 位) - Current 版本 17.5.3 测试代码 #include <iostream> #include <Windows.h>//创建的线程数量 #define THREAD_COUNT 4DWORD WINAPI ThreadProc(LPVOID lpParam) {UNREFERENCED_P…

JS事件监听

目录 事件监听 事件监听案例 事件监听 事件&#xff1a;HTML事件是发生在HTML元素上的“事情” 按钮点击鼠标移动到元素上按下键盘按键事件监听&#xff1a;JS可以在事件被检测到时执行代码事件绑定 方法一&#xff1a;通过HTML标签中的事件属性进行绑定 <input type"…

在windows环境下安装支持CUDA的opencv-python

文章目录 附件&#xff1a;GPU和CUDA的关系 —— 开发人员通过CUDA可以使用GPU的计算能力来加速各种计算任务&#xff0c;并提高计算性能和效率。一、环境配置&#xff08;0&#xff09;我的电脑配置环境&#xff08;1&#xff09;CUDA cuDNN下载与安装&#xff08;2&#xff…

【云原生、Kubernetes】Kubernetes核心概念理解

首先我们要掌握 Kubernete 的一些核心概念。 这些核心可以帮助我们更好的理解 Kubernetes 的特性和工作机制。 集群组件 首先&#xff0c;Kubernetes 集群中包含2类节点&#xff0c;分别是&#xff1a;master控制节点和node工作节点。 master 控制节点 负责管理整个集群系统…

【手撕算法|动态规划系列No.4】leetcode91. 解码方法

个人主页&#xff1a;平行线也会相交 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 平行线也会相交 原创 收录于专栏【手撕算法系列专栏】【LeetCode】 &#x1f354;本专栏旨在提高自己算法能力的同时&#xff0c;记录一下自己的学习过程&#xff0c;希望…

软件测试:系统测试

1 系统测试的概念 系统测试&#xff08;System Testing&#xff09;的定义&#xff1a;将已经集成好的软件系统&#xff0c;作为整个基于计算机系统的一个元素&#xff0c;与计算机硬件、外设、某些支持软件、数据和人员等其他系统元素结合在一起&#xff0c;在实际运行&#…

HDLBits刷题笔记8:Circuits.Sequential Logic.Latches and Flip-Flops

D flip-flop module top_module (input clk,input d,output reg q );always (posedge clk)q < d; endmoduleD flip-flops 建立一个8bit的D触发器 module top_module (input clk,input [7:0] d,output reg [7:0] q );always (posedge clk)q < d; endmoduleDFF with res…

GDAL 图像直方图统计

文章目录 一、简介二、实现代码三、实现效果参考资料 一、简介 这里使用一种简单的方式来计算图像中的像素值直方图分布。计算过程如下所述&#xff1a; 第一种方式&#xff1a; 1、首先将图像变为一维数组&#xff08;reshape&#xff09;&#xff0c;并将数组中的数值进行排序…

vue点击盒子一步一步滚动

vue点击盒子一步一步滚动 HTML <div class"course_detail"><div class"arrow" v-if"index 0" click"step"></div><div class"lightArrow" v-else click"step"></div><div clas…

自定义的车牌号键盘组件

<template><view class"keyboard-wrap" v-if"kbShow"><view class"head"><view class"done" tap"done"><text class"iconfont iconxiala-"></text>关闭</view></vi…

2. 注册platform

这里先分析platform 对应的dts内容如下 i2s0_8ch: i2sff800000 {compatible "rockchip,rv1126-i2s-tdm";reg <0xff800000 0x1000>;interrupts <GIC_SPI 46 IRQ_TYPE_LEVEL_HIGH>;clocks <&cru MCLK_I2S0_TX>, <&cru MCLK_I2S0_RX&g…

JAVA开发( 腾讯云消息队列 RocketMQ使用总结 )

一、问题背景 之所以需要不停的总结是因为在java开发过程中使用到中间件实在太多了&#xff0c;久久不用就会慢慢变得生疏&#xff0c;有时候一个中间很久没使用&#xff0c;可能经过了很多版本的迭代&#xff0c;使用起来又有区别。所以还是得不断总结更新。最近博主就是在使用…