对于给定的二叉树,找到最大二进制搜索子树

时间:2010-07-02 05:15:08

标签: algorithm binary-tree binary-search-tree

对于给定的二叉树,找到最大的子树,它也是二叉搜索树?

示例:

输入:

                   10
               /         \
             50           150
            /  \         /   \
          25    75     200    20
         / \   / \    /  \    / \
        15 35 65 30  120 135 155 250 

输出:

                   50
                  /   \
                 25   75
                / \   /
               15 35  65

7 个答案:

答案 0 :(得分:4)

此答案以前包含基于链接/剪切树的O(n log n)算法。这是一个更简单的 O(n)解决方案。

核心是一个接受节点的过程,以其左子节点为根的唯一最大BSST,以其右子节点为根的唯一最大BSST,以及指向这些BSST的最左侧和最右侧元素的指针。它破坏了它的输入(可以通过持久数据结构避免)并构造以给定节点为根的唯一最大BSST,以及它的最小和最大元素。所有BSST节点都注明了后代的数量。和以前一样,这个过程是从后序遍历中重复调用的。要恢复子树,请记住最大BSST的根;重建它只需要一个简单的遍历。

我只会对待左BSST;右边是对称的。如果左侧BSST的根大于新根,则删除整个子树,新的根现在是最左侧的。否则,旧的最左侧节点仍然是最左侧的。从左侧BSST的最右侧节点开始向上移动,找到小于或等于根的第一个节点。必须将其正确的孩子移除;现在注意,由于BST属性,不需要其他节点!继续执行左侧BSST的根,更新计数以反映删除。

这是O(n)的原因是,尽管存在循环,原始树中的每个边实质上只遍历一次。


编辑:统称,遍历的路径是BST中的最大直线路径,左脊柱和右脊柱除外。例如,在输入

              H
             / \
            /   \
           /     \
          /       \
         /         \
        /           \
       /             \
      D               L
     / \             / \
    /   \           /   \
   /     \         /     \
  B       F       J       N
 / \     / \     / \     / \
A   C   E   G   I   K   M   O

这是遍历每个边的递归调用:

              H
             / \
            /   \
           /     \
          /       \
         /         \
        /           \
       /             \
      D               L
     / h             h \
    /   h           h   \
   /     h         h     \
  B       F       J       N
 / d     d h     h l     l \
A   C   E   G   I   K   M   O

答案 1 :(得分:3)

