C++进阶:搜索树

目录

  • 1. 二叉搜索树
    • 1.1 二叉搜索树的结构
    • 1.2 二叉搜索树的接口及其优点与不足
    • 1.3 二叉搜索树自实现
      • 1.3.1 二叉树结点结构
      • 1.3.2 查找
      • 1.3.3 插入
      • 1.3.4 删除
      • 1.3.5 中序遍历
  • 2. 二叉树进阶相关练习
    • 2.1 根据二叉树创建字符串
    • 2.2 二叉树的层序遍历I
    • 2.3 二叉树层序遍历II
    • 2.4 二叉树最近公共祖先结点
    • 2.5 二叉树搜索与双向链表
    • 2.6 从前序与中序遍历序列构造二叉树
    • 2.7 从中序与后序遍历序列构造二叉树
    • 2.8 二叉树前序遍历(非递归)
    • 2.9 二叉树中序遍历(非递归)
    • !!!3.10 二叉树后序遍历(非递归)

1. 二叉搜索树

1.1 二叉搜索树的结构

  1. 搜索二叉树中的所有结点都满足
    <1> 若左子树不为空,则其所有结点的键值都小于父结点
    <2> 若右子树不为空,则其所有结点的键值都大于父节点
    <3> 左右子树都为二叉搜索树

在这里插入图片描述

1.2 二叉搜索树的接口及其优点与不足

  1. 搜索二叉树的功能接口
    <1> 数据插入(Insert)
    <2> 数据删除(Erase)
    <3> 数据查寻(Find)
    <4> 中序遍历(InOrder)
  1. 二叉搜索树的优点:
    <1> 正如其名,此种数据存储结构,尤其擅长数据搜索,其进行数据搜索的效率极高。
    <2> 在其结构为近似完全二叉树或满二叉树的情况时,其的搜索效率可以达到O( l o g N logN logN)
    <3> 当以中序遍历的方式遍历整棵搜索树时,得到的数据即为升序序列

在这里插入图片描述

  1. 二叉搜索树的不足:
    <1> 根据二叉搜索树的插入逻辑,当数据以完全升序或降序插入时,会使得二叉树退化为单枝结构,此种结构下其搜索效率也退化为O(N)

在这里插入图片描述

1.3 二叉搜索树自实现

1.3.1 二叉树结点结构

  1. 搜索树结点:由左右孩子结点指针与key组成
  2. 搜索树:由根节点与类方法构成
template<class T>
struct BinarySearchNode
{typedef BinarySearchNode<T> BSNode;BSNode* _left;BSNode* _right;T _key;BinarySearchNode(T key):_left(nullptr),_right(nullptr),_key(key){}
};template<class T>
class BinarySearchTree
{
public:typedef BinarySearchNode<T> BSNode;//查找bool Find(T key);//插入bool Insert(T key);//删除bool Erase(T key);//中序遍历void InOrder();private:BSNode* _root = nullptr;
};

1.3.2 查找

  1. 查找实现:在一颗搜索树中搜寻一个数据是否存在
  2. 查找逻辑:
    <1> 若查找值小于当前结点的key值,向左搜寻,向左孩子查找
    <2> 若查找值大于当前结点的key值,向右搜寻,向右孩子查找
    <3> 查找遍历至空结点,则证明搜索树中不存在此值
//非递归
bool Find(T key)
{BSNode* cur = _root;while (cur){if (key < cur->_key){cur = cur->_left;}else if (key > cur->_key){cur = cur->_right;}else{return true;}}return false;
}//递归
bool _FindR(BSNode* cur, T key)
{if (cur == nullptr){return false;}if (key < cur->_key){return _FindR(cur->_left, key);}else if (key > cur->_key){return _FindR(cur->_right, key);}else{return true;}//补全返回路径assert(true);return -1;
}bool FindR(T key)
{return _FindR(_root, key);
}

1.3.3 插入

  1. 搜寻插入位置:
    <1> key小向左
    <2> key大向右
    <3> 直至遍历到空
  2. <1> 若遇key相同的结点,停止插入,返回false
    <2> 遍历至空,创建结点,链接结点
