Найти K-й наименьший элемент в двоичном дереве поиска оптимальным способом


Мне нужно найти K-й наименьший элемент в двоичном дереве поиска без использования какой-либо статической/глобальной переменной. Как добиться этого эффективно? Решение, которое я имею в виду, выполняет операцию в O(n), в худшем случае, так как я планирую выполнить обход всего дерева по порядку. Но в глубине души я чувствую, что я не использую свойство BST здесь. Является ли мое предположительное решение правильным или есть лучшее?

30 102

30 ответов:

вот только набросок идеи:

в 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 , потом kсамый маленький находится где-то в левом поддереве, поэтому мы сводим задачу к поиску kth самый маленький элемент в левом поддереве.

сложность анализа:

это нужно O(depth of node) время, которое O(log n) в худшем случае на сбалансированном BST, или O(log n) в среднем для случайного BST.

BST требует O(n) хранение, и оно принимает другое O(n) для хранения информации о количестве элементов. Все операции BST принимают O(depth of node) и O(depth of node) дополнительное время для поддержания информации "количество элементов" для вставки, удаление или вращение узлов. Таким образом, хранение информации о количестве элементов в левом поддереве сохраняет пространственную и временную сложность BST.

более простым решением было бы выполнить обход inorder и отслеживать элемент, который в настоящее время должен быть напечатан (без его печати). Когда мы достигнем 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); 
}
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

более простым решением было бы выполнить обход inorder и отслеживать элемент, который в настоящее время должен быть напечатан со счетчиком k. когда мы достигнем k, распечатайте элемент. Время выполнения-O (n). Помните, что тип возврата функции не может быть пустым, он должен возвращать свое обновленное значение 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;
}

/ / добавить версию 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();
        }
    }
}

вы можете использовать итеративный обход порядка: http://en.wikipedia.org/wiki/Tree_traversal#Iterative_Traversal с помощью простой проверки для k-го элемента после извлечения узла из стека.

учитывая только простое двоичное дерево поиска, все, что вы можете сделать, это начать с самого маленького и пройти вверх, чтобы найти правильный узел.

Если вы собираетесь делать это очень часто, вы можете добавить атрибут к каждому узлу, обозначающий, сколько узлов находится в его левом поддереве. Используя это, вы можете спуститься по дереву непосредственно к нужному узлу.

рекурсивная прогулка по порядку со счетчиком

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):

на не сбалансированное дерево поиска, оно принимает O (n).

на сбалансированной поиск дерева, он принимает O (k + log n) в худшем случае, но просто O (k) на амортизированной смысле.

наличие и управление дополнительным целым числом для каждого узла: размер поддерева дает O (log n) сложность времени. Такое сбалансированное дерево поиска обычно называют RankTree.

In в общем, есть решения (основанные не на дереве).

с уважением.

Это хорошо работает: статус : есть массив, который содержит ли элемент найден. 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;
}

хотя это определенно не оптимальное решение проблемы, это еще одно потенциальное решение, которое, как я думал, некоторые люди могут найти интересным:

/**
 * 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 );
}

подпись:

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

Ну вот мои 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;
 }

Это то, что я, хотя и работает. Он будет работать в 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

Ну мы можем просто использовать в порядке обхода и толкать посещенный элемент в стек. поп k количество раз, чтобы получить ответ.

мы также можем остановиться после k элементов

решение для полного случая 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;
}

ядро Linux имеет отличную расширенную структуру данных красно-черного дерева, которая поддерживает операции на основе ранга в O(log n) в linux/lib/rbtree.гр.

очень грубый порт Java также можно найти по адресу http://code.google.com/p/refolding/source/browse/trunk/core/src/main/java/it/unibo/refolding/alg/RbTree.javaвместе с RbRoot.java и RbNode.Ява. N-й элемент может быть получен путем вызова RbNode.nth (узел RbNode, int n), проходящий в корне дерево.

вот краткий вариант в 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), чтобы найти the наименьший узел, а затем O(k) для перехода к k-му узлу, так что это O(k + log n).

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

вот точный ответ на этот вопрос: -

1.использование обхода inorder на O (n) время 2.использование расширенного дерева в K + log n time

Я не мог найти лучшего algorithm..so решил написать один :) Поправьте меня, если это неправильно.

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

}

здесь-это Java-код,

max (Node root, int k) - чтобы найти kth самый большой

min (корень узла, 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);
}

Это тоже будет работать. просто вызовите функцию с maxNode в дереве

def k_largest(self, node, k): если k если k == 0: возвращаемый узел еще: k - =1 вернуться самостоятельно.k_largest (self.предшественник(узел), к)

Я думаю, что это лучше, чем принятый ответ, потому что ему не нужно изменять исходный узел дерева для хранения количества его дочерних узлов.

нам просто нужно использовать обход по порядку для подсчета наименьшего узла слева направо, прекратите поиск, как только счетчик равен 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);
}

IVlad решение с помощью order statistics tree является наиболее эффективным. Но если вы не можете использовать order statistics tree и застряли с регулярным старым BST, то лучший подход-это сделать в порядке обхода (как указано prasadvk). Но его решение неадекватно, если вы хотите вернуть K-й наименьший элемент, а не просто распечатать значение. Кроме того, поскольку его решение рекурсивно, существует опасность переполнения стека. Поэтому я написал решение java, которое возвращает K-й наименьший узел и использует стек, чтобы сделать в порядке обхода. Время выполнения-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;
}

лучший подход уже есть.Но я хотел бы добавить простой код

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

использование вспомогательного класса результатов для отслеживания, если узел найден и текущий 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;
    }
}
}

Я написал аккуратные функцию для вычисления 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);  }}
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;
}
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;
    }
}
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;

      }
  }

}