【问题标题】:Finding the largest subtree in a BST在 BST 中找到最大的子树
【发布时间】:2010-02-25 17:31:17
【问题描述】:

给定一棵二叉树,我想找出其中最大的子树是 BST。

天真的方法:

我有一个幼稚的方法,我访问树的每个节点并将这个节点传递给 isBST 函数。如果是 BST,我还将跟踪子树中的节点数。

还有比这更好的方法吗?

【问题讨论】:

  • 您对“最大”的定义是什么?最深?大多数节点?
  • 我认为你需要澄清这个问题。显而易见的答案是整棵树是最大的 BST。您可能是指最大的平衡 BST?
  • 所有的二叉搜索树都是二叉树。反之则不然。
  • 我已将此标记为家庭作业。
  • 最大我的意思是该子树中的节点数是最高的。为了澄清更多,假设我有一个名为 count(node) 的函数,它返回子树中的节点数

标签: algorithm tree binary-tree


【解决方案1】:

我已经在我的博客中发布了完整的解决方案和解释:

http://www.leetcode.com/2010/11/largest-binary-search-tree-bst-in.html

这个想法是进行深度优先遍历并传递最小值和最大值自下而上而不是自上而下。换句话说,在验证上述节点是否满足 BST 要求之前,我们先验证更深的节点。

这样做的主要原因是当其中一个节点不满足 BST 属性时,上面的所有子树(也包括该节点)也必须满足 BST 要求。

与自上而下的方法相比,自下而上的方法是一个很棒的选择,因为节点总数的结果可以向上传递树。这使我们不必一遍又一遍地重新计算。子树的节点总数就是其左右子树的节点总数加一。

该算法在 O(N) 时间复杂度和 O(1) 空间中运行,效率最高。 (其中 N 是二叉树中的节点总数)。

以下是有效的 C++ 代码。实现的棘手部分是处理如何自下而上传递最小值和最大值。请注意,我没有初始化最小值和最大值,因为它们是在树的底部初始化的。

// Find the largest BST subtree in a binary tree.
// If the subtree is a BST, return total number of nodes.
// If the subtree is not a BST, -1 is returned.
int findLargestBSTSubtree(BinaryTree *p, int &min, int &max,
                   int &maxNodes, BinaryTree *& largestBST) {
  if (!p) return 0;
  bool isBST = true;
  int leftNodes = findLargestBSTSubtree(p->left, min, max, maxNodes, largestBST);
  int currMin = (leftNodes == 0) ? p->data : min;
  if (leftNodes == -1 ||
     (leftNodes != 0 && p->data <= max))
    isBST = false;
  int rightNodes = findLargestBSTSubtree(p->right, min, max, maxNodes, largestBST);
  int currMax = (rightNodes == 0) ? p->data : max;
  if (rightNodes == -1 ||
     (rightNodes != 0 && p->data >= min))
    isBST = false;
  if (isBST) {
    min = currMin;
    max = currMax;
    int totalNodes = leftNodes + rightNodes + 1;
    if (totalNodes > maxNodes) {
      maxNodes = totalNodes;
      largestBST = p;
    }
    return totalNodes;
  } else {
    return -1;   // This subtree is not a BST
  }
}

BinaryTree* findLargestBSTSubtree(BinaryTree *root) {
  BinaryTree *largestBST = NULL;
  int min, max;
  int maxNodes = INT_MIN;   // INT_MIN is defined in <climits>
  findLargestBSTSubtree(root, min, max, maxNodes, largestBST);
  return largestBST;
}

【讨论】:

  • 空间是 O(树的高度) 而不是 O(1)
【解决方案2】:

我想您要解决的问题是在 BT 中找到最大(具有更多节点)的 BST。在这种情况下,您需要遍历所有树节点,检查它是否是 BST,一旦找到一个,您就必须检查它是否有比目前找到的最大节点更多的节点。

class TreeNode
{
    public int value;
    public TreeNode left;
    public TreeNode right;
}

