二叉查找树

基本概念

二叉查找树插入节点

将某节点 (insert_node),插入至以node为根的二叉查找树中:

1、如果 insert_node 节点值小于当前node节点值:

  • 如果node有左子树,则递归的将该节点插入至左子树为根的二叉查找树中
  • 否则,将node->left赋值为该节点的地址

2、大于等于时

  • 如果node有右子树,则递归的将该节点插入至右子树为根的二叉查找树中
  • 否则,将node->right赋值为该节点地址

二叉查找树插入节点的复杂度为O(h),h为树的高度。若二叉查找树较为平衡,平均查找复杂度为O(longn)

递归  实现二叉查找树的插入操作

void BST_insert(TreeNode *node, TreeNode *insert_node) {if (inset_node->val < node->val) {if (node->left) {BST_insert(node->left, insert_node);} else {node->left = insert_node;}} else {if (node->right) {BST_insert(node->right, insert_node);} else {node->right = insert_node;}}
}

循环  实现二叉查找树的插入操作

void BST_insert(TreeNode *node, TreeNode *insert_node) {while (node != insert_node) {if (insert_node->val < node->val) {if (!node->left) {node->left = insert_node;}node = node->left;} else {if (!node->right) {node->right = insert_node;}node = node->right;}}
}

二叉查找树查找数值

void BST_search(TreeNode *node, int value) {//当前节点就是value的值if (node->val == value) return true;if (value < node->val) {if (node->left) {return BST_search(node->left, value);} else {return false;}} else {if (node->right) {return BST_search(node->right, value);} else {return false;}}
}

leetcode 108 将有序数组转换为二叉搜索树——二叉查找树的插入例题

将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。

本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。

示例:

给定有序数组: [-10,-3,0,5,9],一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树:0/ \-3   9/   /-10  5

思路:

  • 每次选取数值的中间元素插入二叉查找树,完成选择后将数组划分为左右两个数组,再递归的处理这两个子数组,继续选择数组的中间元素进行处理。
  • 将计算出的元素存入一个数组中,之后依次对数组中的每个元素进行二叉查找树的插入操作,最终会得到高度差不超过1的平衡二叉查找树。
class Solution {
public://将元素插入二叉排序树中对应的位置void BST_insert(TreeNode *node, TreeNode *insert_node) {while (node != insert_node) {if (insert_node->val < node->val) {if (!node->left) {node->left = insert_node;}node = node->left;} else {if (!node->right) {node->right = insert_node;}node = node->right;}}}//获取每个元素在二叉排序树中的插入顺序,将各个元素存入数组中void preorder_insert(const vector<int>&nums, vector<TreeNode*>&node_vec, int begin, int end){if(begin>end) return;int mid=(begin+end)/2;node_vec.push_back(new TreeNode(nums[mid]));preorder_insert(nums, node_vec, begin, mid-1);preorder_insert(nums, node_vec, mid+1, end);}TreeNode* sortedArrayToBST(vector<int>& nums) {if(nums.size()==0) return nullptr;vector<TreeNode*>node_vec;//获取每个元素在二叉排序树中的插入顺序,将各个元素存入数组中preorder_insert(nums, node_vec, 0, nums.size()-1);for(int i=1; i<node_vec.size(); i++){//将元素插入二叉排序树中对应的位置BST_insert(node_vec[0], node_vec[i]);}return node_vec[0];}
};

leetcode 450 删除二叉搜索树中的节点

给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。

一般来说,删除节点可分为两个步骤:

  1. 首先找到需要删除的节点;
  2. 如果找到了,删除它。

说明: 要求算法时间复杂度为 O(h),h 为树的高度。

示例:

root = [5,3,6,2,4,null,7]
key = 35/ \3   6/ \   \
2   4   7给定需要删除的节点值是 3,所以我们首先找到 3 这个节点,然后删除它。一个正确的答案是 [5,4,6,2,null,null,7], 如下图所示。5/ \4   6/     \
2       7另一个正确答案是 [5,2,6,null,4,null,7]。5/ \2   6\   \4   7

分析:

1、在搜索二叉树时,记录带搜索节点的父节点,使用传出参数parent来记录父节点的地址。当函数找到待查找节点时,返回node指针,否则返回空指针。

//获取父节点地址TreeNode *BST_search(TreeNode *node, int value, TreeNode *&parent){while(node){if(node->val==value) break;parent=node; //记录父节点if(value < node->val){node=node->left;}else{node=node->right;}}return node;}

