数据结构--二叉搜索树

目录

二叉搜索树的概念

二叉树的实现

结点类 

函数接口总览

实现二叉树

二叉搜索树的应用

K模型

KV模型

二叉搜索树的性能分析

二叉搜索树的概念

    二叉搜索树(Binary Search Tree,简称BST)是一种特殊的二叉树,其具有以下几个性质:

  1. 每个节点至多有两个子节点:分别称为左子节点和右子节点。
  2. 左子树上的所有节点的值都小于根节点的值
  3. 右子树上的所有节点的值都大于根节点的值
  4. 每个节点的左右子树也都是二叉搜索树

这些性质确保了在二叉搜索树中进行查找、插入和删除操作具有良好的性能。具体地,这些操作在平均情况下的时间复杂度为 O(logn),其中 n 是树中节点的数量。不过,在最坏情况下(树退化成链表),时间复杂度可能会降为 O(n)。

下面是一个二叉搜索树的示例:

在这个二叉搜索树中:

  • 根节点是 8。
  • 根节点的左子树包含节点 3、1、6、4 和 7,这些节点的值都小于 8。
  • 根节点的右子树包含节点 10、14 和 13,这些节点的值都大于 8。

二叉树的实现

结点类 

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

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

函数接口总览

//二叉搜索树
template<class K>
class BSTree
{typedef BSTreeNode<K> Node;
public://构造函数BSTree();//拷贝构造函数BSTree(const BSTree<K>& t);//赋值运算符重载函数BSTree<K>& operator=(BSTree<K> t);//析构函数~BSTree();//插入函数bool Insert(const K& key);//删除函数bool Erase(const K& key);//查找函数Node* Find(const K& key);//中序遍历void InOrder();
private:Node* _root; //指向二叉搜索树的根结点
};

    为了在实现其他接口的过程中方便随时检查,最好实现一个二叉搜索树的中序遍历接口,当我们对二叉搜索树进行一次操作后,可以调用中序遍历接口对二叉搜索树进行遍历,若二叉搜索树进行操作后的遍历结果仍为升序,则可以初步判断所实现的接口是正确。

//中序遍历的子函数
void _InOrder(Node* root)
{if (root == nullptr)return;_InOrder(root->_left); //遍历左子树cout << root->_key << " "; //遍历根结点_InOrder(root->_right); //遍历右子树
}
//中序遍历
void InOrder()
{_InOrder(_root);cout << endl;
}

实现二叉树

代码如下:

