web-dev-qa-db-ja.com

最適な方法でバイナリ検索ツリーでk番目に小さい要素を見つける

静的/グローバル変数を使用せずに、バイナリ検索ツリーでk番目に小さい要素を見つける必要があります。効率的に達成する方法は?私が考えている解決策は、O(n)で操作を実行することです。これは、ツリー全体を順番に走査することを計画しているため、最悪のケースです。しかし、深いところでは、ここではBSTプロパティを使用していないと感じています。私の仮定的な解決策は正しいですか、またはより良いものが利用可能ですか?

110
bragboy

アイデアの概要は次のとおりです。

BSTでは、ノードTの左側のサブツリーには、Tに格納されている値よりも小さい要素のみが含まれます。 kが左サブツリーの要素数よりも小さい場合、kthの最小要素は左サブツリーに属している必要があります。それ以外の場合、kの方が大きい場合、kthの最小要素は右側のサブツリーにあります。

BSTを拡張して、その中の各ノードにその左サブツリーの要素数を格納させることができます(特定のノードの左サブツリーにそのノードが含まれると仮定します)。この情報を使用すると、左サブツリー内の要素の数を繰り返し要求してツリーを走査し、左サブツリーと右サブツリーのどちらに再帰するかを簡単に決定できます。

ここで、ノードTにいると仮定します。

  1. k == num_elements(Tの左サブツリー)の場合、探している答えはノードTの値です。
  2. k> num_elements(Tの左サブツリー)の場合、これらの要素もkthの最小値よりも小さいため、明らかに左サブツリーを無視できます。そのため、正しいサブツリーのk - num_elements(left subtree of T)最小要素を見つけることに問題を減らします。
  3. k <num_elements(Tの左サブツリー)の場合、kthの最小値は左サブツリーのどこかにあるため、左サブツリーでkthの最小要素を見つける問題を減らします。

複雑さの分析:

これには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のスペースと時間の複雑さが維持されます。

167
IVlad

より簡単な解決策は、順番にトラバーサルを行い、現在印刷される要素を(印刷せずに)追跡することです。 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); 
}
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#での私の実装であり、私はそれを投稿すると思ったので、人々はそれが私にとってうまくいくことを理解することができます

よろしくお願いします

13
Para

より簡単な解決策は、順番にトラバーサルを実行し、現在カウンター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;
}
11
Sumit Balani

//再帰なしで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();
        }
    }
}
10
Jiaji Li

スタックからノードをポップした後、kth要素の簡単なチェックを使用して、反復的な順序探索を使用できます。 http://en.wikipedia.org/wiki/Tree_traversal#Iterative_Traversal .

7
Binh Nguyen

カウンターを使用した再帰的インウォーク

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 )テストは、threeの場所で必要です:(a)左サブツリーの後、(b)ルートの後、および(c)右サブツリーの後。これは、k番目の要素がすべての場所で検出されることを保証する、つまり、配置されているサブツリーに関係なくです。

  2. if( result == -1 )結果要素のみが印刷されるを確認するために必要なテスト、そうでない場合は、k番目に小さいものからルートまでのすべての要素が印刷されます。

4
Arun

単純な二分探索木を考えると、できることは最も小さいものから始めて、正しいノードを見つけるために上方向に走査するだけです。

これを頻繁に行う場合は、各ノードに属性を追加して、左側のサブツリーにあるノードの数を示すことができます。それを使用して、ツリーを直接正しいノードに降りることができます。

4
Jerry Coffin

notバランス検索ツリーの場合、O(n)が必要です。

balanced検索ツリーの場合、最悪の場合はO(k + log n)が必要ですが、ちょうどO(k)in Amortized sense。

すべてのノードに追加の整数を持たせて管理する:サブツリーのサイズは、O(log n)時間の複雑さを与えます。このようなバランスの取れた検索ツリーは通常、RankTreeと呼ばれます。

一般に、解決策があります(ツリーに基づいていません)。

よろしく。

3
Slava

これは間違いなく問題の最適な解決策ではありませんが、一部の人々が面白いと思うかもしれないと思った別の潜在的な解決策です。

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

さて、ここに私の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;
 }
1
Manish Shukla

署名:

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

これはうまく機能します: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
pranjal

子ノードの数を格納するために元のツリーノードを変更する必要がないため、これは受け入れられた答えよりも優れていると思います。

順序トラバーサルを使用して、最小のノードを左から右にカウントし、カウントが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

Linux Kernelには、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 node、int n)を呼び出して、ツリーのルートを渡すことで取得できます。

0
Daniel

順番にトラバーサルを使用して、訪問した要素をスタックにプッシュするだけです。答えを得るために、k回ポップします。

k個の要素の後に停止することもできます

0
kartheek babu

補助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;
    }
}
}
0
Alex Fedulov

これも機能します。ツリーでmaxNodeを指定して関数を呼び出すだけです

def k_largest(self、node、k):if k <0:なしを返す
if k == 0:ノードを返すelse:k-= 1 return self.k_largest(self.predecessor(node)、k)

0
user2229805

order statistics treeを使用したIVladソリューションが最も効率的です。ただし、order statistics treeを使用できず、通常の古いBSTに固執している場合、最良のアプローチはIn Order Traversalを実行することです(prasadvkが指摘)。しかし、単に値を出力するだけでなく、k番目に小さい要素を返したい場合、彼の解決策は不十分です。また、彼のソリューションは再帰的であるため、スタックオーバーフローの危険があります。そのため、k番目に小さいノードを返し、スタックを使用してIn Order Traversalを実行するJavaソリューションを作成しました。実行時間は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;
}
0
Haider

C# that returnsの簡潔なバージョンですが、k番目の最小要素ですが、ref引数としてkを渡す必要があります(@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)で、k番目のノードに移動するのはO(k)なので、O(k + log n)です。

0
Erhhung

Javaコードは次のとおりです。

max(Node root、int k)-k番目に大きいものを見つける

min(Node 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);
}
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);
    } 

このJava再帰アルゴリズムは、k番目に小さい要素が見つかると再帰を停止します。

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

より良いアルゴリズムを見つけることができませんでした。

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

完全な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

これはこの質問に対する正確な答えです:

1. O(n)時間での順序走査を使用2. k + log n時間で拡張ツリーを使用

0
Shivendra

これは私が考えたものであり、機能します。 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