1. 问题描述:
请设计一个算法,寻找二叉树中指定结点的下一个结点(即中序遍历的后继),给定树的根结点指针TreeNode* root和结点的值int p,请返回值为p的结点的后继结点的值,保证结点的值大于等于零小于等于100000且没有重复值,若不存在后继返回-1
2. 对于一棵二叉搜索树的节点的定义一般是具有parent指针的,像下面这样:
public class TreeNode<T> {
public T val;
public TreeNode<T> left = null;
public TreeNode<T> right = null;
public TreeNode<T> parent = null;
public TreeNode(T val) {
this.val = val;
}
@Override
public String toString() {
return "TreeNode [val=" + val + "]";
}
}
① 所以对于具有parent指针的二叉搜索树,我们求解后继一般是采用下面的方法:
对于给定的节点首先去寻找它的右孩子,假如有右孩子那么找到右孩子中最小的那个节点,假如没有右孩子那么需要往上去寻找第一个不是左孩子的节点,返回当前节点的父节点即可
public K succecssor(K key) {
//寻找出现拐点的情况那么就是我们需要找的后继
//分为两种情况一种是有右孩子那么直接返回右孩子即可
BSTNode<K, V> node = lookUpNode(key);
if(node == null) return null;
if(node.right != null){
return minNode(node.right).key;
}else{
node = node.parent;
//没有右子树那么需要往上去找
while(node != null && !node.isLeftChild){
node = node.parent;
}
if(node != null){
return node.key;
}
}
return null;
}
② 对于这个问题我们需要思考一下,假如给出的树节点的定义如果没有parent指针,那么我们就不能够使用上面的方法来进行处理
以下面的二叉搜索树为例:
10
7 14
1 9 13
11
对于遍历一棵二叉搜索树我们经常使用到的遍历方式就有中序遍历,所以我们可以采用中序遍历来访问其中的节点并且使用中序遍历的时候当调用左子树的函数返回的时候,返回的层的当前节点是以左---中----右的形式来进行返回的
inOrder(node.left, p);
比如像上面的每一层范湖之后返回的节点是左---中----右的形式
这样我们就可以使用一个全局变量来记录每一层返回的当前节点的值并且与给出的目标值进行比较看是否相等,相等了说明找到了后继了,这个时候要加上一个if判断因为调用是往下执行的,调用完之后会层层返回假如不进行if判断进行return返回的时候那么后继会更新很多次这样就错了,因为相等之后当前的节点就是所要求的后继,不需要层层返回进行更新了,直接return了,那么后面的代码就不会执行了
static int successor = -1;
if(successor != -1){
return;
}
而且找到相等节点之后那么应该更新successor的值,因为不需要再次执行下面的代码进行更新了,所以在找到相等的节点之后更新后继直接return即可,这两个return是一定要加上的
我们知道假如一个节点没有后继那么它在调用返回的时候只会返回到当前层的节点,不会再往上或者往下继续调用了这样当前节点就没有后继了,画出上面的二叉树理解一下就知道了,像上面的14就没有后继,因为它始终找不到与14相等的节点当返回14节点这一层的时候全局变量记录的是13这个节点的节点值但是这个时候已经调用完了所以找不到后继了返回-1
代码如下:
public class Main {
public static void main(String[] args) {
TreeNode<Integer> root = new TreeNode<Integer>(10);
TreeNode<Integer> l = new TreeNode<Integer>(7);
TreeNode<Integer> r = new TreeNode<Integer>(14);
TreeNode<Integer> ll = new TreeNode<Integer>(1);
TreeNode<Integer> lr = new TreeNode<Integer>(9);
TreeNode<Integer> rl = new TreeNode<Integer>(13);
TreeNode<Integer> rll = new TreeNode<Integer>(11);
root.left = l;
root.right = r;
l.left = ll;
l.right = lr;
r.left = rl;
rl.left = rll;
//使用中序遍历来记录退回来节点的值
inOrder(root, 1);
System.out.println(successor);
}
static int preValue = Integer.MIN_VALUE;
static int successor = -1;
private static void inOrder(TreeNode<Integer> node, int p) {
if(node == null) return;
inOrder(node.left, p);
if(preValue == p){
if(successor != -1){
return;
}
successor = node.val;
return;
}
preValue = node.val;
inOrder(node.right, p);
}
}
③ 除了使用上面中序遍历记录中间值的递归解法我们还可以采用中序遍历的非递归的形式来进行求解,其中中序遍历的递归使用到的是一个隐式的栈,所以我们借助栈模仿递归的过程写出中序遍历的非递归形式,我们知道中序遍历的非递归形式中出栈元素的顺序就是中序遍历的顺序,这样我们可以在中序遍历的非递归的形式上增加判断将弹出的元素值与给出的目标值进行比较看是否相等,这个时候需要设置一个标志来记录弹出的元素与目标值是否相等,当标志位true弹出的下一个元素的值就是要求的节点的后继
代码如下:
import java.util.Stack;
public class Main {
public static void main(String[] args) {
TreeNode<Integer> root = new TreeNode<Integer>(10);
TreeNode<Integer> l = new TreeNode<Integer>(7);
TreeNode<Integer> r = new TreeNode<Integer>(14);
TreeNode<Integer> ll = new TreeNode<Integer>(1);
TreeNode<Integer> lr = new TreeNode<Integer>(9);
TreeNode<Integer> rl = new TreeNode<Integer>(13);
TreeNode<Integer> rll = new TreeNode<Integer>(11);
root.left = l;
root.right = r;
l.left = ll;
l.right = lr;
r.left = rl;
rl.left = rll;
System.out.println(findSuccessor(root, 14));
}
private static int findSuccessor(TreeNode<Integer> root, int p) {
if(root == null) return -1;
//中序遍历的迭代形式
TreeNode<Integer> c = root;
boolean isFound = false;
Stack<TreeNode<Integer>> stack = new Stack<TreeNode<Integer>>();
while(c != null || !stack.isEmpty()){
while(c != null){
stack.push(c);
c = c.left;
}
if(!stack.isEmpty()){
TreeNode<Integer> poll = stack.pop();
if(isFound){
return poll.val;
}
//栈中弹出来的元素就是中序遍历的顺序
if(poll.val == p){
isFound = true;
}
c = poll.right;
}
}
return -1;
}
}