之前的算法(请参阅修订版)O(n^2) - 我们可以通过注意以下事实将其概括为O(n log n)

  1. 如果b是最大BST和b.left.value < b.value的根,那么b.left也在BST中(b.right.value ≥ b.value相同)
  2. 如果b是最大BST的根,并且a也在BST中,则a和b之间的每个节点都在BST中。
  3. 因此,如果c在a和b之间,并且c不在由b生根的BST中,则(由于(2。))也不是。使用这个事实,我们可以很容易地确定节点是否在任何给定祖先的BST中。我们将通过将一个节点连同其祖先列表以及当前子节点必须满足的相关min / maxValues(如果确实祖先是最大BST的根)(我们' ll调用此列表ancestorList)。我们将整个潜在根源集合存储在overallRootsList

    让我们定义一个名为potentialRoot的结构,如下所示:

      

    每个 potentialRoot 包含以下值:
              * 节点:我们正在考虑的BST根节点           * minValue和maxValue :另一个节点之间的范围必须介于以节点为根的BST的一部分(每个节点不同)
              * 子节点:以节点为根的最大BST中其余节点的列表

    伪代码看起来像这个(请注意,提到的所有列表都是potentialRoots的列表)

    FindLargestBST(node, ancestorList):
        leftList, rightList = empty lists
        for each potentialRoot in ancestorList:
            if potentialRoot.minValue < node.Value ≤ potentialRoot.maxValue:
                add node to potentialRoot.subNodes (due to (1.))
                (note that the following copies contain references, not copies, of subNodes)
                add copy of potentialRoot to leftList, setting maxValue = node.Value
                add copy of potentialRoot to rightList, setting minValue = node.Value
    
        add the potentialRoot (node, -∞, +∞) to leftList, rightList, and overallRootsList
        FindLargestBST(node.left, leftList)
        FindLargestBST(node.right, rightList)
    

    最后overallRootsList将是n potentialRoots的列表,每个都包含子节点列表。具有最大子节点列表的那个是您的BST。

    因为有&lt; treeHeight值在ancestorList中,然后(假设树是平衡的),算法运行在O(n log n)

答案 2 :(得分:2)

有趣的问题!

我之前的尝试是错误的!

这是另一次尝试(希望这次更正)。

我假设树已连接。

假设对于树的每个节点n,你有一组n,S n 的后代,其属性为

  • 对于S n 的每个成员x,从n到x的唯一路径是二进制搜索树(它只是一个路径,但您仍然可以将其视为树)。

  • 对于x的每个后代y,使得从n到y的路径是BST,y在S n 中。

节点集S n ,为您提供以n为根的最大BST。

我们可以通过对树进行深度优先搜索,并传递路径信息(从根到当前节点的路径)并更新每个节点的S n 来构建S n 通过沿路径回溯的路径中的节点。

当我们访问节点时,我们走上路径,检查到目前为止走的路径段是否满足BST属性。如果是这样,我们将当前节点添加到我们刚刚走过的路径的相应节点集中。我们在违反BST财产的那一刻就停止走路。检查我们到目前为止走的路径段是否为BST可以在O(1)时间内完成,每个节点的O(path_length)时间总处理时间。

最后,每个节点都会填充相应的S n 。我们现在可以走树了,选择S n 值最大的节点。

这需要的时间是节点深度的总和(在最坏的情况下),在平均情况下是O(nlogn)(参见http://www.toves.org/books/data/ch05-trees/index.html的第5.2.4节),但是(n ^ 2)在最坏的情况下。

更新套装的更聪明的方法也许可以保证减少最坏的情况。

伪代码可以是:

static Tree void LargestBST(Tree t)
{
    LargestBST(t, new List<Pair>());
    // Walk the tree and return the largest subtree with max |S_n|.
}

static Tree LargestBST(Tree t, List<Pair> path)
{
    if (t == null) return;

    t.Set.Add(t.Value);

    int value = t.Value;
    int maxVal = value;
    int minVal = value;

    foreach (Pair p in path)
    {
        if (p.isRight)
        {
            if (minVal < p.node.Value)
            {
                break;
            }
        }

        if (!p.isRight)
        {
            if (maxVal > p.node.Value)
            {
                break;
            }
        }

        p.node.Set.Add(t.Value);

        if (p.node.Value <= minVal)
        {
            minVal = p.node.Value;
        }

        if (p.node.Value >= maxVal)
        {
            maxVal = p.node.Value;
        }
    }

    Pair pl = new Pair();
    pl.node = t;
    pl.isRight = false;

    path.Insert(0, pl);
    LargestBST(t.Left, path);

    path.RemoveAt(0);

    Pair pr = new Pair();
    pr.node = t;
    pr.isRight = true;

    path.Insert(0, pr);

    LargestBST(t.Right, path);

    path.RemoveAt(0);

}

答案 3 :(得分:1)

在二元树中搜寻最大的二元树

我们可以通过两种方式解决此问题,

i)最大的BST没有被诱导(从一个节点,它的所有孩子都不需要满足BST条件)

ii)最大的BST诱导(从一个节点,其所有孩子都将满足BST条件)

我们将在这里讨论最大的BST(未诱导)。我们将采用自下而上的方法(邮政订单遍历)来解决这个问题。

a)到达叶节点

b)树节点(来自叶子)将返回一个TreeNodeHelper对象,其中包含以下字段。

public static class TreeNodeHelper {
        TreeNode node;
        int nodes;
        Integer maxValue;
        Integer minValue;
        boolean isBST;


        public TreeNodeHelper() {}

        public TreeNodeHelper(TreeNode node, int nodes, Integer maxValue, Integer minValue, boolean isBST) {
            this.node = node;
            this.nodes = nodes;
            this.maxValue = maxValue;
            this.minValue = minValue;
            this.isBST = isBST;
        }      
    }