2、待删除节点有三种可能的情况:

  1. 待删除节点存在左子节点和右子节点
  2. 待删除节点只有左子节点或右子节点
  3. 待删除节点是叶节点
  • 当待删除节点只有一个子节点时,直接讲其父节点与其子节点相连即可
  • 当该删除节点是叶节点时,直接将该节点删除即可。
//删除只有一个子节点的节点或者叶节点void delete_node(TreeNode *parent, TreeNode *node){if(node->val < parent->val){if(node->left && !node->right){parent->left=node->left;}else if(!node->left &&node->right){parent->left=node->right;}else{parent->left=nullptr;}}else if(node->val >parent->val){if(node->left &&!node->right){parent->right=node->left;}else if(!node->left && node->right){parent->right=node->right;}else{parent->right=nullptr;}}}
  • 当待删除节点存在两个子树时,则使用该节点的后继节点的值喜欢该节点的值,然后删除该节点的后继。

如何查找后继节点?

后继节点,即大于待查找节点各个节点中值最小的那个节点。若某二叉树节点的右子树非空,则他的后继是右子树中最左侧的节点,后继节点最多有一个子右树。

//查找后继节点TreeNode *find_successor(TreeNode *node, TreeNode *&parent){parent=node;TreeNode *ptr=node->right;while(ptr->left){parent=ptr;ptr=ptr->left;}return ptr;}

思路:

设置存储待删除节点的父节点地址的指针parent=nullptr,设置存储待删除节点的指针名为node。

  1. 查找值为key的解答,查找过程中记录该节点的父节点。若未找到该节点,则返回根节点。
  2. 如果node有两个子树,查找node的后继节点,使用后继节点的值替换node的值,然后删除后继节点,返回根节点。
  3. 如果node只有一个子节点或者node为叶子节点,且node的parent不为空,删除node节点,返回根节点。
  4. 如果parent的值为空,则node为根节点。将根节点设置为他不空的孩子(他此时只可能有一个孩子,因为有两个子节点是情况在 2 时已经考虑过了),若node的孩子均为空,则直接设置根节点的值为空。
class Solution {
public://获取父节点地址TreeNode *BST_search(TreeNode *node, int value, TreeNode *&parent){while(node){if(node->val==value) break;parent=node; //记录父节点if(value < node->val){node=node->left;}else{node=node->right;}}return node;}//删除只有一个子节点的节点或者叶节点void delete_node(TreeNode *parent, TreeNode *node){if(node->val < parent->val){if(node->left && !node->right){parent->left=node->left;}else if(!node->left &&node->right){parent->left=node->right;}else{parent->left=nullptr;}}else if(node->val >parent->val){if(node->left &&!node->right){parent->right=node->left;}else if(!node->left && node->right){parent->right=node->right;}else{parent->right=nullptr;}}}//查找后继节点TreeNode *find_successor(TreeNode *node, TreeNode *&parent){parent=node;TreeNode *ptr=node->right;while(ptr->left){parent=ptr;ptr=ptr->left;}return ptr;}TreeNode* deleteNode(TreeNode* root, int key) {if(root==nullptr) return root;TreeNode *parent=nullptr;//获取父节点地址TreeNode *node=BST_search(root, key, parent);//1 若未找到该节点,则返回根节点if(node==nullptr) return root;//2 待删除节点有左子节点和右子节点if(node->left && node->right){TreeNode *successor=find_successor(node, parent);delete_node(parent, successor);node->val=successor->val;return root;}//3  待删除节点只有一个子节点或者没有子节点,但是他不是根节点时if(parent){delete_node(parent, node);return root;}//4 待删除节点只有一个子节点或者没有子节点,但是他是根节点时if(node->left){root=node->left;}else{root=node->right;}return root;       }
};

leetcode 538 把二叉搜索树转换为累加树

给定一个二叉搜索树(Binary Search Tree),把它转换成为累加树(Greater Tree),使得每个节点的值是原来的节点值加上所有大于它的节点值之和。

例如:

输入: 二叉搜索树:5/   \2     13输出: 转换为累加树:18/   \20     13

思考:

     

思路:

修改中序遍历,使其先遍历右子树,再遍历节点本身,之后遍历左子树。

在中序遍历时,使用变量sum记录遍历过的节点累加和,一边遍历一边修改节点的值为sum。

class Solution {
public:void travel_tree(TreeNode *node, int &sum){if(node==nullptr) return;travel_tree(node->right, sum);sum+=node->val;node->val=sum;travel_tree(node->left, sum);}TreeNode* convertBST(TreeNode* root) {int sum=0;travel_tree(root, sum);return root;}
};

