标题:java实现判断是否为二叉搜索树
一、分析
1)第一种:
对于二叉搜索树,首先想到的的是中序遍历,得到的是一个有序序列。
所以方法可以是使用中序的递归,非递归进行实现,只要看是否为有序即可。
- 方法一:中序非递归遍历,保存最小值为
long cur = Long.MIN_VALUE;
每次遍历得到一个节点的值时,比较是否大于当前最小值cur,满足条件则更新当前最小值。
/**
* 使用非递归实现 迭代版的【非递归中序遍历】,简单,容易实现
* 执行用时:2 ms, 在所有 Java 提交中击败了31.16% 的用户
内存消耗:37.9 MB, 在所有 Java 提交中击败了94.63% 的用户
*/
public boolean isBst02(TreeNode head) {
if(head == null) {
return true;
}
Deque<TreeNode> s = new LinkedList<>();
s.push(head);
long cur = Long.MIN_VALUE;
while(!s.isEmpty()) {
TreeNode node = s.peek();
if(node.left != null) {
s.push(node.left);
}else {
node = s.pop();
System.out.print(node.val + " ");
//判断node是否 > cur
if(node.val <= cur) {
return false;
}
cur = node.val;
while(node.right == null && !s.isEmpty()) {
node = s.pop();
System.out.print(node.val + " ");
//判断node是否 > cur
if(node.val <= cur) {
return false;
}
cur = node.val;
}
if(node.right != null) {
s.push(node.right);
}else {
break;
}
}
}
return true;
}
- 方法二:中序递归遍历:实现一,中序遍历后,将得到的节点存到list中,通过for循环进行遍历list,判断是否为有序的【即总是有后面一个小于前面一个节点的值】
/**
* 使用中序遍历 存到list中,再比较 【简单】
* 执行用时:2 ms, 在所有 Java 提交中击败了31.16% 的用户
内存消耗:38.2 MB, 在所有 Java 提交中击败了79.34% 的用户
*/
public boolean isBst03(TreeNode head, List<Integer> list) {
for(int i = 1; i < list.size(); i++) {
if(list.get(i) <= list.get(i - 1)) {
return false;
}
}
return true;
}
/**
* 中序遍历
*/
public void inOrder(TreeNode head, List<Integer> list) {
if(head == null) {
return ;
}else {
this.inOrder(head.left, list);
System.out.print(head.val + " ");
list.add(head.val);
this.inOrder(head.right, list);
}
}
- 实现三:对中序递归遍历的升级版,使用一个对象保存当前已遍历到的节点的最小值【使用一个内部类,创建一个对象
new Cur(Long.MIN_VALUE))
】,递归遍历完左边节点回退的时候【中序遍历的思想:递归遍历完左边节点回退时,System.out.print(node.val);】,取得当前节点的值,若小于当前最小值,则更新最小值,否则,说明不是有序的,return false;使用对象保存当前节点的最小值,是因为这样的话,递归回退的时候,最小值不会改变(类似 传递参数时count + 1 【count不会变】 与 count ++【count会改变】的差别)
class Cur{
long cur;
public Cur(long cur) {
this.cur = cur;
}
}
/**
* 因为二叉搜索树,中序遍历得到的是有序序列,
* 所以 使用中序遍历,每次比较node节点之间的最小值 与 node,即:cur min【保存node节点之前的最小值】 < node
* 执行用时:0 ms, 在所有 Java 提交中击败了100.00% 的用户
内存消耗:38.1 MB, 在所有 Java 提交中击败了84.19% 的用户
* @param head
* @param cur_min
* @return
*/
public boolean isBst(TreeNode head, Cur cur_min) {
if(head == null) {
return true;
}else {
boolean b1 = this.isBst(head.left, cur_min);
if(!b1) {
return false;
}
if(head.val <= cur_min.cur) {
return false;
}
cur_min.cur = head.val;
boolean b2 = this.isBst(head.right, cur_min);
if(!b1 || !b2) {
return false;
}
return true;
}
}
- 实现四:使用一个成员变量指向当前最小值的节点,思想和上面的实现二 一样,使得递归回退的时候,最小值不会改变。
/**
* 参考
* 注意TreeNode p;为一个成员变量
* 这样下面的程序才可以改变p
* 执行用时:0 ms, 在所有 Java 提交中击败了100.00% 的用户
内存消耗:38 MB, 在所有 Java 提交中击败了89.36% 的用户
* @param head
* @param cur_min
* @return
*/
TreeNode p; //将这个p设置为成员遍历才行,这样下面的程序才可以改变p,不然随着函数的退回,p的值又回到了原来,所以这个的p不能作为形参,而是成员变量
public boolean isBst011(TreeNode head) {
if(head == null) {
return true;
}else {
boolean b1 = this.isBst011(head.left);
if(!b1) {
return false;
}
if(p != null && head.val <= p.val) {
return false;
}
p = head;
boolean b2 = this.isBst011(head.right);
if(!b1 || !b2) {
return false;
}
return true;
}
}
2)第二种:
使用先序递归遍历:每次遍历的时候,使得 XXX < node.val < YYY
- 实现一:每次遍历节点的时候,要求node满足 left最大值 < node.val < right最小值
/**
* 方法二: 【可以看看对称(镜像)二叉树的思想】
* 使用递归求解
* 执行用时:0 ms, 在所有 Java 提交中击败了100.00% 的用户
内存消耗:38.2 MB, 在所有 Java 提交中击败了87.07% 的用户
*/
public boolean isBst03(TreeNode node) {
if(node == null) {
return true;
}else { //理论上 why:因为后面的多次重复过程,
if(node.left != null) {
int max = this.rightMax(node.left);//左边节点的理论上(node.left一直向右)的最大值 < 该节点
if(max >= node.val) {
return false;
}
}
if(node.right != null) {
int min = this.leftMin(node.right);右边节点的理论上(node.right一直向左)的最小值 > 该节点
if(min <= node.val) {
return false;
}
}
boolean res1 = this.isBst03(node.left);
boolean res2 = this.isBst03(node.right);
return res1 && res2;
}
}
/**
* 寻找node节点理论上的最大值 找到node节点的最右边的值
* @param node
* @return
*/
public int rightMax(TreeNode node) {
while(node.right != null) {
node = node.right;
}
return node.val;
}
/**
* 寻找node节点理论上的最小值 找到node节点的最左边的值
* @param node
* @return
*/
public int leftMin(TreeNode node) {
while(node.left != null) {
node = node.left;
}
return node.val;
}
- 实现二:每次遍历节点的时候,要求node满足 node.left.val < node.val < node.right.val 同时 node.left.val > min && node.right.val < max
/**
* 注意:这个题目可以补全二叉树来做,得到 头结点上面的 min,max
* min < left < node.val < right < max
* 使用[先序遍历]
* 每次使node.val > left && node.val < right
* 同时left > min && right < max
*
* 递归
*/
public boolean isBst03(TreeNode head, long max, long min) {
if(head == null) {
return true;
}else {
if(head.left != null) {//节点head 左边的值 < head.val , 同时要 > min
if(head.left.val >= head.val || head.left.val <= min) {
// System.out.println("left:");
// System.out.println(" head.val:" + head.val + " head.left.val:" + head.left.val + " min:" + min);
return false;
}
}
if(head.right != null) { //节点head 右边的值 > head.val, 同时要 < max
if(head.right.val <= head.val || head.right.val >= max) {
// System.out.println("right;");
// System.out.println(" head.val:" + head.val + " head.right.val:" + head.right.val + " max:" + max);
return false;
}
}
boolean b1 = this.isBst03(head.left, head.val, min); //head.val
min = head.val;
boolean b2 = this.isBst03(head.right, max, min);
return b1 && b2;
}
}
/**
* 上面的简化
* 执行用时:0 ms, 在所有 Java 提交中击败了100.00% 的用户
内存消耗:37.9 MB, 在所有 Java 提交中击败了94.21% 的用户
* @param head
* @return
*/
public boolean isValidBST02(TreeNode head) {
return this.isBst03(head, Long.MAX_VALUE, Long.MIN_VALUE);
}
- 实现三:类似实现二的简化版,每次遍历节点的时候,要求node满足 min < node.val < max
/**
* 使用参考 思想,类似上面的
* 使用先序遍历
* min < node.val < max
* 执行用时:0 ms, 在所有 Java 提交中击败了100.00% 的用户
内存消耗:38.2 MB, 在所有 Java 提交中击败了77.43% 的用户
*/
public boolean isBst04(TreeNode head, long max, long min) {
if(head == null) {
return true;
}else {
if(head.val >= max || head.val <= min) {
return false;
}
boolean b1 = this.isBst04(head.left, head.val, min);
boolean b2 = this.isBst04(head.right, max, head.val);
return b1 && b2;
}
}
public boolean isValidBST03(TreeNode head) {
return this.isBst04(head, Long.MAX_VALUE, Long.MIN_VALUE);
}
那么实现二,实现三的min, max怎么确定呢?,见下图
- 补全二叉树,找到min,max的规律【可以看看几个节点,进而确定】
完整实例代码如下;
/**
* 判断是否为二叉搜索树:
* 重点:其一:补全了二叉树的图,得到head的 max, min
* 其二:可以使用成员遍历,及时保存最新的 最小值,不需要传递参数了
* --> 我之前的做法是使用一个内部类,传递参数实现 【一个对象】
*
* @author dell
*
*/
public class TestIsBst {
class Cur{
long cur;
public Cur(long cur) {
this.cur = cur;
}
}
/**
* 因为二叉搜索树,中序遍历得到的是有序序列,
* 所以 使用中序遍历,每次比较node节点之间的最小值 与 node,即:cur min【保存node节点之前的最小值】 < node
* 执行用时:0 ms, 在所有 Java 提交中击败了100.00% 的用户
内存消耗:38.1 MB, 在所有 Java 提交中击败了84.19% 的用户
* @param head
* @param cur_min
* @return
*/
public boolean isBst(TreeNode head, Cur cur_min) {
if(head == null) {
return true;
}else {
boolean b1 = this.isBst(head.left, cur_min);
if(!b1) {
return false;
}
if(head.val <= cur_min.cur) {
return false;
}
cur_min.cur = head.val;
boolean b2 = this.isBst(head.right, cur_min);
if(!b1 || !b2) {
return false;
}
return true;
}
}
/**
* 参考
* 注意TreeNode p;为一个成员变量
* 这样下面的程序才可以改变p
* 执行用时:0 ms, 在所有 Java 提交中击败了100.00% 的用户
内存消耗:38 MB, 在所有 Java 提交中击败了89.36% 的用户
* @param head
* @param cur_min
* @return
*/
TreeNode p; //将这个p设置为成员遍历才行,这样下面的程序才可以改变p,不然随着函数的退回,p的值又回到了原来,所以这个的p不能作为形参,而是成员变量
public boolean isBst011(TreeNode head) {
if(head == null) {
return true;
}else {
boolean b1 = this.isBst011(head.left);
if(!b1) {
return false;
}
if(p != null && head.val <= p.val) {
return false;
}
p = head;
boolean b2 = this.isBst011(head.right);
if(!b1 || !b2) {
return false;
}
return true;
}
}
/**
* 使用非递归实现 迭代版的【非递归中序遍历】,简单,容易实现
* 执行用时:2 ms, 在所有 Java 提交中击败了31.16% 的用户
内存消耗:37.9 MB, 在所有 Java 提交中击败了94.63% 的用户
*/
public boolean isBst02(TreeNode head) {
if(head == null) {
return true;
}
Deque<TreeNode> s = new LinkedList<>();
s.push(head);
long cur = Long.MIN_VALUE;
while(!s.isEmpty()) {
TreeNode node = s.peek();
if(node.left != null) {
s.push(node.left);
}else {
node = s.pop();
System.out.print(node.val + " ");
//判断node是否 > cur
if(node.val <= cur) {
return false;
}
cur = node.val;
while(node.right == null && !s.isEmpty()) {
node = s.pop();
System.out.print(node.val + " ");
//判断node是否 > cur
if(node.val <= cur) {
return false;
}
cur = node.val;
}
if(node.right != null) {
s.push(node.right);
}else {
break;
}
}
}
return true;
}
/**
* 使用中序遍历 存到list中,再比较 【简单】
* 执行用时:2 ms, 在所有 Java 提交中击败了31.16% 的用户
内存消耗:38.2 MB, 在所有 Java 提交中击败了79.34% 的用户
*/
public boolean isBst03(TreeNode head, List<Integer> list) {
for(int i = 1; i < list.size(); i++) {
if(list.get(i) <= list.get(i - 1)) {
return false;
}
}
return true;
}
/**
* 注意:这个题目可以补全二叉树来做,得到 头结点上面的 min,max
* min < left < node.val < right < max
* 使用[先序遍历]
* 每次使node.val > left && node.val < right
* 同时left > min && right < max
*
* 递归
*/
public boolean isBst03(TreeNode head, long max, long min) {
if(head == null) {
return true;
}else {
if(head.left != null) {//节点head 左边的值 < head.val , 同时要 > min
if(head.left.val >= head.val || head.left.val <= min) {
// System.out.println("left:");
// System.out.println(" head.val:" + head.val + " head.left.val:" + head.left.val + " min:" + min);
return false;
}
}
if(head.right != null) { //节点head 右边的值 > head.val, 同时要 < max
if(head.right.val <= head.val || head.right.val >= max) {
// System.out.println("right;");
// System.out.println(" head.val:" + head.val + " head.right.val:" + head.right.val + " max:" + max);
return false;
}
}
boolean b1 = this.isBst03(head.left, head.val, min); //head.val
min = head.val;
boolean b2 = this.isBst03(head.right, max, min);
return b1 && b2;
}
}
/**调用上面
* 执行用时:0 ms, 在所有 Java 提交中击败了100.00% 的用户
内存消耗:38 MB, 在所有 Java 提交中击败了87.61% 的用户
* @param head
* @return
*/
public boolean isValidBST(TreeNode head) {
if(head == null) {
return true;
}
if(head.left != null && head.left.val >= head.val) {
return false;
}
if(head.right != null && head.right.val <= head.val) {
return false;
}
//得到最小值
long min = -1;
TreeNode p = head;
while(p.left != null){
p = p.left;
}
min = p.val - 1L; //注意是 -1L
// System.out.println("min:" + (min + 1));
return this.isBst03(head, Long.MAX_VALUE, min);
}
/**
* 上面的简化
* 执行用时:0 ms, 在所有 Java 提交中击败了100.00% 的用户
内存消耗:37.9 MB, 在所有 Java 提交中击败了94.21% 的用户
* @param head
* @return
*/
public boolean isValidBST02(TreeNode head) {
return this.isBst03(head, Long.MAX_VALUE, Long.MIN_VALUE);
}
/**
* 使用参考 思想,类似上面的
* 使用先序遍历
* min < node.val < max
* 执行用时:0 ms, 在所有 Java 提交中击败了100.00% 的用户
内存消耗:38.2 MB, 在所有 Java 提交中击败了77.43% 的用户
*/
public boolean isBst04(TreeNode head, long max, long min) {
if(head == null) {
return true;
}else {
if(head.val >= max || head.val <= min) {
return false;
}
boolean b1 = this.isBst04(head.left, head.val, min);
boolean b2 = this.isBst04(head.right, max, head.val);
return b1 && b2;
}
}
public boolean isValidBST03(TreeNode head) {
return this.isBst04(head, Long.MAX_VALUE, Long.MIN_VALUE);
}
/**
* 初始化一个tree
* 类广度遍历
* @param a
* @return
*/
public TreeNode initTree(Integer[] a) {
if(a == null || a.length == 0) {
return null;
}
int t = 0;
TreeNode p = new TreeNode(a[t]); //至少有一个元素
Queue<TreeNode> q = new LinkedList<>();
q.offer(p);
while(!q.isEmpty()) {
TreeNode node = q.poll();
if(t + 1 == a.length) { //先判断数组中是否还有下一个元素
return p;
}else {
t++;
if(a[t] == null) { //若下一个元素为null,则不需要创建新的节点
node.left = null;
}else {
node.left = new TreeNode(a[t]);
q.offer(node.left);
}
}
if(t + 1 == a.length) {
return p;
}else {
t++;
if(a[t] != null){ //上面的简写,a[t] == null,不需要再赋值
node.right = new TreeNode(a[t]);
q.offer(node.right);
}
}
}
return p;
}
@Test
public void test() {
// Integer[] a = new Integer[] {1, 2, 3, 4, 5, 9, 10, null, 6, 7, 8, null, null, null, 11};
// Integer[] a = new Integer[] {3, 9, 20, 16, 17, 15, 7, null, null, null, 18, null, null, null, null, 19, null};
// Integer[] a = new Integer[] {6, 2, 8, 0, 4, 7, 9, null, null,3, 5};
Integer[] a = new Integer[] {1, 1};
// Integer[] a = new Integer[] {10,5,15,null,null,6,20};
// Integer[] a = new Integer[] {3, 1, 5, 0, 2, 4, 6};
// Integer[] a = new Integer[] {2147483647,-2147483648};
TreeNode head = this.initTree(a);
System.out.println("输出中序遍历:");
List<Integer> list = new ArrayList<>();
this.inOrder(head, list);
System.out.println();
System.out.println("使用递归实现");
System.out.println(this.isBst(head, new Cur(Long.MIN_VALUE)));
System.out.println("使用迭代实现,中序遍历非递归:");
System.out.println(this.isBst02(head));
System.out.println("使用中序遍历,将值存到list中");
System.out.println(this.isBst03(head, list));
System.out.println("递归: min < left < node.val < right < max:");
System.out.println(this.isValidBST(head));
System.out.println("递归:min < node.val < max:");
System.out.println(this.isValidBST03(head));
System.out.println("递归:参考:");
System.out.println(this.isBst011(head));
}
/**
* 中序遍历
*/
public void inOrder(TreeNode head, List<Integer> list) {
if(head == null) {
return ;
}else {
this.inOrder(head.left, list);
System.out.print(head.val + " ");
list.add(head.val);
this.inOrder(head.right, list);
}
}