// 定义二叉搜索树模板类
template<class K>
class BSTree
{
private:BSTreeNode<K>* _root;  // 树的根结点// 辅助函数:递归拷贝树BSTreeNode<K>* CopyTree(BSTreeNode<K>* root){if (root == nullptr)return nullptr;BSTreeNode<K>* newNode = new BSTreeNode<K>(root->_key);newNode->_left = CopyTree(root->_left);newNode->_right = CopyTree(root->_right);return newNode;}// 辅助函数:递归销毁树void DestroyTree(BSTreeNode<K>* root){if (root != nullptr){DestroyTree(root->_left);   // 递归销毁左子树DestroyTree(root->_right);  // 递归销毁右子树delete root;                // 删除当前结点}}// 辅助函数:递归插入BSTreeNode<K>* InsertRecursive(BSTreeNode<K>* root, const K& key){if (root == nullptr){return new BSTreeNode<K>(key);  // 找到插入位置后创建新结点}if (key < root->_key){root->_left = InsertRecursive(root->_left, key);  // 递归插入左子树}else if (key > root->_key){root->_right = InsertRecursive(root->_right, key); // 递归插入右子树}return root;}// 辅助函数:递归删除BSTreeNode<K>* DeleteRecursive(BSTreeNode<K>* root, const K& key){if (root == nullptr)return root;if (key < root->_key){root->_left = DeleteRecursive(root->_left, key);  // 在左子树中删除}else if (key > root->_key){root->_right = DeleteRecursive(root->_right, key); // 在右子树中删除}else{if (root->_left == nullptr){BSTreeNode<K>* temp = root->_right;delete root;  // 删除当前结点return temp;}else if (root->_right == nullptr){BSTreeNode<K>* temp = root->_left;delete root;  // 删除当前结点return temp;}BSTreeNode<K>* temp = MinValueNode(root->_right);  // 找到右子树中最小值结点root->_key = temp->_key;  // 用右子树中最小值替换当前结点root->_right = DeleteRecursive(root->_right, temp->_key);  // 删除右子树中的最小值结点}return root;}// 辅助函数:找到最小值结点BSTreeNode<K>* MinValueNode(BSTreeNode<K>* node){BSTreeNode<K>* current = node;while (current && current->_left != nullptr){current = current->_left;  // 找到最左端的结点即为最小值结点}return current;}// 辅助函数:递归查找BSTreeNode<K>* SearchRecursive(BSTreeNode<K>* root, const K& key) const{if (root == nullptr || root->_key == key)return root;if (key < root->_key)return SearchRecursive(root->_left, key);  // 在左子树中查找return SearchRecursive(root->_right, key); // 在右子树中查找}public:// 构造函数,初始化空树BSTree(): _root(nullptr){}// 拷贝构造函数BSTree(const BSTree<K>& other): _root(nullptr){_root = CopyTree(other._root);  // 深拷贝另一棵树}// 赋值运算符重载函数BSTree<K>& operator=(const BSTree<K>& other){if (this != &other){DestroyTree(_root);  // 销毁当前树_root = CopyTree(other._root);  // 深拷贝另一棵树}return *this;}// 析构函数,销毁树~BSTree(){DestroyTree(_root);  // 递归销毁树中所有结点}// 插入函数(非递归)void InsertIterative(const K& key){if (_root == nullptr){_root = new BSTreeNode<K>(key);  // 插入根结点return;}BSTreeNode<K>* parent = nullptr;BSTreeNode<K>* current = _root;while (current != nullptr){parent = current;if (key < current->_key){current = current->_left;  // 移动到左子结点}else if (key > current->_key){current = current->_right; // 移动到右子结点}else{return;  // 不插入重复值}}if (key < parent->_key){parent->_left = new BSTreeNode<K>(key);  // 插入左子结点}else{parent->_right = new BSTreeNode<K>(key); // 插入右子结点}}// 插入函数(递归)void Insert(const K& key){_root = InsertRecursive(_root, key);  // 调用递归插入函数}// 删除函数(非递归)void DeleteIterative(const K& key){BSTreeNode<K>* parent = nullptr;BSTreeNode<K>* current = _root;while (current != nullptr && current->_key != key){parent = current;if (key < current->_key){current = current->_left;  // 移动到左子结点}else{current = current->_right; // 移动到右子结点}}if (current == nullptr)return;if (current->_left == nullptr || current->_right == nullptr){BSTreeNode<K>* newCurrent;if (current->_left == nullptr){newCurrent = current->_right;}else{newCurrent = current->_left;}if (parent == nullptr){_root = newCurrent;  // 删除根结点}else if (current == parent->_left){parent->_left = newCurrent;  // 删除左子结点}else{parent->_right = newCurrent; // 删除右子结点}delete current;}else{BSTreeNode<K>* p = nullptr;BSTreeNode<K>* temp;temp = current->_right;while (temp->_left != nullptr){p = temp;temp = temp->_left;}if (p != nullptr){p->_left = temp->_right;}else{current->_right = temp->_right;}current->_key = temp->_key;delete temp;}}// 删除函数(递归)void Delete(const K& key){_root = DeleteRecursive(_root, key);  // 调用递归删除函数}// 查找函数(非递归)BSTreeNode<K>* SearchIterative(const K& key) const{BSTreeNode<K>* current = _root;while (current != nullptr && current->_key != key){if (key < current->_key){current = current->_left;  // 移动到左子结点}else{current = current->_right; // 移动到右子结点}}return current;  // 返回找到的结点或 nullptr}// 查找函数(递归)BSTreeNode<K>* Search(const K& key) const{return SearchRecursive(_root, key);  // 调用递归查找函数}
};

