题目:501.二叉搜索树中的插入操作
自己有一定的思路,但是遇到的难点:
可以找到val应该插入的位置,但如何建立新节点的结构呢?也就是说新建立的node应该怎么指,是搭建左孩子还是右孩子?又是应该被pre所指还是被root所指呢?
写出了如下代码:
class Solution {
public:
TreeNode *pre = NULL;
TreeNode* insertIntoBST(TreeNode* root, int val) {
if(root == NULL) return NULL;
TreeNode *left = insertIntoBST(root->left, val);
if(pre == NULL){
if(root->val > val){
TreeNode *node = new TreeNode(val);
root->left = node;
return root;
}else{
pre = root;
}
}else{
if(root->val > val && pre->val < val){
TreeNode *node = new TreeNode(val);
root->left = node;
return root;
}else{
pre = root;
}
}
TreeNode *right = insertIntoBST(root->right, val);
if(left != NULL && right == NULL) return left;
if(left == NULL && right != NULL) return right;
return NULL;
}
};
本题最简单的思路:
不用考虑题干所说改变二叉树结构、插入位置有多种情况那么复杂,将所插入的值都插入在最底层,即最底层一定可以找到满足条件的位置。
按照二叉树的特性进行向下搜索递归,一直到最低点,此时建立新节点,并将建立的节点返回,这是终止条件。
对于单层递归逻辑,应该如所示进行判断,按照大小进行搜索,每一层将节点返回。
有一个难以理解的地方,返回的节点应该用什么接住?
此时可以用最底层的返回值来模拟一下:新建立的节点被返回了,应该接在上一层的root->left或者root->right,即返回给root->left或者root->right。
代码如下:
class Solution {
public:
TreeNode* insertIntoBST(TreeNode* root, int val) {
if(root == NULL){
TreeNode *node = new TreeNode(val);
return node;
}
if(root->val > val){
root->left = insertIntoBST(root->left, val);
return root;
}
if(root->val < val){
root->right = insertIntoBST(root->right, val);
return root;
}
return root;
}
};
可看出本题的遍历顺序其实不属于前、中或者后,而是根据搜索二叉树的特性向下搜索到目标位置。
450.删除二叉搜索树中的节点(难)
删除节点要考虑五种情况:
1.没有找到需要删除的节点。
2.需要删除的节点是叶子节点,左为空右也为空。
3.需要删除的节点,其左不为空右为空。
4.需要删除的节点,其左为空右不为空。
5.需要删除的节点其左右都不为空。(此种情况最难,需要大幅改动二叉树结构)
终止条件比较复杂(因为删除节点的操作就在终止条件里):
①如果遍历到NULL,说明本条路径没有找到目标节点,返回 NULL
如果找到需要删除的节点(key):
②判断是不是叶子节点。return NULL,一定要理解为什么要return NULL?其实是将NULL返回给上一层的子树。
③如果左不为空右为空,返回该节点的子树,同样的,该节点的子树返回给了哪里?答:赋值给了上一层。
④左为空右不为空同理。
⑤左右都不为空:要找到最接近待删除节点那个数的数(比key大一点点或小一点点任选其一),定义一个cur作为指针去遍历到最接近待删除节点那个数的数,然后改动树的结构(详见代码)。最后记得释放内存。
单层递归逻辑:
利用二叉树的特性,向下遍历,注意递归的返回值要用什么接住?
注意:在自己写代码时很容易在最最最后,即单层递归之后忘写返回值,有时也不知道该返回什么。像本题就应该返回root(其实我个人理解,在出第一层之外之不可能执行最后这个return的,必定都会在上面的语句中都返回了。这一句话主要是为了保证第一层函数有返回值,就是说执行完所有的递归完成了删除节点的操作后,会将新二叉树的根节点返回。不然会报错:这个函数没有返回值)(前面说的有问题,每一层递归(除了判断终止条件的时候)都会执行这个return语句,将重新构造的树的root返回给上一层)
class Solution {
public:
TreeNode* deleteNode(TreeNode* root, int key) {
if(root == NULL) return NULL; // 第一种情况:没找到删除的节点,遍历到空节点直接返回了
if(root->val == key){
// 第二种情况:左右孩子都为空(叶子节点),直接删除节点, 返回NULL为根节点
if(root->left == NULL && root->right == NULL){
delete root;
return NULL;
}
// 第三种情况:其左孩子不为空,右孩子为空,删除节点,右孩子补位 ,返回右孩子为根节点
else if(root->left != NULL && root->right == NULL){
auto retNode = root->left;
delete root;
return retNode;
}
// 第三种情况:其左孩子为空,右孩子不为空,删除节点,右孩子补位 ,返回右孩子为根节点
else if(root->left == NULL && root->right != NULL){
auto retNode = root->right;
delete root;
return retNode;
}
// 第五种情况:左右孩子节点都不为空,则将删除节点的左子树放到删除节点的右子树的最左面节点的左孩子的位置
// 并返回删除节点右孩子为新的根节点。
else{
TreeNode *cur = root->right; //现在要将root删除,企图将root->left接在右子树的下面
while(cur->left) cur = cur->left; //如果cur->left还存在,说明此时cur还不是比root大且理root最近的节点
cur->left = root->left; // 把要删除的节点(root)左子树放在cur的左孩子的位置
TreeNode *tmp = root; // 把root节点保存一下,下面来删除
root = root->right; // 返回旧root的右孩子作为新root
delete tmp; // 释放节点内存(这里不写也可以,但C++最好手动释放一下吧)
return root;
}
}
if(root->val > key) root->left = deleteNode(root->left, key); //deleteNode函数有返回值,其返回值需要用root->left接住
if(root->val < key) root->right = deleteNode(root->right, key);
return root;
}
};