c)最初从叶节点开始,节点= 1,isBST = true,minValue = maxValue = node.data。此外,如果满足BST条件,节点数将增加。

d)借助于此,我们将检查当前节点的BST条件。我们将重复相同的直到根。

e)从每个节点返回两个对象。一个用于最后的最大BST,另一个用于满足节点的当前BST。因此,从每个节点(叶子上方)(2 + 2)= 4(左子树为2,右子树为2)对象将被比较,并返回两个。

f)来自root的最终最大节点对象将是最大的BST

<强>问题:

这种方法存在问题。在遵循这种方法的同时,如果子树不满足当前节点的BST条件,我们不能简单地忽略子树(即使它具有较少的节点数)。例如

 55
  \
   75
  /  \
 27  89
    /  \
   26  95
      /  \
     23  105
         /  \
        20  110

从叶节点(20,110),将用节点(105)测试对象,它满足条件。但是当它到达节点(95)时,叶节点(20)不满足BST条件。由于该解决方案用于BST(未诱导),因此我们不应忽略满足条件的节点(105)和节点(110)。因此,从节点(95),我们必须再次回溯测试BST条件并捕获那些节点(105,110)。

此链接提供了此实现的完整代码

https://github.com/dineshappavoo/Implementation/tree/master/LARGEST_BST_IN_BT_NOT_INDUCED_VER1.0

答案 4 :(得分:0)

如果执行按顺序遍历,二叉搜索树将为您提供排序结果。因此,对整个二叉树进行有序遍历。最长的排序序列是您最大的二叉搜索子树。

  • 进行元素的顺序遍历(VISIT LEFT,VISIT ROOT,VISIT RIGHT)
  • 在执行此操作时,获取节点数据,比较先前节点数据是否小于下一个数据。如果是,则将计数器增加1.存储起始节点。
  • 比较失败时,将结束节点和重置计数器存储为0
  • 将此信息(计数器,开始,结束)节点存储在数组结构中,以便稍后找到具有最大值的节点,并为您提供最长的二进制搜索子树

答案 5 :(得分:0)

GetLargestSortedBinarySubtree(thisNode, ref OverallBestTree)
    if thisNode == null
        Return null
    LeftLargest = GetLargestSortedBinarySubtree(thisNode.LeftNode, ref OverallBestTree)
    RightLargest = GetLargestSortedBinarySubtree(thisNode.RightNode, ref OverallBestTree)
    if LeftLargest.Max < thisNode.Value & RightLargest.Min > thisNode.Value
        currentBestTree = new BinaryTree(LeftLargest, thisNode.Value, RightLargest)
    else if LeftLargest.Max < thisNode.Value
        currentBestTree = new BinaryTree(LeftLargest, thisNode.Value, null)
    else if RightLargest.Min > thisNode.Value
        currentBestTree = new BinaryTree(null, thisNode.Value, RightLargest)
    else
        currentBestTree = new BinaryTree(null, thisNode.Value, null)
    if (currentBestTree.Size > OverallBestTree.Size)
        OverallBestTree = currentBestTree
    return currentBestTree

正如BlueRaja所指出的,这种算法不正确。

它应该被称为GetLargestSortedBinarySubtreeThatCanBeRecursivelyConstructedFromMaximalSortedSubtrees

答案 6 :(得分:0)

root(Tree L A R) = A

MaxBST(NULL) = (true, 0, NULL)
MaxBST(Tree L A R as T) = 
  let
    # Look at both children
    (L_is_BST, L_size, L_sub) = MaxBST(L)
    (R_is_BST, R_size, R_sub) = MaxBST(R)
  in
  # If they're both good, then this node might be good too
  if L_is_BST and R_is_BST and (L == NULL or root(L) < A) and (R == NULL or A < root(R))
  then (true, 1 + L_size + R_size, T)
  else
       # This node is no good, so give back the best our children had to offer
       (false, max(L_size, R_size), if L_size > R_size then L_sub else R_sub)

一次查看每个树节点,因此以O(N)运行。

编辑:Crud,这并不认为它可以省略子树的某些部分。当我阅读子树时,我假设“整个树根植于某个节点”。我可能稍后再回来解决这个问题。