void LargestBST(TreeNode bt, IDictionary<TreeNode, bool> isBST, IDictionary<TreeNode, int> nodeCount, ref TreeNode largestBST)
{
    if (bt == null)
        return;
    if (IsBST(bt, isBST) && (largestBST == null || NodeCount(bt, nodeCount) > NodeCount(largestBST, nodeCount)) 
        largestBST = bt;
    else
    {
        LargestBST(bt.left, isBST, nodeCount, ref largestBST);
        LargestBST(bt.Right, isBST, nodeCount, ref largestBST);
    }
}

bool IsBST(TreeNode node, IDictionary<TreeNode, bool> isBST)
{
    if (node == null)
        return true;

    bool result;
    if (!isBST.TryGetValue(node, out result))
    {
        TreeNode maxLeft = Max(node.Left);
        TreeNode minRight = Min(node.Right);
        result = (maxLeft == null || maxLeft.value <= node.value) &&
                 (minRight == null || minRight.value >= node.value) &&
                 IsBST(node.left, isBST) && IsBST(node.Right, isBST);
        isBST.Add(node, result);
    }
    return result;
}

TreeNode Max(TreeNode node)
{
    if (node == null)
        return null;
    while (node.right != null)
        node = node.right;
    return node;
}

TreeNode Min(TreeNode node)
{
    if (node == null)
        return null;
    while (node.left != null)
        node = node.left;
    return node;
}

int NodeCount(TreeNode node, IDictionary<TreeNode, int> nodeCount)
{
    if (node == null)
        return 0;
    int result;
    if (!nodeCount.TryGetValue(node, out result))
    {
        result = 1 + NodeCount(node.left, nodeCount) + NodeCount(node.right, nodeCount);
        nodeCount.Add(node, result);
    }
    return result;
}

【讨论】:

  • 很好的解决方案。请注意,您还可以在O(n) 空间的节点上存储最小值/最大值,以便于分析和运行时。 (一种对最小值-最大值的路径压缩。)
  • 您的程序适用于 O(n^2)。 O(n) 解决方案也可以使用自下而上的方法,从叶子到根。
【解决方案3】:

如果它的中序遍历给你它的元素按排序顺序,那么这棵树就是一个 BST。如果您需要示例实现,可以在此处使用此代码:http://placementsindia.blogspot.com/2007/12/c-program-to-check-whether-binary-tree.html

运行时间为 O(N),其中 N = 节点数。

如果根的两个子树都是 BST,则将树视为 BST 是错误的(对于删除他提出此解决方案的答案的人:你不应该删除你的答案,就我个人而言,我不会反对你从一个糟糕但看似好的解决方案中学到的东西与从一个好的解决方案中学到的东西一样多)。反例:

    3
   / \
  2   4
 / \
1  5

现在,要获得 BST 的最大子树,请考虑这棵树:

    3
   / \
  2   4
 / \
1  5

中序遍历是 1 2 5 3 4。我认为您可以通过在中序遍历中找到最大长度的排序连续子序列来解决您的原始问题。你只需要小心不要选择不描述 BST 的序列。例如,对于:

    10
   / \
  2   14
 / \  |
1  5  20

inorder-traversal 是 1 2 5 10 20 14。不要选择 20。我认为这可以通过确保您关闭元素来实现,只要它们的选择不再有意义。例如,当您达到 14 岁时,请忽略 20 岁。但是我不确定这是否可以有效地完成。如果我找到确切的方法,我会编辑我的帖子。

【讨论】:

  • 实际上由于某种原因,我把它混在了我的脑海中(其他人建议“平衡树”),所以我想到的是平衡树而不是 BST。你当然是对的。
  • 这里不能选择10和14。最大的子树是1,2,5,即大小为3。子树A subtree of a tree T is a tree consisting of a node in T and all of its descendants in T.[c][1] Nodes thus correspond to subtrees (each node corresponds to the subtree of itself and all its descendants) – the subtree corresponding to the root node is the entire tree, and each node is the root node of the subtree it determines; the subtree corresponding to any other node is called a proper subtree (in analogy to the term proper subset).的定义见wikipedia。
【解决方案4】:

我认为您可以通过自上而下而不是自下而上来避免检查每个节点是否是 BST 的根。如果子树是 BST,它将比任何子树本身都大,因此如果它通过了 isBST 测试,则无需检查子树内部。然后,您只需让 isBST 返回有效树的大小,并将其与指向子树根的指针一起存储,如果您需要能够再次找到它,而不是只知道最大的树有多大。

更新:

此处发布的一些用于检查是否为 BST 的代码在某些情况下会失败,因为它们只检查节点的父节点。

举个例子:

       10
     /    \
   4      99
          /
         2

这不是一个有效的 BST,(2 相对于 10 不在位置上)但是如果您没有通过树向下发送最小值和最大值,您将错误地验证它是有效的。这个伪代码考虑到了这一点。

main
{
    Verify(root, MIN_VALUE, MAX_VALUE)
}

boolean Verify(node , min, max)
{

 if(node == null)
   return true;

  if(node.value > min &&
     node.value < max &&
     Verify(node.leftchild, min, node.value) &&
     Verify(node.rightchild,node.value,max)
  {
      return true;
  }
  else
  {
      return false;
  }
}

【讨论】:

    【解决方案5】:
    int getMinMaxValue(Node* root, bool isMin)
    {
       if (!root)
       {
          // Not real limits...
          return (isMin ? INT_MAX : INT_MIN);
       }
       int leftVal = getMinMaxValue(root->left, isMin);
       int rightVal = getMinMaxValue(root->right, isMin);
       if (isMin)
       {
          return min(root->value, min(leftVal, rightVal));
       }
       else
       {
          return max(root->value, max(leftVal, rightVal));
       }
    }
    
    bool isBST(Node* root)
    {
       if (!root)
       {
          return true;
       }
    
       Node* left = root->left;
       Node* right = root->right;
    
       if (left)
       {
          if (getMinMaxValue(left, false) > root->value)
          {
             return false;
          }
       }
    
       if (right)
       {
          if (getMinMaxValue(right, true) < root->value)
          {
             return false;
          }
       }
    
       return isBST(left) && isBST(right);
    }
    

    然后从根节点下降,检查子树是否为BST,取最大的。

    【讨论】:

    • 这是不正确的。我将尝试在 cmets 中举一个例子,希望格式化不会破坏这个例子。好吧,这是对我最初评论的编辑,因为树结构不清楚,所以我将对其进行描述。根值为10,左孩子为5,右孩子为12。12是叶子,5只有一个值为30的右孩子。根据你的算法,这是一个BST,如果不是。
    • 嗯我放弃了解决方案,但现在我考虑了一下,有一个循环不变量,我可以找到最小值/最大值而不必实际查看值,因为如果树不是BST 然后最终我会找到破坏它的节点。这仍然有效,但糟透了大声笑
    【解决方案6】:

    要验证一个节点是否是 BST 的根,我们必须递归检查每个左右子节点。如果你从根开始,你将不得不递归所有的孩子,然后才能确定二叉树的根是否是 BST。因此,为每个节点调用“isBST”没有任何意义。这是我的方法

    1. 从根开始
    2. 从左右查找最大值
    3. 如果不是左右返回最大值 “不是 BST”
    4. 如果 BST 在左边,检查它是否是 比目前为止的最大值大。如是 存储它并返回“NOT BST”
    5. 如果 BST 在右边,检查它是否是 比目前为止的最大值大。如是 存储它并返回“NOT BST”
    6. 如果左右为 BST ,则有 当前根为 ROOT 的新 BST 剩下的节点数+ 右 + 1

    完成这项工作的几个挑战是存储迄今为止我使用 ref 变量 MaxNumNodes 的最大值。 maxbst withh 具有函数 return 时找到的最大 BST 的根。

    public int MaxBST(Node root, int min, int max, ref Node maxbst, 
            ref int MaxNumNodes)
        {
            if (root == null) return 0;
    
            //Not a BST
            if (root.data < min || root.data > max) return -1;
    
            //Find Max BST on left
            int left = MaxBST(root.left, min, root.data, ref maxbst, 
                                        ref MaxNumNodes);
            //Find Max BST on right
            int right = MaxBST(root.right, root.data + 1, max, ref maxbst,
                                                ref MaxNumNodes);
    
            //Case1: -1 from both branches . No BST in both branches
            if (left == -1 && right == -1) return -1;
    
            //Case2:No BST in left branch , so choose right 
            //See if the BST on right is bigger than seen so far
            if (left == -1)
            {
                if (right> MaxNumNodes)
                {
                    MaxNumNodes = right;
                    maxbst = root.right;
                }
                return -1;
            }
    
            //Case3:No BST in right branch , so choose left 
            //See if the BST on left is bigger than seen so far
            if (right == -1)
            {
                if (left > MaxNumNodes)
                {
                    MaxNumNodes = left;
                    maxbst = root.left;
                }
                return -1;
            }
    
            //Case4:Both are BST , new max is left BST + right BST
            maxbst = root;
            return left + right + 1;
    
        }
    

    【讨论】:

      【解决方案7】:

      这不是最佳方法,但您可以对二叉树进行中序遍历并将其存储在一个数组中,然后找到最长的连续递增序列,这将为您提供具有最大节点数的 BST。遍历的复杂度是O(n),搜索的复杂度是O(n),所以它仍然是O(n)

      【讨论】:

        【解决方案8】:

        一种可能的解决方案如下 -

        int maxNodes = INT.MIN;
        Node* lb = NULL; 
        int largesBST(Node* root) { 
            largesBST(root, INT.MIN, INT.MAX);
        }
        
        int largesBST(Node* p, int MIN, int MAX) { 
             if(!p) { return 0; } 
             if(p->data > MIN || p->data < MAX) { return -1; }
             int lc = largestBST(p->left, p->data, MAX);
             if(lc == -1) { return -1; } 
             int rc = largestBST(p->right, MIN, p->data);
             if(rc == -1) { return -1; } 
             // At this point, cur node is BST
             int curNodes = lc + rc + 1;
             if(curNodes > maxNodes) { 
                maxNodes = curNodes;
                lb = p;
             }
        }
        

        【讨论】:

          【解决方案9】:

          解决上述问题:

          1. 可以按顺序遍历树
          2. 将其存储在一个数组中并找到“最大排序子集”。

          【讨论】:

            猜你喜欢
            • 2021-01-31
            • 1970-01-01
            • 1970-01-01
            • 2013-01-08
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多