//非递归
bool Insert(T key)
{//特殊处理根结点插入if (_root == nullptr){_root = new BSNode(key);return true;}BSNode* cur = _root;BSNode* parent = nullptr;while (cur){if (key < cur->_key){parent = cur;cur = cur->_left}else if (key > cur->_key){parent = cur;cur = cur->_right;}else{return false;}}//确定插入位置if (key < parent->_key){parent->_left = new BSNode(key);}else{parent->_right = new BSNode(key);}return true;
}//递归
bool _InsertR(BSNode*& cur, T key)
{//存在单子树的情况,导致段错误//传引用if (cur == nullptr){cur = new BSNode(key);return true;}if (key < cur->_key){return _InsertR(cur->_left, key);}else if (key > cur->_key){return _InsertR(cur->_right, key);}else{return false;}assert(true);return false;
}bool InsertR(T key)
{if (_root == nullptr){_root = new BSNode(key);return true;}return _InsertR(_root, key);
}

1.3.4 删除

  1. 删除结点后,不能破坏搜索树的结构
  2. 删除不同类型结点的场景,删除逻辑:
    <1> 叶子结点,直接删除,父节点链接指针置空
    <2> 单子树结点,托孤,即,将自己的子树链接给父结点
    <3> 双子树结点,寻找key值最接近的结点(左子树的最右结点,右子树的最左结点),值替换被删除的结点,而后删除被替换的结点
  1. 叶子结点
    在这里插入图片描述
  2. 单子树结点
    在这里插入图片描述
  3. 双子树结点
    在这里插入图片描述