用法和预期效果:

  1. 构造函数

    • 用法:BSTree<int> tree;
    • 预期效果:创建一个空的二叉搜索树。
  2. 拷贝构造函数

    • 用法:BSTree<int> tree2 = tree1;
    • 预期效果:深拷贝tree1,创建一个新的二叉搜索树tree2,其结构和tree1相同。
  3. 赋值运算符重载函数

    • 用法:tree2 = tree1;
    • 预期效果:深拷贝tree1tree2,覆盖tree2原来的内容。
  4. 析构函数

    • 用法:当树对象生命周期结束时自动调用。
    • 预期效果:递归销毁树中所有结点,释放内存。
  5. 插入函数(非递归)

    • 用法:tree.InsertIterative(10);
    • 预期效果:在树中插入值为10的结点。
  6. 插入函数(递归)

    • 用法:tree.Insert(10);
    • 预期效果:在树中插入值为10的结点。
  7. 删除函数(非递归)

    • 用法:tree.DeleteIterative(10);
    • 预期效果:在树中删除值为10的结点。
  8. 删除函数(递归)

    • 用法:tree.Delete(10);
    • 预期效果:在树中删除值为10的结点。
  9. 查找函数(非递归)

    • 用法:BSTreeNode<int>* node = tree.SearchIterative(10);
    • 预期效果:在树中查找值为10的结点,返回指向该结点的指针,如果未找到则返回nullptr
  10. 查找函数(递归)

    • 用法:BSTreeNode<int>* node = tree.Search(10);
    • 预期效果:在树中查找值为10的结点,返回指向该结点的指针,如果未找到则返回nullptr

二叉搜索树的应用

二叉搜索树(BST)是一种重要的数据结构,广泛应用于各种算法和系统中。以下是一些常见的应用:

  1. 符号表:在编译器中,二叉搜索树可以用来实现符号表,用于存储变量和函数的名称及其属性。
  2. 字典:二叉搜索树可以用来实现字典(例如键值对存储),支持高效的插入、删除和查找操作。
  3. 优先队列:可以使用二叉搜索树来实现优先队列,其中元素按照优先级排列,支持快速的插入和删除操作。
  4. 数据库索引:在数据库系统中,二叉搜索树可以用作索引结构,以加速查询操作。
  5. 排序和搜索:二叉搜索树天然地支持中序遍历,从而可以对元素进行排序和高效搜索。

K模型

    K模型是基于二叉搜索树的一种简化形式,主要用于处理单个键(key)的存储和查询。每个结点只包含一个键,不涉及值(value)。 

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

  1. 以单词集合中的每个单词作为key,构建一棵二叉搜索树。
  2. 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。

KV模型

    KV模型是二叉搜索树的扩展形式,用于处理键值对(key-value)的存储和查询。每个结点包含一个键和一个值。 

比如:英汉词典就是英文与中文的对应关系,即<word, Chinese>就构成一种键值对。具体方式如下:

以<单词, 中文含义>为键值对,构建一棵二叉搜索树。注意:二叉搜索树需要进行比较,键值对比较时只比较key。
查询英文单词时,只需给出英文单词就可以快速找到与其对应的中文含义。

二叉搜索树的性能分析

时间复杂度

  1. 查找、插入和删除

    • 最优情况:当树是平衡的(完全平衡二叉树),时间复杂度为O(log n)。
    • 最坏情况:当树退化成链表(每个结点只有一个子结点),时间复杂度为O(n)。
  2. 遍历

    • 中序遍历、先序遍历、后序遍历的时间复杂度均为O(n),因为需要访问每个结点。

空间复杂度

  1. 空间使用

    • 空间复杂度为O(n),n为树中的结点数。
  2. 递归调用栈

    • 在最坏情况下(树退化成链表),递归调用栈的空间复杂度为O(n)。

平衡性

  1. 平衡二叉树:如AVL树、红黑树等,保证在最坏情况下也能达到O(log n)的时间复杂度。
  2. 普通二叉搜索树:如果输入数据是随机的,树大概率接近平衡。但如果输入数据是有序的(或接近有序),树可能退化为链表,导致性能下降。

