判断要求
判断一个二叉树是否为搜索树Binary Search Tree
- 对于每一个节点,左子树的所有值小于此节点,右子树的所有值大于此节点。
- 每一个节点的值都不重复(without duplicate values)。
class BinaryTree {
private int key;
private BinaryTree left, right;
/**
* Simple constructor.
*
* @param key
* to set as key.
*/
public BinaryTree(int key) {
this.key = key;
}
/**
* Extended constructor.
*
* @param key
* to set as key.
* @param left
* to set as left child.
* @param right
* to set as right child.
*/
public BinaryTree(int key, BinaryTree left, BinaryTree right) {
this.key = key;
setLeft(left);
setRight(right);
}
public int getKey() {
return key;
}
/**
* @return the left child.
*/
public BinaryTree getLeft() {
return left;
}
/**
* @return the right child.
*/
public BinaryTree getRight() {
return right;
}
public boolean hasLeft() {
return left != null;
}
public boolean hasRight() {
return right != null;
}
/**
* @param left
* to set
*/
public void setLeft(BinaryTree left) {
this.left = left;
}
/**
* @param right
* to set
*/
public void setRight(BinaryTree right) {
this.right = right;
}
}
方法1:简单的比较节点与其左孩子和右孩子
对每一个节点进行操作:简单的比较节点的值与其左孩子和右孩子的值。这是初学的时候最容易想到的方法。
但是是错误的!!!我最开始的时候因为对二叉搜索树的定义理解有疏漏。写出了以下代码:
public static boolean isTreeBST(BinaryTree tree) {
if(tree.hasLeft()){
if(tree.getLeft().getKey() < tree.getKey()){
isTreeBST(tree.getLeft());
}else{
return false;
}
}
if(tree.hasRight()){
if(tree.getRight().getKey() > tree.getKey()){
isTreeBST(tree.getRight());
}else{
return false;
}
}
return true;
}
回到判断要求的第一点:1. 对于每一个节点,左子树的所有值小于此节点,右子树的所有值大于此节点。
注意二叉搜索树的定义:不是 :左孩子的所有值小于此节点,右孩子的所有值大于此节点。
在以下情况此方法的输出结果是true,其实是错误的(因为8在7的左子树,但是8大于7):
方法2:使用Inorder中序遍历将tree的所有节点保存在ArrayList中,判断ArrayList是否为降序排列
这是我发现第一种方法错误之后容易想到的另一种方法,思路比较简单,但是实现需要利用其他的数据结构。
图视这是一棵二叉搜索树。
- 用中序遍历(从右边开始)访问所有节点。这样可以将树中的所有节点的值由大到小的遍历。
- 每次访问节点,都将节点的值存入ArrayList中。
- 判断ArrayList是否为降序排列,同时保证无重复值。
public static boolean isTreeBST(BinaryTree tree) {
if(tree == null) return true;
if((tree.hasLeft() == false) && (tree.hasRight() == false)) return true;
List<Integer> result = new ArrayList<>();
result = Inorder(tree, result);
for(int i = 0; i < result.size()-1; i++){
if(result.get(i) <= result.get(i+1)){
return false;
}
}
return true;
}
public static List<Integer> Inorder(BinaryTree tree, List<Integer> result){
if(tree.hasRight()){
Inorder(tree.getRight(), result);
}
result.add(tree.getKey());
if(tree.hasLeft()){
Inorder(tree.getLeft(), result);
}
return result;
}
方法3:利用变量Min&max来表示整个树的bound,以此来判断每个节点,依然使用Inorder中序遍历
这个方法是学校TA提供的,以下我用Java写一遍。思路确实很精妙,思考过程比较复杂,我也在纸上画了一会儿才搞明白。但是实现过程异常的简单。
注意:本文讨论的树的每个节点的值都是integer
- 访问每一个节点的时候,都有两个变量Min&Max用来表示到目前为止这个树的上下边界。
- 访问节点时,比较节点的值与Min&Max,在范围外则return false。
- 如果节点为null,则说明递归已经到了base case也就是树的边界点。一直到此的节点都在边界范围内,那么说明树没有问题,return true。
- 如果节点不是null,那么继续开始递归到他的左右子树(更新Min&Max)。并且只有他的左右子树都能通过测试时,这个节点才能通过测试。所以用&&(and)。
这个方法最关键的是理解对于每个节点来说,什么是他的Min&Max。
以上图的树为例进行分析:
- 从root 10 开始,因为他是root,所以可以为任何值。也就是在【Integer.MIN_VALUE, Integer.MAX_VALUE】范围内。显然10通过了范围测试。
- 开始递归到10的右子树15,15的范围是多少呢?首先他需要比他的parent要大,也就是>10,也就是>=11。上边界呢?没有改变,可以非常大,只要>10就行了。所以他的范围是【10+1,Integer.MAX_VALUE】。显然15通过了范围测试。
- 开始递归到15的右子树20,20的范围是多少呢?首先他需要比他的parent要大,也就是>15,也就是>=16。上边界呢?没有改变,可以非常大,只要>16就行了。所以他的范围是【15+1,Integer.MAX_VALUE】。显然20通过了范围测试。
- 开始递归到15的左子树11,11的范围是多少呢?首先他需要比他的parent要小,也就是<15,也就是<=14。下边界呢?没有改变。所以他的范围是【11,15-1】。显然11通过了范围测试。
11,20通过了测试。并且子节点都是null–> 那么以这两个点为root的树是BST,return true–>
以15为节点的树是BST,return true --> 以8为root的树是BST,return true -->
以10为节点的树是BST,return true
public static boolean isTreeBST(BinaryTree tree) {
return Inorder(tree, Integer.MIN_VALUE, Integer.MAX_VALUE);
}
public static boolean Inorder(BinaryTree tree, int Min, int Max){
if(tree.getKey() < Min || tree.getKey() > Max){
return false;
}
if(tree == null) return true;
return Inorder(tree.getRight(), tree.getKey()+1, Max) && Inorder(tree.getLeft(), Min, tree.getKey()-1);
}