web-dev-qa-db-fra.com

Trouver le kth le plus petit élément dans un arbre de recherche binaire de manière optimale

J'ai besoin de trouver le kème plus petit élément de l'arbre de recherche binaire sans utiliser de variable statique/globale. Comment y parvenir efficacement? La solution que j'ai à l'esprit est de faire l'opération dans O (n), le pire des cas depuis que je prévois de faire une traversée en ordre de l'arbre entier. Mais au fond de moi, je sens que je n’utilise pas la propriété BST ici. Ma solution de prise en charge est-elle correcte ou existe-t-il une meilleure solution?

105
bragboy

Voici juste un aperçu de l'idée:

Dans un fichier BST, le sous-arbre de gauche du noeud T contient uniquement des éléments plus petits que la valeur stockée dans T. Si k est inférieur au nombre d'éléments dans le sous-arbre gauche, le kth plus petit élément doit appartenir au sous-arbre gauche. Sinon, si k est plus grand, alors le kth plus petit élément est dans le sous-arbre de droite.

Nous pouvons augmenter la valeur BST pour que chaque noeud en stocke le nombre d'éléments dans son sous-arbre de gauche (supposons que le sous-arbre de gauche d'un noeud donné inclut ce noeud). Avec cette information, il est simple de parcourir l’arbre en demandant à plusieurs reprises le nombre d’éléments dans le sous-arbre de gauche, afin de décider s’il convient de faire une récursion dans le sous-arbre de gauche ou de droite.

Supposons maintenant que nous sommes au noeud T:

  1. Si k == num_elements (sous-arbre de gauche de T) , la réponse que nous recherchons est la valeur dans le nœud T.
  2. Si k> num_elements (sous-arbre de gauche de T) , nous pouvons évidemment ignorer le sous-arbre de gauche, car ces éléments seront également plus petits que la plus petite variable kth. Nous réduisons donc le problème en recherchant le plus petit élément k - num_elements(left subtree of T) du sous-arbre de droite. 
  3. Si k <num_elements (sous-arbre de gauche de T) , le kth plus petit est quelque part dans le sous-arbre de gauche, nous réduisons donc le problème en recherchant le kth plus petit élément du sous-arbre de gauche.

Analyse de complexité:

Cela prend O(depth of node) time, qui est O(log n) dans le pire des cas sur un BST équilibré, ou O(log n) en moyenne pour un BST aléatoire.

Un fichier BST nécessite le stockage de O(n) et il faut un autre O(n) pour stocker les informations sur le nombre d'éléments. Toutes les opérations BST prennent O(depth of node) time et il faut O(depth of node) extra time pour conserver les informations relatives au "nombre d'éléments" lors de l'insertion, de la suppression ou de la rotation des nœuds. Par conséquent, le stockage d'informations sur le nombre d'éléments dans la sous-arborescence de gauche conserve la complexité spatiale et temporelle d'un fichier BST.

164
IVlad

Une solution plus simple consisterait à effectuer un parcours dans l’ordre et à garder trace de l’élément à imprimer (sans l’imprimer). Lorsque nous atteignons k, affiche l'élément et ignore le reste de la traversée de l'arbre.

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); 
}
65
prasadvk
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’est mon implémentation en C # basée sur l’algorithme ci-dessus, juste pensé à le poster pour que les gens comprennent mieux que ça marche pour moi

merci IVlad

13
Para

Une solution plus simple consisterait à effectuer une traversée dans l’ordre et à garder trace de l’élément à imprimer avec un compteur k. Lorsque nous atteignons k, imprimons l'élément. Le temps d'exécution est O (n). Rappelez-vous que le type de retour de fonction ne peut pas être nul, il doit renvoyer sa valeur mise à jour de k après chaque appel récursif. Une meilleure solution à cela serait un BST augmenté avec une valeur de position triée à chaque nœud.

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;
}
11
Sumit Balani

// ajoute une version Java sans récursivité

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();
        }
    }
}
10
Jiaji Li

Vous pouvez utiliser le parcours en ordre itératif: http://en.wikipedia.org/wiki/Tree_traversal#Iterative_Traversal Avec une simple vérification du kth élément après avoir sorti un nœud de la pile.

7
Binh Nguyen

Marche récursive en ordre avec un compteur

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

L'idée est similaire à celle de la solution @prasadvk, mais elle présente quelques inconvénients (voir les notes ci-dessous). Je publie donc cette réponse séparément.

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

Notes (et différences avec la solution de @ prasadvk):

  1. if( counter == k ) test est requis à trois places: (a) après le sous-arbre gauche, (b) après la racine et (c) après le sous-arbre droit. C’est pour que le kth élément soit détecté pour tous les emplacements , c’est-à-dire quel que soit le sous-arbre où il se trouve.

  2. if( result == -1 ) test requis pour assurer seul l'élément de résultat est imprimé , sinon tous les éléments à partir du kième le plus petit jusqu'à la racine sont imprimés.

4
Arun