性能优化

  1. 自平衡二叉搜索树:如AVL树、红黑树、Splay树等,通过自动调整树的结构,保证树的平衡,从而提升性能。
  2. B树和B+树:特别适用于数据库索引,支持高效的磁盘存取操作。
  3. 散列:对于一些应用,哈希表(Hash Table)可以提供更快的查找、插入和删除操作,但不适用于需要排序的场景。

总结

二叉搜索树(BST)是一种基础而重要的数据结构,广泛应用于符号表、字典、优先队列和数据库索引等场景。K模型和KV模型分别处理集合和字典的需求。BST的性能在很大程度上取决于树的平衡性,使用自平衡树可以保证最坏情况下的性能。此外,对于特定应用场景,选择合适的数据结构(如B树或哈希表)也非常重要。

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

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

相关文章

6818 android 修改开机 logo, 编译脚本分析

问题&#xff1a; 客户需要去掉 android5.1 的开机logo. 说明&#xff1a; 对于Android5.1 来说&#xff0c;uboot 与kernel 的logo 是一个。 过程&#xff1a; 其实对于开机logo 的修改很简单&#xff0c;直接参考厂家手册就可以了。 这是 android4.4 的开机logo 的修改&…

产品经理-需求收集(二)

1. 什么是需求 指在一定的时期中&#xff0c;一定场景中&#xff0c;无论是心理上还是生理上的&#xff0c;用户有着某种“需要”&#xff0c;这种“需要”用户自己不一定知道的&#xff0c;有了这种“需要”后用户就有做某件事情的动机并促使达到其某种目的&#xff0c;这也就…

FPGA实现多路并行dds

目录 基本原理 verilog代码 仿真结果​ 基本原理 多路并行dds&#xff0c;传统DDS的局限性在于输出频率有限。根据奈奎斯特采样定理&#xff0c;单路DDS的输出频率应小于系统时钟频率的一半。但是在很多地方&#xff0c;要使采样率保持一致&#xff0c;所以&#xff0c;为了…

【CTF Web】CTFShow web7 Writeup(SQL注入+PHP+进制转换)

web7 1 阿呆得到最高指示&#xff0c;如果还出问题&#xff0c;就卷铺盖滚蛋&#xff0c;阿呆心在流血。 解法 注意到&#xff1a; <!-- flag in id 1000 -->拦截很多种字符&#xff0c;连 select 也不给用了。 if(preg_match("/\|\"|or|\||\-|\\\|\/|\\*|\…

CSP俄罗斯方块(简单易懂)

开始将题目理解成了&#xff0c;开始的列应该是从输入图案的最左端开始计算&#xff0c;将前面所有的空列都删掉&#xff0c;代码如下&#xff1a; #include<bits/stdc.h> using namespace std; const int N 1e410; const int M 1e510; int a[20][20]; int b[5][5];int…

leedcode【203】. 移除链表元素——Java解法

Problem: 203. 移除链表元素 题目思路解题方法复杂度Code效果 题目 给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链表中所有满足 Node.val val 的节点&#xff0c;并返回 新的头节点 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,6,3,4,5,6], val…

OS复习笔记ch6-1

死锁的原理 定义 一组进程中&#xff0c;其中每个进程因等待事件而阻塞&#xff0c;且所等待的事件只能被这组进程中的另一阻塞进程激发称之为死锁。 举例如下 四个车辆希望紧迫的希望能很快通过&#xff0c;每辆车需要两个象限的资源&#xff0c;然而四个车都只得到一个象…

电子电器架构 - AUTOSAR软件架构介绍

电子电器架构 - AUTOSAR软件架构介绍 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明自己…

Maven多环境打包配置

一、启动时指定环境配置文件 在启动springboot应用的jar包时&#xff0c;我们可以指定配置文件&#xff0c;通常把配置文件上传到linux服务器对应jar包的同级目录&#xff0c;或者统一的配置文件存放目录 java -jar your-app.jar --spring.config.location/opt/softs/applicat…

matlab 图像的中值滤波

