目录
LeetCode235.二叉搜索树的最近公共祖先
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
思路:之前已经讲过二叉树如何求给定两个结点的最近公共祖先, 对于二叉搜索树来说是通用的,但是二叉搜索树本身是有序的,我们可以根据有序这一特性来进行求解。
这里大体分为递归法和迭代法。
1. 递归法
1.1 普通二叉树
对于普通二叉树是将整棵树都遍历一次,包括左右子树,然后根据左右子树的情况进行逻辑处理,之前讲过,这里不再赘述。
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(root == p || root == q || root == NULL) return root;
TreeNode* left = lowestCommonAncestor(root -> left, p, q);
TreeNode* right = lowestCommonAncestor(root -> right, p, q);
if(left != NULL && right != NULL) return root;
if(left == NULL) return right;
return left;
}
1.2 二叉搜索树
对于二叉搜索树,需要知道,当所遍历的结点的值处于要找的两个结点之间时,该结点必为最近的公共结点。所以我们需要在此时返回即可。
这里不同于一般的二叉树找最近公共结点需要遍历整棵树,这里只要找到了就立即返回,不需要遍历整棵树。
TreeNode* traversal(TreeNode* cur, TreeNode* p, TreeNode* q){
if(cur == NULL) return NULL; //中
if(cur -> val > p -> val && cur ->val > q -> val){//左
//当当前结点的值大于p和q的值时,说明应该向左子树查找
//注意此时并不知道p和q谁大,所以都要比较
TreeNode* left = traversal(cur -> left, p, q);
if(left) return left;//当left不为空,直接返回
}
if(cur -> val < p -> val && cur -> val < q -> val){//右
//同样的道理,当当前结点的值小于p和q的值时,说明应该向右子树查找
TreeNode* right = traversal(cur -> right, p, q);
if(right) return right;//当right不为空,直接返回
}
return cur;//其余情况的话直接返回当前结点即为所找最近公共祖先
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
return traversal(root, p, q);
}
下面的代码是对上面代码的精化。
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;
}
}
2. 迭代法
采用迭代法的思路起始和递归的二叉搜索树的思路一样,只不过实现方式是while循环,不是递归进行的处理。
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
while(root != NULL){
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;
}
LeetCode701.二叉搜索树中的插入操作
给定二叉搜索树(BST)的根节点
root
和要插入树中的值value
,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回 任意有效的结果 。
思路:虽然题目中说可以重构,也就是存在多种结果,只要有效。但是其实没有必要进行重构,只要根据二叉树的有序的特点进行分向,在遇到空结点的时候进行添加就行。这里同样分为递归法和迭代法。
1. 递归法
1.1 无返回值
如果没有返回值,那么注意需要将树进行&引用,否则没有办法将新增结点加入树中。
void insertNode(TreeNode*& cur, int val){//对cur需要引用,否则修改不成功
if(cur == NULL){
TreeNode* node = new TreeNode(val);
cur = node;//创建一个新的结点,使得cur指向新的结点
return;
}
if(cur -> val > val) insertNode(cur -> left, val);
if(cur -> val < val) insertNode(cur -> right, val);
return;
}
TreeNode* insertIntoBST(TreeNode* root, int val) {
insertNode(root, val);
return root;
}
当然也可以使用parent指针指向前一个结点,这样就能够判断新增结点是前一个结点的左孩子还有右孩子。
TreeNode* parent = NULL;//将加入结点的父节点记录下,完成最后的赋值
void insertNode(TreeNode* cur, int val){
if(cur == NULL){
TreeNode* node = new TreeNode(val);
if(parent -> val > val) parent -> left = node;
else parent -> right = node;
return;
}
parent = cur;//指向前一个结点,成为加入结点的父节点
if(cur -> val > val) insertNode(cur -> left, val);
if(cur -> val < val) insertNode(cur -> right, val);
return;
}
TreeNode* insertIntoBST(TreeNode* root, int val) {
if(root == NULL){
root = new TreeNode(val);
return root;
}
insertNode(root, val);
return root;
}
1.2 有返回值
相较于递归没有返回值,有返回值的代码更加简洁,每次递归返回的时候,使用结点的左孩子或右孩子指针接住,即可完成父子结点的融合。
TreeNode* insertIntoBST(TreeNode* root, int val) {
if(root == NULL){
root = new TreeNode(val);
return root;
}
if(root -> val > val) root -> left = insertIntoBST(root -> left, val);
if(root -> val < val) root -> right = insertIntoBST(root -> right, val);
return root;
}
2. 迭代法(双指针法)
迭代法中采用双指针方法,使用parent指针指向新增结点的前一个结点,方便后续添加处理。
TreeNode* insertIntoBST(TreeNode* root, int val) {
TreeNode* node = new TreeNode(val);
if(root == NULL){
root = node;
return root;
}
TreeNode* cur = root;
TreeNode* parent = NULL;//记录父节点位置
while(cur != NULL){
parent = cur;
if(cur -> val > val){
cur = cur -> left;
}else {
cur = cur -> right;
}
}
if(parent ->val > val) parent -> left = node;
else parent -> right = node;
return root;
}
LeetCode450.删除二叉搜索树中的结点
给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
一般来说,删除节点可分为两个步骤:
- 首先找到需要删除的节点;
- 如果找到了,删除它。
思路:删除结点相较于增加结点来说存在一定的难度,因为存在多种处理情况。
这里将其概括为5种情况。
1、树中不存在删除结点
2、树中存在删除结点,左右子树都为空(即根节点)
3、树中存在删除结点,左子树为空,右子树不为空
4、树中存在删除结点,左子树不为空,右子树为空
5、树中存在删除结点,左右子树均不为空
这里我们主要有递归法和迭代法。
1. 递归法
1.1 普通二叉树
对于普通的二叉树来说,这里采用了交换数值的方法来最终删除目标结点,首先当第一次遇到所要删除的结点时,如果其右子树存在,那么找到该右子树的最左结点,将两个结点值进行交换,这是第一次遇到的时候的处理方法;后面再遇到时,只要其右子树不为空,就按照上面的方法继续交换,知道再遇到该删除结点时,其右子树为空了,那么就将左子树返回,其实这时候是将NULL覆盖了要删除的值,并返回了NULL,将其删除了。
TreeNode* deleteNode(TreeNode* root, int key) {
if(root == NULL) return NULL; //未找到结点
if(root -> val == key){
if(root -> right == NULL) return root -> left;
//也就是说,此时该结点不存在右子树了,直接返回其左子树即可
TreeNode* tmp = root -> right;
while(tmp -> left){
tmp = tmp -> left;
}
swap(tmp -> val, root -> val);
//这里是找到该结点的右子树的最左结点,将值进行交换,方便最后进行删除
}
root -> left = deleteNode(root -> left, key);
root -> right = deleteNode(root -> right, key);
return root;
}
1.2 二叉搜索树
对于二叉搜索树来说,因为其有序,所以最后会根据结点大小来定向,也就是说不会遍历整棵树,而是有选择地遍历相关路径的树。
当遇到第一种情况时,直接返回NULL就行;当遇到第二种情况时,也返回NULL,因为是根节点,直接删除即可,删除了就是NULL了;对于第三和第四种情况,一方为空,一方不为空,返回不为空的一方作为新的根节点,然后删除结点即可;对于第五种情况,首先是找到删除结点的右子树,然后找到该右子树的最左结点,将删除结点的左子树整体迁移到该地方,然后返回删除结点的右子树作为新的根节点。以上是当遇到了删除结点时的处理方法。
TreeNode* deleteNode(TreeNode* root, int key) {
//第一种情况,结点不存在
if(root == NULL) return NULL;//未找到相应元素或者说root本身未空
if(root -> val == key){
//第二种情况,结点的左右子树为空
if(root -> left == NULL && root -> right == NULL){
delete root;
return NULL;
}else if(root -> left == NULL){
//第三种情况,结点的左子树为空,右子树不为空
TreeNode* tmp_root = root -> right;
delete root;
return tmp_root;
}else if(root -> right == NULL){
//第四种情况,结点的右子树为空,左子树不为空
TreeNode* tmp_root = root -> left;
delete root;
return tmp_root;
}else{
TreeNode* tmp = root -> right;
while(tmp -> left != NULL){
tmp = tmp -> left;//找到右子树的最左结点位置
}
tmp -> left = root -> left;//将结点的左子树全部转移到右子树的最左结点位置;
TreeNode* tmp_ = root;//准备删除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;
}
2. 迭代法
迭代法的主要思想是和二叉搜索树递归的时候的思路是一样的,这里实现了一个函数deleteOneNode专门处理遇到删除结点时的各种情况,这里对处理情况进行了一个精化。然后在主函数中我们使用了pre指针指向要删除结点的父节点,主要是为了判断删除的是左结点还是右结点,这样方便进行接收。
TreeNode* deleteOneNode(TreeNode* root){
if(root -> right == NULL) return root -> left;
TreeNode* cur = root -> right;
while(cur -> left){
cur = cur -> left;
}
cur -> left = root -> left;
return root -> right;
}
TreeNode* deleteNode(TreeNode* root, int key) {
if(root == NULL) return NULL;
TreeNode* cur = root;
TreeNode* pre = NULL;//设置父节点
while(cur != NULL){
if(cur -> val == key){
break;
}
pre = cur;
if(cur -> val > key) cur = cur -> left;
else cur = cur -> right;
}
if(pre == NULL){
//头节点为需要删除的结点
return deleteOneNode(root);
}
//这里需要清楚删除的是pre的左结点还是右结点
if(pre -> left && pre -> left -> val == key){
pre -> left = deleteOneNode(pre -> left);
}
if(pre -> right && pre -> right -> val == key){
pre -> right = deleteOneNode(pre -> right);
}
return root;
}
感谢你的阅读,希望我的文章能够给你帮助,如果有帮助,麻烦点赞加收藏,或者点点关注,非常感谢。
如果有什么问题欢迎评论区讨论!