在最优方式中查找二叉搜索树中第k小的元素

116

我需要在二叉搜索树中找到第k个最小的元素,且不能使用任何静态/全局变量。如何高效实现?

我目前考虑的解决方案是,最坏情况下通过遍历整棵树进行O(n)操作。但我感觉这里没有充分利用二叉搜索树的性质。请问我的解决方案是否正确,还有更好的方案吗?


8
这棵树是否平衡? - kennytm
不是的。但如果它是平衡的,是否有最佳方式? - bragboy
1
如果你在搜索“顺序统计量”一词,你会找到你需要的内容。 - RAL
我有点觉得下面大部分的答案虽然正确,但是它们都在作弊,因为它们使用了某种全局变量(无论是对整数的引用还是被递减并返回的变量)。如果绝对不允许使用这些变量,我会使用递归而不传入任何引用。 - Henley
35个回答

1

虽然这绝对不是问题的最佳解决方案,但这是另一个潜在的解决方案,我认为有些人可能会觉得很有趣:

/**
 * Treat the bst as a sorted list in descending order and find the element 
 * in position k.
 *
 * Time complexity BigO ( n^2 )
 *
 * 2n + sum( 1 * n/2 + 2 * n/4 + ... ( 2^n-1) * n/n ) = 
 * 2n + sigma a=1 to n ( (2^(a-1)) * n / 2^a ) = 2n + n(n-1)/4
 *
 * @param t The root of the binary search tree.
 * @param k The position of the element to find.
 * @return The value of the element at position k.
 */
public static int kElement2( Node t, int k ) {
    int treeSize = sizeOfTree( t );

    return kElement2( t, k, treeSize, 0 ).intValue();
}

/**
 * Find the value at position k in the bst by doing an in-order traversal 
 * of the tree and mapping the ascending order index to the descending order 
 * index.
 *
 *
 * @param t Root of the bst to search in.
 * @param k Index of the element being searched for.
 * @param treeSize Size of the entire bst.
 * @param count The number of node already visited.
 * @return Either the value of the kth node, or Double.POSITIVE_INFINITY if 
 *         not found in this sub-tree.
 */
private static Double kElement2( Node t, int k, int treeSize, int count ) {
    // Double.POSITIVE_INFINITY is a marker value indicating that the kth 
    // element wasn't found in this sub-tree.
    if ( t == null )
        return Double.POSITIVE_INFINITY;

    Double kea = kElement2( t.getLeftSon(), k, treeSize, count );

    if ( kea != Double.POSITIVE_INFINITY )
        return kea;

    // The index of the current node.
    count += 1 + sizeOfTree( t.getLeftSon() );

    // Given any index from the ascending in order traversal of the bst, 
    // treeSize + 1 - index gives the
    // corresponding index in the descending order list.
    if ( ( treeSize + 1 - count ) == k )
        return (double)t.getNumber();

    return kElement2( t.getRightSon(), k, treeSize, count );
}

1

这个很好用:status:是保存元素是否被找到的数组。k:是要查找的第k个元素。count:在树遍历期间跟踪经过的节点数。

int kth(struct tree* node, int* status, int k, int count)
{
    if (!node) return count;
    count = kth(node->lft, status, k, count);  
    if( status[1] ) return status[0];
    if (count == k) { 
        status[0] = node->val;
        status[1] = 1;
        return status[0];
    }
    count = kth(node->rgt, status, k, count+1);
    if( status[1] ) return status[0];
    return count;
}

1

好的,这是我的两分钱意见...

int numBSTnodes(const Node* pNode){
     if(pNode == NULL) return 0;
     return (numBSTnodes(pNode->left)+numBSTnodes(pNode->right)+1);
}


