@Catyee
2021-05-28T22:10:00.000000Z
字数 5546
阅读 425
数据结构与算法
二叉搜索数的一个重要特性:中序遍历是升序的
int result = Integer.MIN_VALUE;
int current = 0;
public int kthSmallest(TreeNode root, int k) {
calKthSmallest(root, k);
return result;
}
public void calKthSmallest(TreeNode root, int k) {
if (root == null || result != Integer.MIN_VALUE) {
return;
}
calKthSmallest(root.left, k);
if (++current == k) {
result = root.val;
}
calKthSmallest(root.right, k);
}
利用中序遍历升序的特点进行遍历,当遍历到第k个节点的时候就是要找的节点,不用再继续遍历了。
给出二叉搜索树的根节点,该树的节点值各不相同,将该树转化为累加树,使新树的每个节点的新值等于原树中大于或等于node.val的值的和。
TreeNode last = null;
public TreeNode convertBST(TreeNode root) {
convert(root);
return root;
}
public void convert(TreeNode root) {
if (root == null) {
return;
}
convert(root.right);
int newVal = root.val;
if (last != null) {
newVal += last.val;
}
root.val = newVal;
last = root;
convert(root.left);
}
下图是leetcode中给出的示例:
由于要处理每个节点的值,每个节点都要访问到,所以总是要进行树的遍历的,树的遍历就只有前序、中序、后序、层次这四种,不妨把遍历顺序和最终结果都写出来找找规律,这里就只写中序遍历的结果了:
节点原值 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
最终值 | 36 | 36 | 35 | 33 | 30 | 26 | 21 | 15 | 8 |
发现了吗?当前节点的最终值是下一个节点的最终值+当前节点的原值,如果我们将中序反过来,也就是说先遍历右子树,然后访问当前节点,再访问左子树,那么当前节点的最终值就是上一个访问的节点的最终值+当前节点的原值,我们只需要一个额外的节点记录上一次访问的节点就可以了。
给定一个二叉树,判断其是否是一个有效的二叉搜索树。
二叉搜索树是这样一棵树,如果左子树不为空,则左子树的所有节点的值都小于根节点,如果右子树不为空,则右子树的所有节点都大于根节点,同时左子树和右子树也都是二叉搜索树。
根据题目的意思,我们很可能将代码写成如下的样子:
public boolean isVaildBST(TreeNode root) {
if (root == null) {
return true;
}
if (root.left != null && root.left.val >= root.val) {
return false;
}
if (root.right != null && root.right.val <= root.val) {
return false;
}
return isVaildBST(root.left) && isVaildBST(root.right);
}
但是要注意!!二叉搜索树的根节点大于所有左子树节点,小于所有右子树节点,并不仅仅是大于左子树的根节点,也不仅仅是小于右子树的根节点。上面这段代码只是比较了根节点和左孩子右孩子的值,这是不够的。比如这样一棵树:
这棵树显然不是一棵二叉搜索树,但是上面的代码将返回true。
正确的做法就是在递归的时候传入子树节点的取值范围来加强约束:
public boolean isVaildBST(TreeNode root) {
return isVaildBST(root, null, null);
}
public boolean isVaildBST(TreeNode root, TreeNode min, TreeNode max) {
if (root == null) {
return true;
}
if (min != null && min.val >= root.val) {
return false;
}
if (max != null && max.val <= root.val) {
return false;
}
return isVaildBST(root.left, min, root) && isVaildBST(root.right, root, max);
}
二叉搜索树的插入操作是比较简单的,因为最终插入的位置总是叶子节点的位置。
public TreeNode insertIntoBST(TreeNode root, int val) {
if (root == null) {
return new TreeNode(val);
}
insert(root, val);
return root;
}
public void insert(TreeNode root, int val) {
if (val < root.val) {
if (root.left != null) {
insert(root.left, val);
} else {
TreeNode node = new TreeNode(val);
root.left = node;
}
} else if (val > root.val) {
if (root.right != null) {
insert(root.right, val);
} else {
TreeNode node = new TreeNode(val);
root.right = node;
}
} else {
throw new RuntimeException("unexpect error");
}
}
删除二叉树的节点要比插入麻烦许多,因为一个节点直接删除之后可能会破坏二叉搜索树的性质,所以要考虑清楚如何删除:
有三种情况:
三种情况分析完了之后我们看"删除节点"这个行为,如果"被删除的节点"有父节点,则需要将父节点原来指向"被删除节点"的指针指向替代它的节点,同时将"被删除节点"的左右孩子置为空,还要保证替代它的节点左右指针的指向也要正确。如果被删除的节点没有父节点,则说明被删除的节点就是根节点。
小技巧:在上面第三种情况中:"删除节点,然后用左子树的最大值或者右子树的最小值代替它的位置"可以进一步简化,这里以左子树的最大值为例,只要把左子树的最大值赋给"待删除的节点",然后删掉左子树最大值所在的节点,由于左子树最大值所在的节点要么是叶子节点,要么是右孩子为空的节点,所以第三种情况转化为了前两种情况。
代码:
/**
* deleteNode方法的定义:删除二叉搜索树中的一个节点,然后返回删除之后树的根节点
* 要记住这个定义,递归的时候不要陷入递归栈中,而应该把握递归方法的定义,相信这个方法能完成的工作
*/
public TreeNode deleteNode(TreeNode root, int key) {
if (root == null) {
return null;
}
if (key == root.val) { // 相等表示root即使要删除的节点
/* 处理左右孩子有空节点的情况,也就是上面列举的前两种情况 */
if (root.left == null) {
return root.right;
}
if (root.right == null) {
return root.left;
}
/* 处理左右孩子节点都不为空的情况,也就是上面列举的第三种情况 */
TreeNode leftMaxNode = getMaxNode(root.left);
// 把左子树的最大值赋给待删除的节点
root.val = leftMaxNode.val;
// 在左子树中删除左子树最大值所在的节点,相信deleteNode方法的定义,所以返回的是删除之后左子树的根节点,重新连接上
root.left = deleteNode(root.left, leftMaxNode.val);
} else if (key < root.val) {
// 到左子树上去删除,相信deleteNode方法的定义,所以返回的是删除之后左子树的根节点,重新连接上
root.left = deleteNode(root.left, key);
} else if (key > root.val) {
// 到右子树上去删除,相信deleteNode方法的定义,所以返回的是删除之后右子树的根节点,重新连接上
root.right = deleteNode(root.right, key);
}
return root;
}
private TreeNode getMaxNode(TreeNode root) {
while (root.right != null) {
root = root.right;
}
return root;
}
这种实现优雅、巧妙,但是如果真实的开发环境,可能每个节点都有着复杂的值,“赋值操作”反而麻烦,进行引用的操作反而简单,下面提供修改引用的方式:
/**
* deleteNode方法的定义:删除二叉搜索树中的一个节点,然后返回删除之后树的根节点
* 要记住这个定义,递归的时候不要陷入递归栈中,而应该把握递归方法的定义,相信这个方法能完成的工作
*/
public TreeNode deleteNode(TreeNode root, int key) {
if (root == null) {
return null;
}
if (key == root.val) {
/* 处理左右孩子有空节点的情况,也就是上面列举的前两种情况 */
if (root.left == null) {
return root.right;
}
if (root.right == null) {
return root.left;
}
/* 处理左右孩子节点都不为空的情况,也就是上面列举的第三种情况 */
TreeNode leftMaxNode = getMaxNode(root.left);
leftMaxNode.left = deleteNode(root.left, leftMaxNode.val);
leftMaxNode.right = root.right;
root.left = null;
root.right = null;
// 用左子树最大值所在节点替换要删除的节点,所以返回的也是左子树最大值所在节点
return leftMaxNode;
} else if (key < root.val) {
root.left = deleteNode(root.left, key);
} else if (key > root.val) {
root.right = deleteNode(root.right, key);
}
return root;
}
private TreeNode getMaxNode(TreeNode root) {
while (root.right != null) {
root = root.right;
}
return root;
}
非递归的实现方式:
private TreeNode parrent = null;
private TreeNode node = null;
public TreeNode deleteNode(TreeNode root, int key) {
findNode(root, key);
if (node == null) {
return root;
}
TreeNode current = null;
if (node.left != null && node.right != null) {
current = findAndDeleteLeftMax(node.left);
if (current != node.left) {
current.left = node.left;
}
current.right = node.right;
} else {
current = node.left == null ? node.right : node.left;
}
node.left = null;
node.right = null;
if (parrent == null) {
return current;
}
if (parrent.left == node) {
parrent.left = current;
}
if (parrent.right == node) {
parrent.right = current;
}
return root;
}
public TreeNode findAndDeleteLeftMax(TreeNode root) {
TreeNode last = null;
while (root.right != null) {
last = root;
root = root.right;
}
if (last != null) {
last.right = root.left;
root.left = null;
}
return root;
}
public void findNode(TreeNode root, int key) {
if (root == null || node != null) {
return;
}
if (root.val == key) {
node = root;
return;
}
if (key < root.val) {
parrent = root;
findNode(root.left, key);
}
if (key > root.val) {
parrent = root;
findNode(root.right, key);
}
}