静的/グローバル変数を使用せずに、バイナリ検索ツリーでk番目に小さい要素を見つける必要があります。それを効率的に達成する方法は?私が考えている解決策は、O(n)で操作を行うことです。これは、ツリー全体を順番にトラバースすることを計画しているため、最悪のケースです。しかし、ここではBSTプロパティを使用していないように感じます。私の仮定の解決策は正しいですか、それともより良い解決策がありますか?
34 に答える
アイデアの概要は次のとおりです。
BSTでは、ノードの左側のサブツリーには、にT
格納されている値よりも小さい要素のみが含まれT
ます。k
が左側のサブツリーの要素数よりも小さい場合、2番目k
に小さい要素は左側のサブツリーに属している必要があります。それ以外の場合、k
が大きい場合、k
th番目に小さい要素は右側のサブツリーにあります。
BSTを拡張して、各ノードにその左側のサブツリー内の要素の数を格納させることができます(特定のノードの左側のサブツリーにそのノードが含まれていると想定します)。この情報を使用すると、左側のサブツリーの要素数を繰り返し尋ねてツリーをトラバースし、左側のサブツリーと右側のサブツリーのどちらに再帰するかを決定するのは簡単です。
ここで、ノードTにいるとします。
- k == num_elements(Tの左側のサブツリー)の場合、探している答えはノードの値です
T
。 - k> num_elements(Tの左側のサブツリー)の場合、これらの要素も
k
最小のものよりも小さいため、明らかに左側のサブツリーを無視できます。したがって、問題を減らしてk - num_elements(left subtree of T)
、適切なサブツリーの最小要素を見つけます。 - k <num_elements(Tの左側のサブツリー)の場合、最小の要素
k
は左側のサブツリーのどこかにあるため、問題をk
左側のサブツリーで最小の要素を見つけることに減らします。
複雑さの分析:
これには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の空間と時間の複雑さが維持されます。
より簡単な解決策は、順序どおりにトラバーサルを実行し、現在印刷されている要素を(印刷せずに)追跡することです。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
//再帰なしで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();
}
}
}
より簡単な解決策は、順序どおりにトラバーサルを実行し、現在印刷される要素をカウンター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;
}
スタックからノードをポップした後、k番目の要素を簡単にチェックしてhttp://en.wikipedia.org/wiki/Tree_traversal#Iterative_Traversalの反復インオーダートラバーサルを使用でき ます。
単純な二分探索木だけを考えると、できることは、最小のものから始めて、上にトラバースして適切なノードを見つけることだけです。
これを頻繁に行う場合は、各ノードに属性を追加して、左側のサブツリーにノードがいくつあるかを示すことができます。これを使用して、ツリーを直接正しいノードに降ろすことができます。
カウンター付きの再帰的順序ウォーク
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のソリューションとの違い):
if( counter == k )
テストは、(a)左サブツリーの後、(b)ルートの後、(c)右サブツリーの後の3か所で必要です。これは、k番目の要素が、つまり、配置されているサブツリーに関係なく、すべての場所で確実に検出されるようにするためです。if( result == -1 )
結果要素のみが印刷されることを確認するためにテストが必要です。そうでない場合、k番目に小さいものからルートまでのすべての要素が印刷されます。
バランスの取れていない探索木では、O(n)が必要です。
バランスの取れた探索木では、最悪の場合はO(k + log n)が必要ですが、償却の意味ではO(k)だけです。
すべてのノードに追加の整数を設定して管理します。サブツリーのサイズにより、O(log n)の時間計算量が増加します。このようなバランスの取れた探索木は通常、ランクツリーと呼ばれます。
一般に、(ツリーに基づかない)解決策があります。
よろしく。
これはうまく機能します: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;
}
さてここに私の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;
}
これは間違いなく問題の最適な解決策ではありませんが、一部の人々が興味深いと思うかもしれない別の潜在的な解決策です。
/**
* 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;
}
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 node、int n)を呼び出して、ツリーのルートを渡すことで取得できます。
完全な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;
}
これは私がしかしそれが機能するものです。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個の要素の後で停止することもできます
これは、k番目に小さい要素を返すC#の簡潔なバージョンですが、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);
}
最小のノードを見つけるのはO(log n)であり、次にk番目のノードにトラバースするのはO(k)なので、O(k + log n)です。
http://www.geeksforgeeks.org/archives/10379
これはこの質問に対する正確な答えです:-
1. O(n)時間で順番にトラバーサルを使用する2. k +logn時間で拡張ツリーを使用する
私はより良いアルゴリズムを見つけることができませんでした..それでそれを書くことに決めました:)これが間違っているなら私を訂正してください。
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)-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);
}
これもうまくいくでしょう。ツリー内のmaxNodeを使用して関数を呼び出すだけです
def k_largest(self、node、k):if k <0:return None
if k == 0:return node else:k-= 1 return self.k_largest(self.predecessor(node)、k)
子ノードの数を格納するために元のツリーノードを変更する必要がないため、これは受け入れられた答えよりも優れていると思います。
インオーダートラバーサルを使用して、最小のノードを左から右にカウントし、カウントが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);
}
最善のアプローチはすでにありますが、そのための簡単なコードを追加したいと思います
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;
}
}
}
Pythonソリューション時間計算量:O(n)空間計算量:O(1)
アイデアは、MorrisInorderTraversalを使用することです
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 )
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;
}
}
手順は次のとおりです。
1.ルートするツリーのサイズを示すフィールドを各ノードに追加します。これは、O(logN)平均時間での操作をサポートします。
2.スペースを節約するには、ルートするノードのサイズを示す1つのフィールドで十分です。左側のサブツリーと右側のサブツリーの両方のサイズを保存する必要はありません。
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関数も取得できます。
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); }}
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;
}
}
}
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番目に小さい要素が見つかると再帰を停止します。
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;
}
}
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;
}
二分探索木の場合、順序のないトラバーサルは要素を...順番に返します。
順番どおりにトラバーサルを実行し、k個の要素をトラバースした後に停止します。
kの定数値の場合はO(1)。