寻找二叉树是否为二叉搜索树

33

今天我参加了一场面试,被要求编写一个程序,该程序接收一棵二叉树并返回 true(如果它也是二叉搜索树)否则返回 false。

我的解决方法1:执行中序遍历并在 O(n) 时间内存储元素。现在扫描元素的数组/列表,并检查第i个元素是否大于(i+1)个元素。如果遇到这种情况,请返回false并跳出循环。(这需要O(n)时间)。最后返回true。

但这位绅士希望我提供一个高效的解决方案。我尝试了但是不成功,因为要找出它是否是BST,我必须检查每个节点。

此外他建议我考虑使用递归。

我的解决方法2:如果对于任何节点N N->left < N且N->right > N,则BT是BST,以及N的左节点的中序继承者小于N,N的右节点的中序继承者大于N,左子树和右子树都是BST。

但这将变得复杂且运行时间似乎不好。如果你知道任何最优解,请帮忙。


3
中序遍历已经按照数组中的顺序给出了节点的值,因此您不需要复制整棵树,只需跟踪上一个遇到的值,以便与当前值进行比较。 - anton.burger
哇!这是真的,我可能不需要数组,即使如此,顺序仍然是O(n)。 - dharam
你能定义一下面试官所说的“高效”是指时间复杂度还是空间复杂度吗?我倾向于认同你的看法,需要逐个节点检查,但不需要使用数组。 - anton.burger
他希望我在时间上进行优化。我认为这不可能在O(n)以下完成。 - dharam
3
他希望你告诉他,在O(n)以内是做不到的,如果有人声称可以做到,就可以用破坏二叉搜索树的值之一替换未被检查的节点来证明他是错的。(别忘了,在面试中询问不可能的事情是公平的 ;)) - Frank
7个回答

78

这是一个比较常见的问题,有以下回答:

public boolean isValid(Node root) {
     return isValidBST(root, Integer.MIN_VALUE,
          Integer.MAX_VALUE);
}
private boolean isValidBST(Node node, int l, int h) {
     if(node == null)
         return true;
     return node.value > l 
         && node.value < h
         && isValidBST(node.left, l, node.value)
         && isValidBST(node.right, node.value, h);
}

递归调用确保子树节点在其祖先的范围内,这很重要。由于每个节点只被检查一次,因此运行时间复杂度将为O(n)。

另一个解决方案是进行中序遍历并检查序列是否已排序,特别是因为您已知输入是二叉树。


4
你的代码对于像这样的树会做什么 https://www.dropbox.com/s/mg7jpqbn9z7jvn1/Screenshot%202014-07-16%2017.41.42.png。即使它不是二叉搜索树,它也会被计算为真吗? - seeker
我就是不理解这行代码 Math.min(node.value,MAX),你能否提供更多的解释? - moji
我认为可以简单地用node.value替换Math.min(node.value,MAX)。 - EFreak
该算法并不适用于所有输入值。例如,如果根节点的值为Integer.MIN_VALUE或Integer.MAX_VALUE,则算法将返回false,但实际上它是一个完美的BST,应该返回true。要纠正算法,您可以将初始的min、max值替换为null,并检查其是否为空。这样,您的算法将适用于所有输入值。 - CanC
你if语句中的表达式本身就是一个布尔值,直接返回即可。 - koolaang
显示剩余2条评论

7

@Dhruv提供的答案是很好的。除此之外,这里还有一种在O(n)时间内有效的解决方案。
我们需要在这种方法中跟踪上一个节点。在每个递归调用中,我们检查上一个节点数据与当前节点数据是否相同。如果当前节点数据小于上一个,则返回false。

int isBST(node* root) {
  static node* prev  = NULL;

  if(root==NULL)
    return 1;

  if(!isBST(root->left))
    return 0;

  if(prev!=NULL && root->data<=prev->data)
    return 0;

  prev = root;
  return isBST(root->right);
}


