以Optimum方式在二叉搜索树中查找第k个最小元素

时间:2010-02-24 20:18:05

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

我需要在不使用任何静态/全局变量的情况下在二叉搜索树中找到第k个最小元素。如何有效地实现它? 我在脑海中的解决方案是在O(n)中进行操作,这是最糟糕的情况,因为我计划对整个树进行顺序遍历。但在内心深处,我觉得我没有在这里使用BST属性。我的假设解决方案是正确的还是有更好的解决方案?

35 个答案:

答案 0 :(得分:166)

以下是这个想法的概述:

在BST中,节点T的左子树仅包含小于T中存储的值的元素。如果k小于左子树中的元素数,则k个最小元素必须属于左子树。否则,如果k较大,则k最小元素位于右子树中。

我们可以扩充BST以使其中的每个节点存储其左子树中的元素数量(假设给定节点的左子树包括该节点)。通过这条信息,可以通过反复询问左子树中的元素数来遍历树,以决定是否递归到左子树或右子树。

现在,假设我们在节点T:

  1. 如果 k == num_elements(T的左子树),那么我们要查找的答案就是节点T中的值。
  2. 如果 k> num_elements(T的左子树),显然我们可以忽略左子树,因为这些元素也将小于k最小的子树。因此,我们将问题减少到找到正确子树的k - num_elements(left subtree of T)最小元素。
  3. 如果 k< num_elements(T的左子树),然后k最小值位于左子树中的某个位置,因此我们将问题简化为在左子树中找到k个最小元素。 / LI>

    复杂性分析:

    这需要O(depth of node)时间,在平衡BST的最差情况下为O(log n),对于随机BST平均为O(log n)

    BST需要O(n)存储,并且需要另一个O(n)来存储有关元素数量的信息。所有BST操作都需要O(depth of node)次,并且需要O(depth of node)个额外时间来维护节点插入,删除或轮换的“元素数”信息。因此,存储左子树中元素数量的信息可以保持BST的空间和时间复杂度。

答案 1 :(得分:65)

更简单的解决方案是进行顺序遍历并跟踪当前要打印的元素(不打印它)。当我们到达k时,打印元素并跳过树遍历的其余部分。

void findK(Node* p, int* k) {
  if(!p || k < 0) return;
  findK(p->left, k);
  --k;
  if(k == 0) { 
    print p->data;
    return;  
  } 
  findK(p->right, k); 
}

答案 2 :(得分:13)

public int ReturnKthSmallestElement1(int k)
    {
        Node node = Root;

        int count = k;

        int sizeOfLeftSubtree = 0;

        while(node != null)
        {

            sizeOfLeftSubtree = node.SizeOfLeftSubtree();

            if (sizeOfLeftSubtree + 1 == count)
                return node.Value;
            else if (sizeOfLeftSubtree < count)
            {
                node = node.Right;
                count -= sizeOfLeftSubtree+1;
            }
            else
            {
                node = node.Left;
            }
        }

        return -1;
    }

这是我在C#中实现的基于上面的算法只是想我发布它所以人们可以更好地理解它对我有用

谢谢你IVlad

答案 3 :(得分:11)

更简单的解决方案是进行顺序遍历并使用计数器k跟踪当前要打印的元素。当我们到达k时,打印元素。运行时为O(n)。记住函数返回类型不能为void,它必须在每次递归调用后返回其更新的k值。对此更好的解决方案是增强的BST,每个节点都有一个排序的位置值。

public static int kthSmallest (Node pivot, int k){
    if(pivot == null )
        return k;   
    k = kthSmallest(pivot.left, k);
    k--;
    if(k == 0){
        System.out.println(pivot.value);
    }
    k = kthSmallest(pivot.right, k);
    return k;
}

答案 4 :(得分:10)

//添加没有递归的java版本

public static <T> void find(TreeNode<T> node, int num){
    Stack<TreeNode<T>> stack = new Stack<TreeNode<T>>();

    TreeNode<T> current = node;
    int tmp = num;

    while(stack.size() > 0 || current!=null){
        if(current!= null){
            stack.add(current);
            current = current.getLeft();
        }else{
            current = stack.pop();
            tmp--;

            if(tmp == 0){
                System.out.println(current.getValue());
                return;
            }

            current = current.getRight();
        }
    }
}