À partir d’un simple arbre de recherche binaire, tout ce que vous pouvez faire, c’est commencer par le plus petit et aller vers le haut pour trouver le bon noeud.

Si vous allez le faire très souvent, vous pouvez ajouter un attribut à chaque nœud indiquant le nombre de nœuds dans son sous-arbre de gauche. En utilisant cela, vous pouvez descendre l’arbre directement sur le bon noeud.

4
Jerry Coffin

Pour un arbre de recherche équilibré/ pas , il faut O(n).

Pour équilibré arbre de recherche, il faut O (k + log n) dans le pire des cas mais juste O(k) dans Amortized sense.

Avoir et gérer le nombre entier supplémentaire pour chaque nœud: la taille du sous-arbre donne à O (log n) une complexité temporelle . Un tel arbre de recherche équilibré est généralement appelé RankTree.

En général, il existe des solutions (non basées sur l'arbre).

Cordialement.

3
Slava

Bien que ce ne soit certainement pas la solution optimale au problème, il s'agit d'une autre solution potentielle que certaines personnes pourraient trouver intéressante:

/**
 * 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
Robert S. Barnes

Eh bien voici mes 2 cents ...

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;
 }
1
Manish Shukla

signature:

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

appeler comme:

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

définition:

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

Cela fonctionne bien: status: est le tableau qui contient si un élément est trouvé. k: est le kième élément à trouver. count: garde la trace du nombre de nœuds traversés lors de la traversée de l'arbre.

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
pranjal

Je pense que c'est mieux que la réponse acceptée car il n'est pas nécessaire de modifier le nœud d'arborescence d'origine pour stocker le nombre de nœuds enfants.

Il suffit d’utiliser la traversée dans l’ordre pour compter le plus petit des noeuds de gauche à droite et arrêter la recherche une fois que le nombre est égal à 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
jinshui

Le noyau Linux possède une excellente structure de données arborescente rouge-noire augmentée qui prend en charge les opérations basées sur les rangs dans O (log n) dans linux/lib/rbtree.c.

Un port Java très grossier est également disponible à l’adresse http://code.google.com/p/refolding/source/browse/trunk/core/src/main/Java/it/unibo/refolding/alg/RbTree. Java , avec RbRoot.Java et RbNode.Java. Le nième élément peut être obtenu en appelant RbNode.nth (nœud RbNode, int n), en passant à la racine de l’arbre.

0
Daniel

Eh bien, nous pouvons simplement utiliser la traversée dans l’ordre et pousser l’élément visité sur une pile . Pop k nombre de fois, pour obtenir la réponse.

on peut aussi s'arrêter après k éléments

0
kartheek babu

Utilisation de la classe de résultat auxiliaire pour déterminer si un nœud est trouvé et si 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;
    }
}
}
0
Alex Fedulov

cela fonctionnerait aussi. il suffit d'appeler la fonction avec maxNode dans l'arbre

def k_largest (self, node, k): si k <0: retourner Aucun
si k == 0: noeud de retour autre: k - = 1 retourne self.k_largest (self.predecessor (node), k)

0
user2229805

La solution IVlad utilisant un order statistics tree est la plus efficace. Mais si vous ne pouvez pas utiliser un order statistics tree et êtes bloqué avec un ancien BST classique, la meilleure approche consiste à effectuer une traversée dans l'ordre (comme l'a souligné prasadvk). Mais sa solution est inadéquate si vous voulez renvoyer le kème élément le plus petit et ne pas simplement en imprimer la valeur. De plus, sa solution étant récursive, il existe un risque de débordement de pile. Par conséquent, j’ai écrit une solution Java qui renvoie le kème plus petit nœud et utilise une pile pour effectuer la traversée Dans l’ordre. Le temps d'exécution est O(n) tandis que la complexité d'espace est O(h) où h est la hauteur maximale de l'arbre.

// 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;
}
0
Haider

Voici une version concise de C # that renvoie le k-ème plus petit élément, mais nécessite de passer k comme argument de référence (la même approche que @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);
}

C'est O (log n) pour trouver le plus petit nœud, puis O(k) pour aller jusqu'au k-ième nœud, donc c'est O (k + log n).

0
Erhhung

Voici le code Java,

max (racine du nœud, int k) - pour trouver le kth le plus grand

min (nœud racine, int k) - pour trouver le kth le plus petit

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

Cet algorithme récursif Java arrête la récursion une fois que le k-ème élément le plus petit est trouvé. 

0
Vineeth Chitteti

La meilleure approche existe déjà. Mais je voudrais ajouter un code simple pour cela.

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

Je ne pouvais pas trouver un meilleur algorithme, j'ai donc décidé d'en écrire un:).

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
laxman

Solution pour le cas complet de 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;
}
0
gvijay

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

c'est la réponse exacte à cette question: -

1.Utilisation en ordre traversant O(n) heure 2.using Arbre augmenté en k + log n heure

0
Shivendra

C'est ce que je pensais et ça marche. Il se déroulera en 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
0
Learner