1
prev!=NULL && root->data<=prev->data ...不正确...如果第二个左子节点大于父节点,这个算法将会失败! - NoobEditor
@NoobEditor 我不这么认为。由于它按顺序扫描节点,如果任何左子节点大于其父节点,它将在那一刻返回0。因此,如果第二个左子节点大于父节点,则父节点的第一个左子节点也必定比它大,否则该函数将返回0值。如果您能想到某些函数失败的情况,请提供。 - AgentX
你的代码只检查了直接子节点,而没有检查子孙节点。作为广度优先遍历,考虑以下情况 [10 8 15 5 12] - NoobEditor
我尝试了 root = 10,root->left = 8,root->right=15,root->left->left = 5,root->left->right = 12,它正常工作! - AgentX

1
boolean b = new Sample().isBinarySearchTree(n1, Integer.MIN_VALUE, Integer.MAX_VALUE);
.......
.......
.......
public boolean isBinarySearchTree(TreeNode node, int min, int max){
  if(node == null){
    return true;
  }

  boolean left  = isBinarySearchTree(node.getLeft(), min, node.getValue());
  boolean right = isBinarySearchTree(node.getRight(), node.getValue(), max);

  return left && right && (node.getValue()<max) && (node.getValue()>=min);      
}

欢迎留言评论。谢谢。


0

我认为第二种方法是正确的。可以以递归方式遍历树。在每次迭代中,可以存储当前子树的下限和上限。如果我们想要检查以x为根的子树,并且子树的边界为l和h,则我们只需要检查l <= x <= h并检查具有边界l和x的左子树以及具有边界x和h的右子树。

这将具有O(n)的复杂度,因为我们从根开始,每个节点仅作为某个子树的根节点被检查一次。此外,我们需要O(h)的内存用于递归调用,其中h是树的高度。


你能否详细解释一下这种方法的运行时间复杂度?在我看来,它大于O(n),但我不确定。 - dharam
所以,根据你的看法,这仍然需要O(n)的时间复杂度。我被要求对其进行优化。 - dharam

-1

最好在SO本身内部重述链接的重要部分,因为链接可能随时间而消失或包含多余信息。 - Richard
Please dont paste links - Anshu Kumar Gupta

-1

上面有一些使用INTEGER.MAX和MIN的示例,我看不出通过它们的原因和意义, 如果我有任何错误,请纠正我或解释一下原因。

此外,二叉搜索树可能具有通过compareTo方法或Coperator进行比较的对象..(因此Integer.MIN和Integer.MAX不适用于该模型) 我正在编写一个代码,它返回true或false 必须调用(root_node,true),如果它是bst,则它将返回true,否则返回false

void boolean isBSt( node root_node, boolean passed_root)
{

  if ( node==null){
        if ( passed_root)
        return false;
        // you have passed null pointer as 
        //root of the tree , since there is no 
        // valid start point we can throw an exception or return false      
        return true;
   }

  if( node.right !=null ) 
     if ( node.right.data <= node.data)
   return false;

  if ( node.left !=null ) 
        if ! ( node.left.data <= node.data) 
    return false;

  return ( isBST( node.right , false) && isBST( node.left, false ) )

}

MIN和MAX只是为了避免角落情况,使递归调用看起来对称。 - dharam
我明白了,为什么会这样。 - user2398113
MIN和MAX值将较低层次树的验证与祖先的值绑定在一起。你的函数不知道它的祖先的值,因此可能错误地标记树为有效。 - Richard

-2

这里有另一种解决方案,它使用2个辅助函数来计算每个节点子树中的最小值和最大值,使用辅助函数minValue和maxValue。

int isBST(struct node* node)
    {
      if (node == NULL)
        return(true); 

      /* false if the max of the left is > than us */
        if (node->left!=NULL && maxValue(node->left) > node->data)
            return(false); 

      /* false if the min of the right is <= than us */
        if (node->right!=NULL && minValue(node->right) < node->data)
            return(false); 

      /* false if, recursively, the left or right is not a BST */ 
         if (!isBST(node->left) || !isBST(node->right))
           return(false); 

      /* passing all that, it's a BST */
          return(true);
    }

2
这不是遍历树两次吗? - Ja͢ck

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接