目录 一、功能概述1、算法概述2、主要函数3、计算公式二、代码实现三、结果展示四、参考链接本文由CSDN点云侠翻译,放入付费专栏只为防不要脸的爬虫。专栏值钱的不是本文,切勿因本文而订阅。 一、功能概述 1、算法概述 中值滤波是图像处理中一种常用的非线性运算,用于减少…

间接平差——以水准网平差为例 (python详细过程版)

目录 一、原理概述二、案例分析三、代码实现四、结果展示本文由CSDN点云侠原创,间接平差——以水准网平差为例 (python详细过程版),爬虫自重。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫与GPT生成的文章。 一、原理概述 间接平差的函数模型和随机模型…

openai api的初次尝试

不懂已经不去百度了&#xff0c;现在直接问chatgpt就解决绝大多数问题了。 OpenAI API目前还没有官方支持的npm库&#xff0c;但是您可以使用现有的第三方npm库进行OpenAI API的访问和使用。这里提供一个npm库 openai-node 的安装和使用方法&#xff1a; 在命令行或终端中使用…

区块链会议投稿资讯CCF A--USENIX Security 2025 截止9.4、1.22 附录用率

会议名称&#xff1a;34th USENIX Security Symposium CCF等级&#xff1a;CCF A类学术会议 类别&#xff1a;网络与信息安全 录用率&#xff1a;2023年接收率29%&#xff0c;2024录用的区块链相关文章请查看 Symposium Topics System security Operating systems security …

vue实现可拖拽移动悬浮球

封装悬浮球组件&#xff0c;文件名s-icons.vue <template><div ref"icons" class"icons-container" :style"{ left: left px, top: top px }"><slot></slot></div> </template> <script> export …

国产化服务器开启NTP功能并向NTP时钟服务器同步

1.备份/etc/chrony.conf文件&#xff1b; cp -rp /etc/chrony.conf /etc/chrony.conf.bak.20240522 2.修改chrony.conf文件&#xff0c;增加NTP时钟信息。&#xff08;客户端填写时钟同步服务器的IP地址或者域名&#xff0c;我这里写的IP地址。下面Allow NTP Client是只允许…

数字图像处理冈塞雷斯第四版课后习题答案【英文原版】

第二章 第三章 . 第四章 傅里叶变换是一个线性过程&#xff0c;而计算梯度的平方根和平方根则是非线性运算。傅里叶变换可以用来计算微分的差值(如问题4.50)&#xff0c;但必须在空间域中直接计算平方和平方根值。 (a)实际上&#xff0c;由于高通操作&#xff0c;环有一个暗中心…

留守儿童|基于SprinBoot+vue的留守儿童爱心网站(源码+数据库+文档)

留守儿童爱心网站 目录 基于SprinBootvue的留守儿童爱心网站 一、前言 二、系统设计 三、系统功能设计 1系统功能模块 2管理员功能模块 3用户功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&…

STM_HAL_TIM_IC_(输入捕获,捕获PWM波)

介绍 TM32微控制器提供了输入捕获&#xff08;Input Capture&#xff09;功能&#xff0c;这是一种用于精确测量外部信号脉冲宽度和周期的强大技术。输入捕获通常与定时器&#xff08;如TIM&#xff09;的高级控制定时器&#xff08;TIM1和TIM8&#xff09;或通用定时器&#…

JavaEE-文件IO2

文章目录 前言一、字节流1.1 读文件1.2 写文件 二、字符流2.1 读文件2.2 写文件 三、文件IO三道例题 前言 在这里对Java标准库中对文件内容的操作进行总结&#xff0c;总体上分为两部分&#xff0c;字节流和字符流&#xff0c;就是以字节为单位读取文件和以字符为单位读取文件…

[AI Google] 介绍 VideoFX,以及 ImageFX 和 MusicFX 的新功能

VideoFX 是来自 labs.google 的最新实验&#xff0c;您可以查看音乐效果和图像效果的新更新&#xff0c;现在在 110 多个国家可用。 生成式媒体正在改变人们构思创意并增强我们的创造力能力的方式。我们致力于与创作者和艺术家合作构建人工智能&#xff0c;以更好地理解这些生成…