//非递归
bool Erase(T key)
{BSNode* cur = _root;BSNode* parent = nullptr;while (cur){//查找到删除结点的位置if (key < cur->_key){parent = cur;cur = cur->_left;}else if (key > cur->_key){parent = cur;cur = cur->_right;}else//找到了{//叶子结点与单子树结点的删除//可以合并共用一套逻辑if (cur->_left == nullptr || cur->_right == nullptr)//叶子结点/单子树{//根结点特殊处理if (cur == _root){if (cur->_left){_root = cur->_left;}else{_root = cur->_right;}delete cur;}else//普通结点{//托孤if (cur->_left)//左单子树{if (cur == parent->_left){parent->_left = cur->_left;}else{parent->_right = cur->_left;}delete cur;}else//右单子树/叶子节点{if (cur == parent->_left){parent->_left = cur->_right;}else{parent->_right = cur->_right;}delete cur;}}return true;}else//双子树{//寻找右子树的最左结点BSNode* RightParent = cur;BSNode* RightMin = cur->_right;while (RightMin->_left){RightParent = RightMin;RightMin = RightMin->_left;}//值替换cur->_key = RightMin->_key;//删除被替换结点//删除结点的左孩子一定为空,所以按照右子树结点处理//托孤if (RightMin == RightParent->_left){RightParent->_left = RightMin->_right;}else{RightParent->_right = RightMin->_right;}//删除delete RightMin;return true;}}}//未找到return false;
}//递归
bool _EraseR(BSNode*& cur, T key)
{if (cur == nullptr){return false;}//寻找删除结点if (key < cur->_key){return _EraseR(cur->_left, key);}else if (key > cur->_key){return _EraseR(cur->_right, key);}else//找到了{//使用传引用后,对操作的优化if (cur->_left == nullptr || cur->_right == nullptr)//叶子结点/单子树结点{BSNode* del = cur;if (cur->_left){cur = cur->_left;}else{cur = cur->_right;}delete del;return true;}else//双子树{//寻找右子树的最左结点BSNode* RightMin = cur->_right;while (RightMin->_left){RightMin = RightMin->_left;}//值替换cur->_key = RightMin->_key;//值替换后,删除结点传值改变//转化为单子树删除,调用单子树删除处理_EraseR(cur->_right, cur->_key);}}assert(true);return false;
}

1.3.5 中序遍历

void _InOrder(BSNode* cur)
{if (cur == nullptr){return;}//左根右_InOrder(cur->_left);cout << cur->_key << " ";_InOrder(cur->_right);
}void InOrder()
{_InOrder(_root);cout << endl;
}

2. 二叉树进阶相关练习

2.1 根据二叉树创建字符串

  1. 题目信息:
    在这里插入图片描述
  2. 题目连接:
    根据二叉树创建字符串
class Solution 
{
public:void _tree2str(string& str, TreeNode* cur){if(cur == nullptr){return;}//字符串转字符串stoi(整形),stod(浮点型)str += to_string(cur->val);//除左空右不空的情况外,其余省略if(cur->left || cur->right){str += "(";_tree2str(str, cur->left);str += ")";}if(cur->right){str += "(";_tree2str(str, cur->right);str += ")";}}string tree2str(TreeNode* root) {string ret;//递归_tree2str(ret, root);return ret;}
};

2.2 二叉树的层序遍历I

  1. 题目信息:
    在这里插入图片描述
  2. 题目连接:
    二叉树的层序遍历I
  3. 思路:levelsize计数
class Solution
{
public:vector<vector<int>> levelOrder(TreeNode* root){//levelsize计数vector<vector<int>> ret;queue<TreeNode*> q;int levelsize = 1;q.push(root);//可能为空树if(root == nullptr){return ret;}while (!q.empty()){vector<int> count;while (levelsize--){//记录TreeNode* top = q.front();q.pop();count.push_back(top->val);//插入新结点if (top->left)q.push(top->left);if (top->right)q.push(top->right);}levelsize = q.size();ret.push_back(count);}return ret;}
};

2.3 二叉树层序遍历II

  1. 题目信息:
    在这里插入图片描述
  2. 题目连接:
    二叉树层序遍历II
  3. 思路:反转自上至下的层序遍历
class Solution 
{
public:vector<vector<int>> levelOrderBottom(TreeNode* root) {//全部压栈,无法判断层//得出自上至下的序列,然后逆置vector<vector<int>> ret;if(root == nullptr){return ret;}queue<TreeNode*> q;int levelsize = 1;q.push(root);while(!q.empty()){vector<int> count;while(levelsize--){TreeNode* cur = q.front();q.pop();count.push_back(cur->val);if(cur->left)q.push(cur->left);if(cur->right)q.push(cur->right);}levelsize = q.size();ret.push_back(count);}reverse(ret.begin(), ret.end());return ret;}
};

2.4 二叉树最近公共祖先结点

  1. 题目信息:
    在这里插入图片描述
  2. 二叉树最近公共祖先结点
  3. 思路:
    <1> 方法1:p与q一定分别在祖先结点的左侧与右侧,祖先结点可能是自己
    <2> 方法2:栈记录遍历路径,路径上的所有祖先结点
//方法1:
class Solution 
{
public:bool SearchNode(TreeNode* cur, TreeNode* search){if(cur == nullptr){return false;}if(cur == search){return true;}return SearchNode(cur->left, search) || SearchNode(cur->right, search);}TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {bool qInleft = false;bool qInright = false;bool pInleft = false;bool pInright = false;TreeNode* cur = root;while(cur){//可能最近祖先结点是自己if(cur == p){pInleft = pInright = true;}else if(SearchNode(cur->left, p)){pInleft = true;pInright = false;}else{pInright = true;pInleft = false;}if(cur == q){qInleft = qInright = true;}else if(SearchNode(cur->left, q)){qInleft = true;qInright = false;}else{qInright = true;qInleft = false;}if((pInleft && qInright) || (pInright && qInleft))break;else if(pInleft && qInleft)cur = cur->left;elsecur = cur->right;}return cur;}
};//方法2:
class Solution 
{
public:bool PreOrder(TreeNode* cur, TreeNode* search, stack<TreeNode*>& count){if(cur == nullptr){return false;}count.push(cur);if(cur == search){return true;}if(!PreOrder(cur->left, search, count) && !PreOrder(cur->right, search, count)){count.pop();}else{return true;}assert(true);return false;}TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {//栈记录遍历路径,所有祖先结点//到达指定结点的路径只有一条//深度优先遍历stack<TreeNode*> p_st;stack<TreeNode*> q_st;PreOrder(root, p, p_st);PreOrder(root, q, q_st);//路径上的相交结点while(p_st.size() != q_st.size()){if(p_st.size() > q_st.size())p_st.pop();elseq_st.pop();}while(p_st.top() != q_st.top()){p_st.pop();q_st.pop();}return p_st.top();}
};

2.5 二叉树搜索与双向链表

  1. 题目信息:
    在这里插入图片描述
  2. 题目链接:
    二叉搜索树与双向链表
  3. 思路:记录前置结点,中序遍历,注意调整指针链接的时机
class Solution 
{
public:void InOrder(TreeNode* cur, TreeNode*& pre){if(cur == nullptr){return;}InOrder(cur->left, pre);cur->left = pre;if(pre)pre->right = cur;pre = cur;InOrder(cur->right, pre);}TreeNode* Convert(TreeNode* pRootOfTree) {if(pRootOfTree == nullptr){return nullptr;}TreeNode* pre = nullptr;InOrder(pRootOfTree, pre);while(pRootOfTree->left){pRootOfTree = pRootOfTree->left;}return pRootOfTree;}
};

2.6 从前序与中序遍历序列构造二叉树

  1. 题目信息:
    在这里插入图片描述
  2. 题目链接:
    从前序与中序遍历序列构造二叉树
  3. 思路:根据根分割出左右子树,区域分割,引用
class Solution 
{
public:TreeNode* buildNode(vector<int>& preorder, vector<int>& inorder, int& i, int left, int right){if(left > right){return nullptr;}//在中序中找到需构建结点int j = 0;while(inorder[j] != preorder[i]){j++;}//根左右//构建TreeNode* newnode = new TreeNode(preorder[i++]);//分割域,判断,构建左孩子newnode->left = buildNode(preorder, inorder, i, left, j - 1);//分割域,判断,构建右孩子newnode->right = buildNode(preorder, inorder, i, j + 1, right);return newnode;}TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {int i = 0;return buildNode(preorder, inorder, i, 0, inorder.size() - 1);}
};

2.7 从中序与后序遍历序列构造二叉树

  1. 题目信息:
    在这里插入图片描述
  2. 题目链接:
    从中序与后序遍历序列构造二叉树
  3. 思路:区域分割判断,引用,逆向遍历,根右左
class Solution 
{
public:TreeNode* buildNode(vector<int>& inorder, vector<int>& postorder, int& i, int left, int right){if(left > right){return nullptr;}int j = 0;while(inorder[j] != postorder[i]){j++;}TreeNode* newnode = new TreeNode(postorder[i--]);newnode->right = buildNode(inorder, postorder, i, j + 1, right);newnode->left = buildNode(inorder, postorder, i, left, j - 1);return newnode;}TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {//逆向遍历,根右左int i = postorder.size() - 1;return buildNode(inorder, postorder, i, 0, postorder.size() - 1);}
};

2.8 二叉树前序遍历(非递归)

  1. 题目信息:
    在这里插入图片描述
  2. 题目链接:
    二叉树前序遍历
  3. 思路:根左右,插入右子树时就将根删除,栈记录
class Solution 
{
public:vector<int> preorderTraversal(TreeNode* root) {//非递归,前序遍历,根左右vector<int> ret;TreeNode* cur = root;stack<TreeNode*> st;while(cur || !st.empty()){while(cur){st.push(cur);ret.push_back(cur->val);cur = cur->left;}TreeNode* top = st.top();st.pop();cur = top->right;}return ret;}
};

2.9 二叉树中序遍历(非递归)

  1. 题目信息:
    在这里插入图片描述
  2. 题目链接:
    二叉树中序遍历
  3. 思路:插入时机,删除时插入,左右根
class Solution {
public:vector<int> inorderTraversal(TreeNode* root) {//中序,左右根vector<int> ret;stack<TreeNode*> st;TreeNode* cur = root;//插入时机,删除时插入while(cur || !st.empty()){while(cur){st.push(cur);cur = cur->left;}TreeNode* top = st.top();st.pop();ret.push_back(top->val);cur = top->right;}return ret;}
};

!!!3.10 二叉树后序遍历(非递归)

  1. 题目信息:
    在这里插入图片描述
  2. 题目链接:
    二叉树后续遍历
  3. 思路:二次遍历时删除,删除时插入,何时删除
class Solution 
{
public:vector<int> postorderTraversal(TreeNode* root) {//左右根vector<int> ret;stack<TreeNode*> st;TreeNode* cur = root;TreeNode* pre = nullptr;while(cur || !st.empty()){//左while(cur){st.push(cur);cur = cur->left;}//根TreeNode* top = st.top();//cur = st.top();if(top->right == nullptr || pre == top->right)//if(cur->right == nullptr || pre == cur->right){st.pop();pre = top;//pre = cur;ret.push_back(top->val);//ret.push_back(cur->val);}else{//右,死循环cur = top->right;//cur调整错误//cur = cur->right;}}return ret;}
};

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

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

相关文章

用Cmake编译程序时,链接到FFmpeg库

用Cmake编译程序时&#xff0c;链接到FFmpeg库 一、前言 可喜可贺&#xff0c;折腾了一晚上终于把这个勾八链接成功了&#xff0c;已经要吐了。看到下面控制台的输出&#xff0c;吾心甚慰呀&#x1f62d; [100%] Linking CXX executable rknn_yolov5_demo [100%] Built targe…

lesson03:类和对象(中)

1.类的6个默认的成员函数 2.构造函数 3.析构函数 4.拷贝构造函数 1.类的6个默认的成员函数 空类&#xff08;类中一个成员都没没有&#xff09;会有成员函数吗&#xff1f; 其实是有的&#xff01;如果我们在类中什么都不写&#xff0c;编译器会自动生成6个默认成员函数&a…

浏览器渲染流程中的 9 个面试点

记得 08 年以前&#xff0c;打开网页的时候一个页面卡死整个浏览器凉凉。 这是因为当时浏览器是单进程架构&#xff0c;一个页面或者插件卡死&#xff0c;整个浏览器都会崩溃&#xff0c;非常影响用户体验。 经过了一代代工程师的设计&#xff0c;现代浏览器改成了多进程架构&…

【BUG】前端|GET _MG_0001.JPG 404 (Not Found),hexo博客搭建过程图片路径正确却找不到图片

我的问题 我查了好多资料&#xff0c;结果原因是图片名称开头是_则该文件会被忽略。。。我注意到网上并没有提到这个问题&#xff0c;遂补了一下这篇博客并且汇总了我找到的所有解决办法。 具体检查方式&#xff1a; hexo生成一下静态资源&#xff1a; hexo g会发现这张图片…

深入了解PBKDF2:密码学中的关键推导函数

title: 深入了解PBKDF2&#xff1a;密码学中的关键推导函数 date: 2024/4/20 20:37:35 updated: 2024/4/20 20:37:35 tags: 密码学对称加密哈希函数KDFPBKDF2安全密钥派生 第一章&#xff1a;密码学基础 对称加密和哈希函数 对称加密&#xff1a;对称加密是一种加密技术&…

【Pytorch】Yolov5中CPU转GPU过程报错完善留档归纳

Yolov5 从CPU转GPU Python多版本切换 Conda包处理 文章目录 Yolov5 从CPU转GPU Python多版本切换 Conda包处理1.Pytorch套件中存在版本不匹配2.numpy停留在3.8没跟上pytorch2.2.23.ModuleNotFoundError: No module named pandas._libs.interval4.ImportError: cannot imp…

【Ubuntu20.04+Noetic】UR5e+Gazebo+Moveit

环境准备 创建工作空间 mkdir -p ur5e_ws/src cd ur5e_ws/srcUR机械臂软件包 UR官方没更新最新的noetic的分支,因此安装melodic,并需要改动相关文件。 安装UR的模型配置包,包里面有UR模型文件,moveit配置等: cd ~/ur5e_ws/src git clone -b melodic-devel https://git…

SQLite轻量级会话扩展(三十四)

返回&#xff1a;SQLite—系列文章目录 上一篇&#xff1a;SQLite R*Tree 模块&#xff08;三十三&#xff09; 下一篇&#xff1a;SQLite—系列文章目录 1. 引言 会话扩展提供了一种方便记录的机制 对 SQLite 数据库中某些表的部分或全部更改&#xff0c;以及 将这些…

R语言中的execl数据转plink

文章目录 带出外部连接的方式添加列的方式从列表中选出对应的数据信息查看变量信息没有成功 带出外部连接的方式 点击这个黄色的按钮就可以弹出外部链接的方式 添加列的方式 创建一个数据框的方式 我们创建一个三行三列的数据方式 df <- data.frame(name c("Alice&…

拓展网络技能:利用lua-http库下载www.linkedin.com信息的方法

引言 在当今的数字时代&#xff0c;网络技能的重要性日益凸显。本文将介绍如何使用Lua语言和lua-http库来下载和提取LinkedIn网站的信息&#xff0c;这是一种扩展网络技能的有效方法。 背景介绍 在当今科技潮流中&#xff0c;Lua语言以其轻量级和高效的特性&#xff0c;不仅…

Swift-25-普通函数、闭包函数与Lamda表达式编程

函数 语法定义 先来看下swift中函数的定义&#xff0c;函数用关键字func来指定&#xff0c;语法相对复杂一点&#xff0c;主要有下列4种基本情况&#xff0c;还有比较复杂的&#xff0c;会在后续详细讲解。 无参函数定义 有参函数定义 一个简单的函数和函数调用示例如下&…

打印机扫描到共享文件夹教程(Win系统和Mac系统)

一&#xff0e;Windows系统扫描文件到共享文件夹。 1.同时按下键盘WinR键&#xff0c;输入control&#xff0c;点击确定。 2.点击类别&#xff0c;点击大图标&#xff0c;点击凭据管理器。 3.点击Windows凭据&#xff0c;点击添加Windows凭据。 4.internet地址或网络地址&…