答案 5 :(得分:7)

您可以使用迭代inorder遍历: http://en.wikipedia.org/wiki/Tree_traversal#Iterative_Traversal 在将一个节点弹出堆栈后,简单地检查第k个元素。

答案 6 :(得分:4)

只给出一个简单的二叉搜索树,你可以做的就是从最小的开始,然后向上遍历以找到正确的节点。

如果您经常这样做,可以向每个节点添加一个属性,表示其左侧子树中有多少个节点。使用它,您可以将树直接下降到正确的节点。

答案 7 :(得分:4)

带计数器的递归有序步行

Time Complexity: O( N ), N is the number of nodes
Space Complexity: O( 1 ), excluding the function call stack

这个想法类似于@prasadvk解决方案,但它有一些缺点(见下面的注释),所以我将其作为一个单独的答案发布。

// Private Helper Macro
#define testAndReturn( k, counter, result )                         \
    do { if( (counter == k) && (result == -1) ) {                   \
        result = pn->key_;                                          \
        return;                                                     \
    } } while( 0 )

// Private Helper Function
static void findKthSmallest(
    BstNode const * pn, int const k, int & counter, int & result ) {

    if( ! pn ) return;

    findKthSmallest( pn->left_, k, counter, result );
    testAndReturn( k, counter, result );

    counter += 1;
    testAndReturn( k, counter, result );

    findKthSmallest( pn->right_, k, counter, result );
    testAndReturn( k, counter, result );
}

// Public API function
void findKthSmallest( Bst const * pt, int const k ) {
    int counter = 0;
    int result = -1;        // -1 := not found
    findKthSmallest( pt->root_, k, counter, result );
    printf("%d-th element: element = %d\n", k, result );
}

备注(与@ prasadvk解决方案的不同之处):

    三个位置需要
  1. if( counter == k )测试:(a)在左子树之后,(b)在root之后,以及(c)在右子树之后。这是确保检测到所有位置的第k个元素,即不管它所在的子树如何。

  2. if( result == -1 )测试确保仅打印结果元素,否则将打印从第k个最小到根的所有元素。

    < / LI>

答案 8 :(得分:3)

对于平衡搜索树,需要 O(n)

对于平衡搜索树,在最坏的情况下需要 O(k + log n),但 O(k) < strong>摊销感。

拥有并管理每个节点的额外整数:子树的大小给出 O(log n)时间复杂度。 这种平衡搜索树通常称为RankTree。

一般来说,有解决方案(不基于树)。

问候。

答案 9 :(得分: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;
}

答案 10 :(得分:1)

这是我的2美分......

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;
 }

答案 11 :(得分: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 );
}

答案 12 :(得分:1)

签名:

Node * find(Node* tree, int *n, int k);

致电:

*n = 0;
kthNode = find(root, n, k);

定义:

Node * find ( Node * tree, int *n, int k)
{
   Node *temp = NULL;

   if (tree->left && *n<k)
      temp = find(tree->left, n, k);

   *n++;

   if(*n==k)
      temp = root;

   if (tree->right && *n<k)
      temp = find(tree->right, n, k);

   return temp;
}

