代码随想录算法训练营Day22|二叉搜索树的最近公共祖先、二叉搜索树中的插入操作、删除二叉搜索树中的节点

文章详细介绍了在二叉搜索树中查找最近公共祖先的递归和迭代方法,以及如何通过递归和迭代实现插入和删除节点,保持二叉搜索树的性质。
摘要由CSDN通过智能技术生成

二叉搜索树的最近公共祖先

题目:给定二叉搜索树,找到两个指定节点的最近公共祖先。

思路:利用有序性!!!因为有序树,所以如果中间节点是q和p的公共祖先,那么中节点的数组一定是在[p,q]区间的。

即中节点 >p&&中节点<q

或者中节点>q&&中节点<p.

从上到下遍历,第一次遇到的在这个区间内的节点就是p和q的最近公共祖先。不用担心p和q不在同一子树上。

递归法: 

1.确定递归函数返回参数和返回值。参数是节点p和q.返回值是最近公共祖先TreeNode*cur。

2.确定终止条件。如果节点最后遍历为空,就返回。这个条件在本题多余,由题意知一定会找到p和q.

3.确定单层递归的逻辑。寻找区间[p->val, q->val]。两种情况都要判断(p>q,p<q),整棵树都要搜索。第三种情况是,p或q本身就是最近公共祖先。

class Solution{
private:
   TreeNode* traversal(TreeNode* cur,TreeNode* p,TreeNode* q){
      if(cur == NULL)retrun cur;

      if(cur->val > p->val && cur->val > q->val){//第一种情况,遍历左树
        TreeNode* left = traversal(cur->left,p,q);
        if(left != NULL){
            return left;
        }
      }
      if(cur->val < p->val && cur->val < q->val){
        TreeNode* right = traversal(cur->right,p,q);
        if(right != NULL){
            return right;
        }
      }
      return cur;
   }
   public:
       TreeNode* lowestCommonAncestor(TreeNode* root,TreeNode* p,TreeNode* q){
         return traversal(root,p,q);
       }
};

精简:

class Solution{
public:
   TreeNode* lowestCommonAncestor(TreeNode* root,TreeNode* p,TreeNode* q){
      if(root->val > p->val && root->val > q->val){
        return lowestCommonAncestor(root->left,p,q);
      }else if(root->val < p->val && root->val < q->val){
        return lowestCommonAncestor(root->right,p,q);
      }else return root;
   }
};

迭代法:

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        while(root) {
            if (root->val > p->val && root->val > q->val) {
                root = root->left;
            } else if (root->val < p->val && root->val < q->val) {
                root = root->right;
            } else return root;
        }
        return NULL;
    }
};

二叉搜索树中的插入操作

题目:根节点和要插入树中的值,插进二叉搜索树,保存,返回任意有效的二叉搜索树。

思路:

可以不改变树的原有结构,按照二叉搜索树的特点遍历,遇到空节点插入就可以。

递归:

>>确定递归函数参数以及返回值

参数就是根节点指针,以及要插入元素,这里递归函数科尔已有返回值,也可以没有返回值,但是没有返回值,实现会比较麻烦。

有返回值的情况下,可以利用返回值完成新加入节点于其父节点的赋值操作。

递归函数的返回类型为节点类型TreeNode*.

TreeNode* insertIntoBST(TreeNode* root,int val)

>>确定终止条件

终止条件就是找到遍历的节点为null的时候,就是要插入节点的位置了,并把插入的节点返回。

代码如下:

if(root == NULL){

TreeNode* node = new TreeNode(val);

return node;

}

这里把添加的节点返回给上一层,就完成了父子节点的赋值操作了。

>>确定单层递归的逻辑

此时要明确,需要遍历整棵树吗?遍历整棵树是对搜索树的侮辱。搜索树是有方向了,可以根据插入元素的数值,决定递归方向。

if(root->val > val)root->left = insertIntoBST(root->left,val);

if(root->val < val)root->right = insertIntoBST(root->right,val);

return root;

以上是通过递归函数书返回值完成新加入节点的父子关系赋值操作,下一层将加入节点返回,本层用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);
    if(root->val < val)root->right = insertIntoBST(root->right,val);
    return root;
   }
}

不用递归函数返回值,找到插入的节点位置,直接让其父节点指向插入节点,结束递归,也可以。

那么递归函数定义:

TreeNode* parent;//记录遍历节点的父节点

void traversal(TreeNode* cur,int val)

没有返回值,需要记录上一个节点parent,遇到空节点了,就让parent左孩子或者右孩子指向新插入的节点。然后结束递归。

class Solution{
public:
  TreeNode* parent;
  void traversal(TreeNode* cur,int val){
    if(cur == NULL){
        TreeNode* node = new TreeNode(val);
        if(val > parent->val)parent->right = node;
        else parent->left = node;
        return;
    }
    parent = cur;
    if(cur->val > val)traversal(cur->left,val);
    if(cur->val < val)traversal(cur->right,val);
    return;
  }

  public:
     TreeNode* insertIntoBST(TreeNode* root,int val){
        parent = new TreeNode(0);
        if(root == NULL){
            root = new TreeNode(val);
        }
        traversal(root,val);
        return root;
     }
};