//This function will find Kth smallest element
Node* findKthSmallestBSTelement(Node* root, int k){
     Node* pTrav = root;
     while(k > 0){
         int numNodes = numBSTnodes(pTrav->left);
         if(numNodes >= k){
              pTrav = pTrav->left;
         }
         else{
              //subtract left tree nodes and root count from 'k'
              k -= (numBSTnodes(pTrav->left) + 1);
              if(k == 0) return pTrav;
              pTrav = pTrav->right;
        }

        return NULL;
 }

0
这是一个简洁的C#版本,它返回第k小的元素,但需要将k作为ref参数传递(这与@prasadvk采用的方法相同):
Node FindSmall(Node root, ref int k)
{
    if (root == null || k < 1)
        return null;

    Node node = FindSmall(root.LeftChild, ref k);
    if (node != null)
        return node;

    if (--k == 0)
        return node ?? root;
    return FindSmall(root.RightChild, ref k);
}

找到最小的节点是O(log n),然后遍历到第k个节点是O(k),所以总时间复杂度是O(k + log n)。


Java 版本怎么样? - Henley

0

最佳方法已经存在。但我想为此添加一个简单的代码

int kthsmallest(treenode *q,int k){
int n = size(q->left) + 1;
if(n==k){
    return q->val;
}
if(n > k){
    return kthsmallest(q->left,k);
}
if(n < k){
    return kthsmallest(q->right,k - n);
}

}

int size(treenode *q){
if(q==NULL){
    return 0;
}
else{
    return ( size(q->left) + size(q->right) + 1 );
}}

0

以下是步骤:

1.为每个节点添加一个字段,指示其根节点所在树的大小。这支持平均O(logN)时间的操作。

2.为了节省空间,只需要一个字段来指示它所在节点的大小即可。我们不需要保存左子树和右子树的大小。

3.进行中序遍历,直到LeftTree == K, LeftTree = Size(T->Left) + 1

4.以下是示例代码:

int Size(SearchTree T)
{
    if(T == NULL) return 0;
    return T->Size;
}
Position KthSmallest(SearchTree T, int K)
{
    if(T == NULL) return NULL;

    int LeftTree;
    LeftTree = Size(T->Left) + 1;

    if(LeftTree == K) return T;

    if(LeftTree > K){ 
        T = KthSmallest(T->Left, K); 
    }else if(LeftTree < K){ 
        T = KthSmallest(T->Right, K - LeftTree);
    }   

    return T;
}

5. 同样地,我们也可以得到 KthLargest 函数。


0
public int kthSmallest(TreeNode root, int k) {
     
    LinkedList<TreeNode> stack = new LinkedList<TreeNode>();

    while (true) {
      while (root != null) {
        stack.push(root);
        root = root.left;
      }
      root = stack.pop();
      k = k - 1;
      if (k == 0) return root.val;
      root = root.right;
    }

}     

0

Python解决方案 时间复杂度:O(n) 空间复杂度:O(1)

思路是使用Morris中序遍历

class Solution(object):
def inorderTraversal(self, current , k ):
    while(current is not None):    #This Means we have reached Right Most Node i.e end of LDR traversal

        if(current.left is not None):  #If Left Exists traverse Left First
            pre = current.left   #Goal is to find the node which will be just before the current node i.e predecessor of current node, let's say current is D in LDR goal is to find L here
            while(pre.right is not None and pre.right != current ): #Find predecesor here
                pre = pre.right
            if(pre.right is None):  #In this case predecessor is found , now link this predecessor to current so that there is a path and current is not lost
                pre.right = current
                current = current.left
            else:                   #This means we have traverse all nodes left to current so in LDR traversal of L is done
                k -= 1
                if(k == 0):
                    return current.val
                pre.right = None       #Remove the link tree restored to original here 
                current = current.right
        else:               #In LDR  LD traversal is done move to R 
            k -= 1
            if(k == 0):
                return current.val
            current = current.right

    return 0

def kthSmallest(self, root, k):
    return self.inorderTraversal( root , k  )

0

这就是我想的,而且它有效。它将在O(log n)中运行。

public static int FindkThSmallestElemet(Node root, int k)
    {
        int count = 0;
        Node current = root;

        while (current != null)
        {
            count++;
            current = current.left;
        }
        current = root;

        while (current != null)
        {
            if (count == k)
                return current.data;
            else
            {
                current = current.left;
                count--;
            }
        }

        return -1;


    } // end of function FindkThSmallestElemet

3
我不认为这个解决方案会奏效。如果第K小的元素在树节点的右子树中怎么办? - Anil Vishnoi

0
完整二叉搜索树的解决方案:-
Node kSmallest(Node root, int k) {
  int i = root.size(); // 2^height - 1, single node is height = 1;
  Node result = root;
  while (i - 1 > k) {
    i = (i-1)/2;  // size of left subtree
    if (k < i) {
      result = result.left;
    } else {
      result = result.right;
      k -= i;
    }  
  }
  return i-1==k ? result: null;
}

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