答案 13 :(得分: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函数。

答案 14 :(得分:0)

这是java代码,

max(Node root,int k) - 找到第k个最大的

min(节点root,int k) - 找到第k个最小

static int count(Node root){
    if(root == null)
        return 0;
    else
        return count(root.left) + count(root.right) +1;
}
static int max(Node root, int k) {
    if(root == null)
        return -1;
    int right= count(root.right);

    if(k == right+1)
        return root.data;
    else if(right < k)
        return max(root.left, k-right-1);
    else return max(root.right, k);
}

static int min(Node root, int k) {
    if (root==null)
        return -1;

    int left= count(root.left);
    if(k == left+1)
        return root.data;
    else if (left < k)
        return min(root.right, k-left-1);
    else
        return min(root.left, k);
}

答案 15 :(得分:0)

这也会奏效。只需在树中使用maxNode调用该函数

def k_largest(self,node,k):         如果k < 0:             返回无
        如果k == 0:             返回节点         其他:             k - = 1             return self.k_largest(self.predecessor(node),k)

答案 16 :(得分:0)

我认为这比接受的答案更好,因为它不需要修改原始树节点来存储它的子节点数。

我们只需要使用有序遍历从左到右计算最小的节点,一旦计数等于K就停止搜索。

private static int count = 0;
public static void printKthSmallestNode(Node node, int k){
    if(node == null){
        return;
    }

    if( node.getLeftNode() != null ){
        printKthSmallestNode(node.getLeftNode(), k);
    }

    count ++ ;
    if(count <= k )
        System.out.println(node.getValue() + ", count=" + count + ", k=" + k);

    if(count < k  && node.getRightNode() != null)
        printKthSmallestNode(node.getRightNode(), k);
}

答案 17 :(得分:0)

使用order statistics tree的IVlad解决方案效率最高。但是如果你不能使用order statistics tree并且坚持使用常规的旧BST,那么最好的方法是进行Inorder Traversal(如prasadvk所指出的)。但是如果你想要返回第k个最小元素并且不仅仅是打印出值,那么他的解决方案是不够的。此外,由于他的解决方案是递归的,因此存在堆栈溢出的危险。因此,我编写了一个java解决方案,它返回第k个最小节点并使用堆栈来执行In Order Traversal。运行时间为O(n),而空间复杂度为O(h),其中h是树的最大高度。

// The 0th element is defined to be the smallest element in the tree.
public Node find_kth_element(Node root , int k) {

    if (root == null || k < 0) return null;

    Deque<Node> stack = new ArrayDeque<Node>();
    stack.push(root);

    while (!stack.isEmpty()) {

        Node curr = stack.peek();

        if (curr.left != null) {

            stack.push(curr.left);
            continue;
        }

        if (k == 0) return curr;
        stack.pop();
        --k;

        if (curr.right != null) {

            stack.push(curr.right);

        }

    }

    return null;
}

答案 18 :(得分: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 );
}}

答案 19 :(得分:0)

使用辅助Result类来跟踪是否找到节点和当前k。

public class KthSmallestElementWithAux {

public int kthsmallest(TreeNode a, int k) {
    TreeNode ans = kthsmallestRec(a, k).node;
    if (ans != null) {
        return ans.val;
    } else {
        return -1;
    }
}

private Result kthsmallestRec(TreeNode a, int k) {
    //Leaf node, do nothing and return
    if (a == null) {
        return new Result(k, null);
    }

    //Search left first
    Result leftSearch = kthsmallestRec(a.left, k);

    //We are done, no need to check right.
    if (leftSearch.node != null) {
        return leftSearch;
    }

    //Consider number of nodes found to the left
    k = leftSearch.k;

    //Check if current root is the solution before going right
    k--;
    if (k == 0) {
        return new Result(k - 1, a);
    }

    //Check right
    Result rightBalanced = kthsmallestRec(a.right, k);

    //Consider all nodes found to the right
    k = rightBalanced.k;

    if (rightBalanced.node != null) {
        return rightBalanced;
    }

    //No node found, recursion will continue at the higher level
    return new Result(k, null);

}

private class Result {
    private final int k;
    private final TreeNode node;

    Result(int max, TreeNode node) {
        this.k = max;
        this.node = node;
    }
}
}

答案 20 :(得分:0)

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

想法是使用Morris Inorder Traversal

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  )

答案 21 :(得分: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;
    }

}     

答案 22 :(得分:0)

我找不到更好的算法..所以决定写一个:) 如果这是错误的,请纠正我。

class KthLargestBST{
protected static int findKthSmallest(BSTNode root,int k){//user calls this function
    int [] result=findKthSmallest(root,k,0);//I call another function inside
    return result[1];
}
private static int[] findKthSmallest(BSTNode root,int k,int count){//returns result[]2 array containing count in rval[0] and desired element in rval[1] position.
    if(root==null){
        int[]  i=new int[2];
        i[0]=-1;
        i[1]=-1;
        return i;
    }else{
        int rval[]=new int[2];
        int temp[]=new int[2];
        rval=findKthSmallest(root.leftChild,k,count);
        if(rval[0]!=-1){
            count=rval[0];
        }
        count++;
        if(count==k){
            rval[1]=root.data;
        }
        temp=findKthSmallest(root.rightChild,k,(count));
        if(temp[0]!=-1){
            count=temp[0];
        }
        if(temp[1]!=-1){
            rval[1]=temp[1];
        }
        rval[0]=count;
        return rval;
    }
}
public static void main(String args[]){
    BinarySearchTree bst=new BinarySearchTree();
    bst.insert(6);
    bst.insert(8);
    bst.insert(7);
    bst.insert(4);
    bst.insert(3);
    bst.insert(4);
    bst.insert(1);
    bst.insert(12);
    bst.insert(18);
    bst.insert(15);
    bst.insert(16);
    bst.inOrderTraversal();
    System.out.println();
    System.out.println(findKthSmallest(bst.root,11));
}

}