对比发现,通过递归函数的返回值来完成父子节点的赋值可以带来便利。

迭代法:在迭代中更需要记录当前遍历的节点的父节点,才能做插入节点的操作。利用记录pre和cur两个指针的技巧。

class Solution{
public:
   TreeNode* insertIntoBST(TreeNode* root,int val){
       if(root == NULL){
        TreeNode* node = new TreeNode(val);
        return node;
       }
       TreeNode* cur = root;
       TreeNode* parent = root;//记录上一节点
       while(cur != NULL){
        parent = cur;
        if(cur->val > val)cur = cur->left;
        else cur = cur->right;
       }
       TreeNode* node = new TreeNode(val);
       if(val < parent->val)parent->left = node;//此时是用parent节点的进行赋值
       else parent->right = node;
       return root;
   }
};

删除二叉搜索树中的节点

题目:给定二叉搜索树的根节点root和一个值key,删除二叉搜索树中的key对应的节点,并保证二叉搜索树的性质不变。返回根节点的引用。

思路:

1.递归

递归函数返回值,类似于增加节点。

TreeNode* deleteNode(TreeNode* root, int key)

确定终止条件

遇到空返回,其实这也说明没找到删除的节点,遍历到空节点直接返回了

if(root == nullptr)return root;

确定单层递归的逻辑

有5种情况:

第一种,没找到删除的节点,遍历到空节点直接返回了,找到删除的节点

第二种,左右孩子都为空(叶子节点),直接删除接节点,返回NULL为根节点。

第三种,第四种,删除节点的左(右)孩子为空,右(左)孩子不为空,删除节点,右(左)孩子补位,返回右(左)孩子为根节点

第五种,左右孩子节点都不为空,则将删除节点的左子树节点(左孩子)放到删除节点的左子树的最左面节点的左孩子上,返回删除节点右孩子为新的根节点。

if(root->val == key){
    if(root->left == nullptr)return root->right;
    else if(root->right == nullptr)return root->left;
    else{
        TreeNode* cur = root->right;
        while(cur->left != nullptr){
            cur = cur->left;
        }
        cur->left = root->left;
        TreeNode* tmp = root;
        root = root->right;
        delete tmp;
        return root;
    }
}

相当于把新节点返回给上一层,上一层就要用root->left或者root->right接住,

if(root->val > key) root->left = deleteNode(root->left,key);

if(root->val < key)root->right = deleteNode(root->right,key);

return root;

class Solution{
public:
   TreeNode* deleteNode(TreeNode* root,int key){
      if(root == nullptr)return root;
      if(root->val == key){
        if(root->left == nullptr && root->right == nullptr){
            delete root;
            return nullptr;
        }
        else if(root->left == nullptr){
            auto retNode = root->right;
            delete root;
            return retNode;
        }
        else if(root->right == nullptr){
            suto retNode = root->left;
            delete root;
            return retNode;
        }
        else{
            TreeNode* cur = root->right;
            while(cur->left != nullptr){
                cur = cur->left;
            }
            cur->left = root->left;
            TreeNode* tmp = root;
            root = root->right;
            delete tmp;
            return root;
        }
      }
      if(root->val > key)root->left = deleteNode(root->left,key);
      if(root->val < key)root->right = deleteNode(root->right,key);
      return root;
   }
};

普通二叉树节点删除

不能利用搜索树的特性,遍历整棵树。用交换值的操作来删除目标节点。

代码中目标节点(要删除的节点)被操作了两次:

第一次是和目标节点的右子树最左面节点交换。

第二次直接被null覆盖了。

class Solution{
public:
   TreeNode* deleteNode(TreeNode* root,int key){
    if(root == nullptr)return root;
    if(root->val == key){
        if(root->right == nullptr){//第二次覆盖
           return root->left;
        }
        TreeNode* cur = root->right;
        while(cur->left){
            cur = cur->left;
        }
        swap(root->val,cur->val);//第一次操作,交换
    }
    root->left = deleteNode(root->left,key);
    root->right = deleteNode(root->right,key);
    return root;
   }
};

迭代法

删除节点的迭代法还是复杂一些的,关键在于删除节点的操作

class Solution{
private:
//将目标节点(删除节点)的左子树放到 目标节点的右子树的最左面节点的左孩子位置上
//返回目标节点右孩子为新的根节点
TreeNode* deleteOneNode(TreeNode* target){
    if(target == nullptr)return target;
    if(target->right == nullptr)return target->left;
    TreeNode* cur = target->right;
    while(cur->left){
        cur = cur->left;
    }
    cur->left = target->left;
    return target->right;
}
public:
   TreeNode* deleteNode(TreeNode* root,int key){
    if(root == nullptr)return root;
    TreeNode* cur = root;
    TreeNode* pre = nullptr;
    while(cur){
        if(cur->val == key)break;
        pre = cur;
        if(cur->val > key)cur = cur->left;
        else cur = cur->right;
    }
    if(pre == nullptr){
        return deleteOneNode(cur);
    }
    if(pre->left && pre->left->val == key){
        pre->left = deleteOneNode(cur);
    }
    if(pre->right && pre->right->val == key){
        pre->right = deleteOneNode(cur);
    }
    return root;
   }
};

  • 20
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值