二叉查找树
基本概念
二叉查找树插入节点
将某节点 (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 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
一般来说,删除节点可分为两个步骤:
- 首先找到需要删除的节点;
- 如果找到了,删除它。
说明: 要求算法时间复杂度为 O(h),h 为树的高度。
示例:
root = [5,3,6,2,4,null,7]
key = 3
5
/ \
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、待删除节点有三种可能的情况:
- 待删除节点存在左子节点和右子节点
- 待删除节点只有左子节点或右子节点
- 待删除节点是叶节点
- 当待删除节点只有一个子节点时,直接讲其父节点与其子节点相连即可
- 当该删除节点是叶节点时,直接将该节点删除即可。
//删除只有一个子节点的节点或者叶节点
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。
- 查找值为key的解答,查找过程中记录该节点的父节点。若未找到该节点,则返回根节点。
- 如果node有两个子树,查找node的后继节点,使用后继节点的值替换node的值,然后删除后继节点,返回根节点。
- 如果node只有一个子节点或者node为叶子节点,且node的parent不为空,删除node节点,返回根节点。
- 如果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_small
BST_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;
}
};