答案 23 :(得分:0)

http://www.geeksforgeeks.org/archives/10379

这是这个问题的确切答案: -

1.在O(n)时间内使用inorder遍历 2.在k + log n时间内使用增强树

答案 24 :(得分:0)

这是 C#中的简洁版本,返回第k个最小元素,但需要将k in作为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),然后O(k)遍历到第k个节点,所以它是O(k + log n)。

答案 25 :(得分:0)

Linux内核具有出色的增强红黑树数据结构,支持linux / lib / rbtree.c中O(log n)中基于秩的操作。

还可以在http://code.google.com/p/refolding/source/browse/trunk/core/src/main/java/it/unibo/refolding/alg/RbTree.java找到非常粗略的Java端口,以及RbRoot.java和RbNode.java。第n个元素可以通过调用RbNode.nth(RbNode节点,int n),传入树的根来获得。

答案 26 :(得分:0)

完整BST案例的解决方案: -

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;
}

答案 27 :(得分:0)

我们可以简单地使用顺序遍历并将被访问元素推送到堆栈上。 弹出k次,得到答案。

我们也可以在k元素之后停止

答案 28 :(得分: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

答案 29 :(得分:-1)

public TreeNode findKthElement(TreeNode root, int k){
    if((k==numberElement(root.left)+1)){
        return root;
    }
    else if(k>numberElement(root.left)+1){
        findKthElement(root.right,k-numberElement(root.left)-1);
    }
    else{
        findKthElement(root.left, k);
    }
}

public int numberElement(TreeNode node){
    if(node==null){
        return 0;
    }
    else{
        return numberElement(node.left) + numberElement(node.right) + 1;
    }
}

答案 30 :(得分:-1)

int RecPrintKSmallest(Node_ptr head,int k){
  if(head!=NULL){
    k=RecPrintKSmallest(head->left,k);
    if(k>0){
      printf("%c ",head->Node_key.key);
      k--;
    }
    k=RecPrintKSmallest(head->right,k);
  }
  return k;
}

答案 31 :(得分:-1)

public static Node kth(Node n, int k){
    Stack<Node> s=new Stack<Node>();
    int countPopped=0;
    while(!s.isEmpty()||n!=null){
      if(n!=null){
        s.push(n);
        n=n.left;
      }else{
        node=s.pop();
        countPopped++;
        if(countPopped==k){
            return node;
        }
        node=node.right;

      }
  }

}

答案 32 :(得分:-1)

我写了一个简洁的函数来计算第k个最小元素。我使用有序遍历并在它到达第k个最小元素时停止。

void btree::kthSmallest(node* temp, int& k){
if( temp!= NULL)   {
 kthSmallest(temp->left,k);       
 if(k >0)
 {
     if(k==1)
    {
      cout<<temp->value<<endl;
      return;
    }

    k--;
 }

 kthSmallest(temp->right,k);  }}

答案 33 :(得分:-1)

 public int printInorder(Node node, int k) 
    { 
        if (node == null || k <= 0) //Stop traversing once you found the k-th smallest element
            return k; 

        /* first recur on left child */
        k = printInorder(node.left, k); 

        k--;
        if(k == 0) {  
            System.out.print(node.key);
        }

        /* now recur on right child */
        return printInorder(node.right, k);
    } 

此Java递归算法一旦找到第k个最小元素,便停止递归。

答案 34 :(得分:-5)

对于二叉搜索树,inorder遍历将按顺序返回元素....

只需执行inorder遍历并在遍历k个元素后停止。

O(1)表示k的常数值。