预备知识——二叉查找树的先序遍历与复原

对二叉查找树进行先序遍历,将遍历结果按顺序重新构造为一颗二叉查找树。

#include<vector>
#include<iostream>
using namespace std;struct TreeNode {int val;TreeNode *left;TreeNode *right;TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};void collect_nodes(TreeNode *node, vector<TreeNode *>&node_vec) {if (!node) return;node_vec.push_back(node);collect_nodes(node->left, node_vec);collect_nodes(node->right, node_vec);
}void BST_insert(TreeNode *node, TreeNode *insert_node) {while (node != insert_node) {if (insert_node->val < node->val) {if (!node->left) {node->left = insert_node;}node = node->left;} else {if (!node->right) {node->right = insert_node;}node = node->right;}}
}void preorder_printf(TreeNode *root, int level) {if (root == nullptr) return;for (int i = 0; i < level; i++) {printf("---");}printf("[%d]\n", root->val);preorder_printf(root->left, level+1);preorder_printf(root->right, level + 1);
}
int main(int argc, char *argv[]) {TreeNode a(8);TreeNode b(3);TreeNode c(10);TreeNode d(1);TreeNode e(6);TreeNode f(15);a.left = &b;a.right = &c;b.left = &d;b.right = &e;c.right = &f;vector<TreeNode *>node_vec;collect_nodes(&a, node_vec);for (int i = 0; i < node_vec.size(); i++) {node_vec[i]->left = nullptr;node_vec[i]->right = nullptr;}for (int i = 1; i < node_vec.size(); i++) {BST_insert(node_vec[0], node_vec[i]);}preorder_printf(node_vec[0], 0);return 0;
}

leetcode 449 序列化和反序列化二叉搜索树

