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

二叉查找树

 

基本概念

 

二叉查找树插入节点

将某节点 (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 = 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、待删除节点有三种可能的情况:

  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_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;
    }
};

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值