首页 文章

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

提问于
浏览
103

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

30 回答

  • 62

    这里只是一个概念的概述:

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

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

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

    • 如果是 k == num_elements(left subtree of T) ,那么我们要找的答案就是节点 T 中的值 .

    • 如果 k > num_elements(left subtree of T) ,那么显然我们可以忽略左子树,因为那些元素也将小于 k 最小 . 因此,我们将问题简化为找到右子树的 k - num_elements(left subtree of T) 最小元素 .

    • 如果 k < num_elements(left subtree of T) ,则 k 最小的是左子树中的某个位置,因此我们将问题减少为在左子树中找到 k 最小的元素 .

    复杂性分析:

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

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

  • 0

    一个更简单的解决方案是进行顺序遍历并跟踪当前要打印的元素(不打印它) . 当我们到达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); 
    }
    
  • 9
    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

  • -1

    更简单的解决方案是进行顺序遍历并使用计数器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;
    }
    
  • 0

    //添加没有递归的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();
            }
        }
    }
    
  • 0

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

  • -1

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

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

  • 0

    使用计数器递归有序行走

    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解决方案的不同之处):

    three 位置需要进行

    • if( counter == k ) 测试:(a)在左子树之后,(b)在根之后,以及(c)在右子树之后 . 这是 ensure that kth element is detected for all locations ,即不管它所在的子树如何 .

    确保 only the result element is printed 需要

    • if( result == -1 ) 测试,否则将打印从第k个最小到根的所有元素 .
  • 4

    对于 not balancer 搜索树,需要O(n) .

    对于 balanced 搜索树,在最坏的情况下需要O(k log n)但在 Amortized 意义上只是O(k) .

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

    通常,有解决方案(不基于树) .

    问候 .

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

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

    /**
     * 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

    签名:

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

    那么这是我的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;
     }
    
  • 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
    
  • 12

    好吧,我们可以简单地使用顺序遍历并将访问过的元素推送到堆栈上 . 流行k次,得到答案 .

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

  • 159

    完整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;
    }
    
  • 3

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

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

  • 4

    这是 C# 中的简洁版本 returns 是第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);
    }
    

    找到 the 最小节点是O(log n),然后O(k)遍历到第k个节点,所以它是O(k log n) .

  • 1

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

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

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

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

    }

  • 0

    这是java代码,

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

    min(Node root, int k) - 找到最小的kth

    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);
    }
    
  • 0

    这也行得通 . 只需在树中使用maxNode调用该函数

    def k_largest(self,node,k):如果k <0:返回None
    如果k == 0:返回节点else:k - = 1返回self.k_largest(self.predecessor(node),k)

  • 7

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

    我们只需要使用有序遍历从左到右计算最小节点,一旦计数等于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);
    }
    
  • 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;
    }
    
  • 10

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

    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

    使用辅助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;
        }
    }
    }
    
  • 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个最小元素就停止递归 .

  • 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);  }}
    
  • 0
    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;
    }
    
  • 0
    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;
        }
    }
    

相关问题