序列化是将数据结构或对象转换为一系列位的过程,以便它可以存储在文件或内存缓冲区中,或通过网络连接链路传输,以便稍后在同一个或另一个计算机环境中重建。(protobuf ???

设计一个算法来序列化和反序列化二叉搜索树。 对序列化/反序列化算法的工作方式没有限制。 您只需确保二叉搜索树可以序列化为字符串,并且可以将该字符串反序列化为最初的二叉搜索树。

编码的字符串应尽可能紧凑。

注意:不要使用类成员/全局/静态变量来存储状态。 你的序列化和反序列化算法应该是无状态的。

思路:

编码

  • 先序遍历二叉查找树,遍历时将整型的数据转为字符串,并将这些字符串进行连接,连接时使用特殊符号分隔。

      

  • 对于节点上的值,利用对整数除10取余的方式,将每个整数从低位到高位反转过来。
void change_int_to_string(int val, string &str_val) {string tmp;//对于每个val,将其转为逆序字符串while (val) {tmp += val % 10 + '0';val = val / 10;}for (int i = tmp.length() - 1; i >= 0; i--) {str_val += tmp[i];}//添加分隔符str_val += '#';
}void BST_preorder(TreeNode *node, string &data) {if (!node) return;string str_val;change_int_to_string(node->val, str_val);data += str_val;BST_preorder(node->left, data);BST_preorder(node->right, data);
}

解码

  • 将字符串编码时的以“#”作为分隔符,将各个数字逐个拆分出来。将第一个数字构建成二叉查找树的根节点,后面各个数字构建出的节点按解析式的顺序插入根节点中,返回根节点,即完成了解码操作。

   

int main() {string str = "123#456#10000#1#1#";int val = 0;for (int i = 0; i < str.length(); i++) {if (str[i] == '#') {printf("val=%d\n", val);val = 0;} else {val = val * 10 + str[i] - '0';}}return 0;
}

实现代码:

class Codec {
public://二叉查找树插入节点void BST_insert(TreeNode *node, TreeNode *insert_node) {while (node != insert_node) {if (insert_node->val < node->val) {if (!node->left) {node->left = insert_node;}node = node->left;} else {if (!node->right) {node->right = insert_node;}node = node->right;}}}//将int转成string,123转321#void change_int_to_string(int val, string &str_val) {string tmp;//对于每个val,将其转为逆序字符串while (val) {tmp += val % 10 + '0';val = val / 10;}for (int i = tmp.length() - 1; i >= 0; i--) {str_val += tmp[i];}//添加分隔符str_val += '#';}//先序遍历二叉树节点,并将每个val的值进行反转void BST_preorder(TreeNode *node, string &data) {if (!node) return;string str_val;change_int_to_string(node->val, str_val);data += str_val;BST_preorder(node->left, data);BST_preorder(node->right, data);}// Encodes a tree to a single string.string serialize(TreeNode* root) {string data;BST_preorder(root, data);return data;}// Decodes your encoded data to tree.TreeNode* deserialize(string data) {if(data.length()==0) return nullptr;vector<TreeNode*>node_vec;int val=0;for(int i=0; i<data.length(); i++){if(data[i]=='#'){node_vec.push_back(new TreeNode(val));val=0;} else {val=val*10+data[i]-'0';}}for(int i=1; i<node_vec.size(); i++){BST_insert(node_vec[0],node_vec[i]);}return node_vec[0];}
};

leetcode 315 计算右侧小于当前元素的个数

给定一个整数数组 nums,按要求返回一个新数组 counts。数组 counts 有该性质: counts[i] 的值是  nums[i] 右侧小于 nums[i] 的元素的数量。

示例:

输入: [5,2,6,1]
输出: [2,1,1,0]
解释:
5 的右侧有 2 个更小的元素 (2 和 1).
2 的右侧仅有 1 个更小的元素 (1).
6 的右侧有 1 个更小的元素 (1).
1 的右侧有 0 个更小的元素.

另一种解法在此:分门别类刷leetcode——二分查找与分治算法

思路:

将数组逆置,然后插入二叉查找树中。为了记录当前二叉查找树中有多少个比当前插入元素小的节点,我们自己设置一个树节点的结构体:

struct BSTNode{int val;int count;BSTNode *left;BSTNode *right;BSTNode(int x):val(x),left(nullptr),right(nullptr),count(0){}
};

节点中添加count变量,当带插入节点insert_node小于等于当前node时,count++

struct BSTNode {int val;int count;BSTNode *left;BSTNode *right;BSTNode(int x) :val(x), left(nullptr), right(nullptr), count(0) {}
};void BST_insert(BSTNode *node, BSTNode *insert_node, int &count_small) {if (insert_node->val <= node->val) {node->count++;if (node->left) {BST_insert(node->left, insert_node, count_small);} else {node->left = insert_node;}} else {count_small += node->count + 1;if (node->right) {BST_insert(node->right, insert_node, count_small);} else {node->right = insert_node;}}
}

实现代码:

struct BSTNode{int val;int count;BSTNode *left;BSTNode *right;BSTNode(int x):val(x),left(nullptr),right(nullptr),count(0){}
};class Solution {
public:void BST_insert(BSTNode *node, BSTNode *insert_node, int &count_small){if(insert_node->val <= node->val){node->count++;if(node->left){BST_insert(node->left, insert_node, count_small);}else{node->left=insert_node;}}else{count_small+=node->count+1;if(node->right){BST_insert(node->right, insert_node, count_small);}else{node->right=insert_node;}}}vector<int> countSmaller(vector<int>& nums) {//最终逆序的数组vector<int>result;//创建二叉查找树节点池vector<BSTNode*>node_vec;//从后向前插入过程中,比当前节点值小的count_small数组vector<int>count;for(int i=nums.size()-1; i>=0; i--){node_vec.push_back(new BSTNode(nums[i]));}count.push_back(0);for(int i=1; i<node_vec.size(); i++){int count_small=0;//将第2到第n个节点插入到以第一个节点为根节点的二叉排序树中//插入过程中计算每个节点的count_smallBST_insert(node_vec[0], node_vec[i], count_small);//将count_small数组按照从后向前的顺序push进result数组count.push_back(count_small);}//回收内存for(int i=node_vec.size()-1; i>=0; i--){delete node_vec[i];result.push_back(count[i]);}return result;}
};

分门别类刷leetcode——二叉查找树(C++实现)相关推荐

  1. 分门别类刷leetcode——高级数据结构(字典树,前缀树,trie树,并查集,线段树)

    目录 Trie树(字典树.前缀树)的基础知识 字典树的节点表示 字典树构造的例子 字典树的前序遍历 获取字典树中全部单词 字典树的整体功能 字典树的插入操作 字典树的搜索操作 字典树的前缀查询 字典树 ...

  2. 分门别类刷题总结列表 C++ 实现

     目录 输入输出 leetcode 牛客网 算法训练营 SQL shell编程 零七八碎 买的课 真题 输入输出 1 牛客刷题输入输出总结 2 记录各个七七八八的输入 持续更新中 leetcode 1 ...

  3. 吴忠强:刷LeetCode的正确姿势!

    ↑↑↑关注后"星标"Datawhale 每日干货 & 每月组队学习,不错过 Datawhale干货 作者:吴忠强,东北大学,Datawhale成员 写在前面 最近面试中做算 ...

  4. 惊了,AI已经学会刷LeetCode了!

    点击上方"视学算法",选择加"星标"或"置顶" 重磅干货,第一时间送达 梦晨 发自 凹非寺 量子位 报道 | 公众号 QbitAI 你在面试 ...

  5. 在IDE中刷LeetCode,编码调试一体化,刷题效率直线up!

    点击上方"视学算法",选择加"星标"或"置顶" 重磅干货,第一时间送达 明敏 发自 凹非寺 量子位 报道 | 公众号 QbitAI 还在为刷 ...

  6. 【前端来刷LeetCode】两数之和与两数相加

    大部分玩前端的小伙伴,在算法上都相对要薄弱些,毕竟调样式.调兼容就够掉头发的了,哪还有多余的头发再去折腾. 确实在前端中需要使用到算法的地方是比较少,但若要往高级方向发展,算法的基本功就非常重要啦.对 ...

  7. AI已经会刷LeetCode了

    梦晨 发自 凹非寺 量子位 报道 | 公众号 QbitAI 你在面试中会遇到的那种算法题,AI已经能自己解决了,比如下面这道题: 对于一个记录论文引用次数的数组,每个元素都是非负整数.请写出函数h_i ...

  8. 知乎高赞:985计算机视觉毕业后找不到工作怎么办?怒刷leetcode,还是另寻他路?

    [导读]985研究生,学计算机视觉,出来后找不到工作?新智元带你看看这个70万浏览量问题下的答案干货:找工作难,是因为前两年AI领域泡沫太大.然而,真正的人才什么时候都紧缺,搞扎实自己的基本功比什么都 ...

  9. python刷leetcode_零基础python刷leetcode -- 3. Longest Substring Without Repeating Characters

    算法很重要,但是每天也需要学学python,于是就想用python刷leetcode 的算法题,和我一起开始零基础python刷leetcode之旅吧.如有不对的地方,希望指正,万分感谢~~ 题目 最 ...

  10. 【算法】吴忠强:刷LeetCode的正确姿势!

    作者:吴忠强,东北大学,Datawhale成员 写在前面 最近面试中做算法题,受了打击,和我之前准备的很不一样.这篇文章通过笔试实战经验复盘我在刷LeetCode的一些误区和今后对策,供大家参考备战秋 ...

最新文章

  1. cad缩小了怎么还原_新手必备,CAD常用50个技巧
  2. service和thread的区别,何时用service,何时用thread?
  3. 专属微信二维码python制作_如何利用Python制作简单的公众号二维码关注图
  4. 上帝就在机器里:复杂算法背后隐藏的可怕现实
  5. [UE4] 动画蓝图的 Beginplay 事件不激活的解决办法:换用 Initialize 事件
  6. html 弹出一个邮件连接,mailto scheme 高级用法, 显示带html样式的邮件文本
  7. 《社交网站界面设计(原书第2版)》——3.9 使用生命周期
  8. 关于主函数main(int argc,char *argv[])
  9. Git 学习笔记--git 查看某个文件的修改历史
  10. openresty的html文件夹在,Openresty 配置访问静态文件,拆分路径
  11. json/pickle模块(序列化)
  12. 求数组中最小的k个数
  13. #脚本实现宠物动作行为_短视频剧情创作方法有哪些?爆款短视频的标配,只需88个脚本模板...
  14. redis整理の配置
  15. 长文解读:迟到的Libra与即将到来的Commodity 2.0
  16. CASS10.1软件在windows10中细等线等字体显示不出来的解决方案
  17. python-安居客-郑州二手房销售信息抓取
  18. 我的win10平板维修,重刷系统的经验,windows pe5.0拯救我的平板
  19. 【Educoder作业】※ 数值信息——除二取余
  20. 电信网上营业厅用户自服务系统的设计与实现

热门文章

  1. arp在交换机级联情况下的访问
  2. 捋一捋Unified Language Model Pre-training for Natural Language Understanding and Generation
  3. php支付宝的签名处理,PHP实现RSA签名生成订单功能【支付宝示例】
  4. phaser3场景中的图片缩放scale
  5. 每日数学-三角变换恒等式
  6. 鸿蒙OS到底是不是Android套皮?(少bb,看源码!)
  7. python yield是什么意思_python yield有什么用
  8. Android 10.0在电话拨号盘(Dialer app中)通过暗码进入工厂测试模式
  9. 如何用发票查验软件快速批量查验发票(返回官网查验截图)
  10. 基于单片机的超市储物柜设计_基于单片机的超市储物柜控制系统设计.docx