정적 / 전역 변수를 사용하지 않고 이진 검색 트리에서 k 번째로 작은 요소를 찾아야합니다. 효율적으로 달성하는 방법은 무엇입니까? 내가 생각하는 해결책은 O (n)에서 작업을 수행하는 것인데, 전체 트리를 순회 할 계획이므로 최악의 경우입니다. 그러나 나는 여기서 BST 속성을 사용하지 않는다고 느낍니다. 내 가정적인 솔루션이 정확합니까 아니면 더 나은 솔루션이 있습니까?
정적 / 전역 변수를 사용하지 않고 이진 검색 트리에서 k 번째로 작은 요소를 찾아야합니다. 효율적으로 달성하는 방법은 무엇입니까? 내가 생각하는 해결책은 O (n)에서 작업을 수행하는 것인데, 전체 트리를 순회 할 계획이므로 최악의 경우입니다. 그러나 나는 여기서 BST 속성을 사용하지 않는다고 느낍니다. 내 가정적인 솔루션이 정확합니까 아니면 더 나은 솔루션이 있습니까?
답변:
다음은 아이디어의 개요입니다.
BST에서 노드의 왼쪽 하위 트리 T
에는에 저장된 값보다 작은 요소 만 포함됩니다 T
. 경우 k
좌측 서브 트리 내의 요소의 수보다 작은 k
최소 번째 요소는 좌측 서브 트리에 속해야한다. 그렇지 k
않고 더 크면 k
가장 작은 요소가 오른쪽 하위 트리에 있습니다.
BST를 확장하여 각 노드가 왼쪽 하위 트리에 요소 수를 저장하도록 할 수 있습니다 (주어진 노드의 왼쪽 하위 트리에 해당 노드가 포함되어 있다고 가정). 이 정보를 사용하면 왼쪽 하위 트리의 요소 수를 반복적으로 요청하여 트리를 탐색하여 왼쪽 또는 오른쪽 하위 트리로 재귀할지 여부를 결정하는 것이 간단합니다.
이제 노드 T에 있다고 가정합니다.
T
.k
번째 최소. 따라서 문제를 k - num_elements(left subtree of T)
오른쪽 하위 트리 에서 가장 작은 요소 를 찾는 것으로 줄 입니다.k
우리가 찾는 문제를 줄일 수 있도록 일 최소의 왼쪽 서브 트리에 어딘가에 k
왼쪽 하위 트리에서 일 가장 작은 요소.복잡성 분석 :
이것은 균형이 잡힌 BST의 경우 최악의 경우 또는 임의의 BST의 경우 평균적으로 O(depth of node)
시간 이 걸립니다 .O(log n)
O(log n)
BST에는 O(n)
스토리지 가 필요하며 O(n)
요소 수에 대한 정보를 저장하려면 다른 스토리지 가 필요합니다 . 모든 BST 작업 O(depth of node)
에는 시간이 걸리며 O(depth of node)
노드의 삽입, 삭제 또는 회전을 위해 "요소 수"정보를 유지하는 데 추가 시간 이 걸립니다 . 따라서 왼쪽 하위 트리의 요소 수에 대한 정보를 저장하면 BST의 공간 및 시간 복잡성이 유지됩니다.
더 간단한 해결책은 inorder traversal을 수행하고 현재 인쇄 할 요소를 추적하는 것입니다 (인쇄하지 않음). 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 traversal을 수행하고 카운터 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;
}
// 재귀없이 자바 버전 추가
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 솔루션과의 차이점) :
if( counter == k )
테스트는 (a) 왼쪽 하위 트리 뒤, (b) 루트 뒤, (c) 오른쪽 하위 트리 뒤의 세 위치 에서 필요합니다 . 이는 모든 위치 에 대해 즉, 위치 하는 하위 트리에 관계없이 k 번째 요소가 감지되도록하기위한 것 입니다.
if( result == -1 )
결과 요소 만 인쇄 되는지 확인하는 데 필요한 테스트입니다 . 그렇지 않으면 k 번째에서 가장 작은 요소부터 루트까지 모든 요소가 인쇄됩니다.
O(k + d)
. 여기서는 d
트리의 최대 깊이입니다. 따라서 전역 변수를 사용 counter
하지만이 질문에는 불법입니다.
이것은 잘 작동합니다 : 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;
}
이것이 문제에 대한 최적의 해결책은 아니지만 일부 사람들이 흥미로울 것이라고 생각하는 또 다른 잠재적 인 해결책입니다.
/**
* 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
완전한 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 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)을 호출하여 트리의 루트를 전달하여 얻을 수 있습니다.
다음 은 k 번째로 작은 요소 를 반환 하지만 k를 ref 인수로 전달해야하는 C # 의 간결한 버전입니다 (@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) 시간에 inorder traversal 사용 2. k + log n 시간에 Augmented tree 사용
나는 더 나은 알고리즘을 찾을 수 없었습니다. 그래서 하나를 작성하기로 결정했습니다. :) 이것이 잘못된 경우 저를 수정하십시오.
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));
}
}
다음은 자바 코드입니다.
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) : k <0 인 경우 :
k == 0 인 경우 None을 반환합니다. else : k-= 1 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 );
}}
보조 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;
}
}
}
Python 솔루션 시간 복잡성 : O (n) 공간 복잡성 : O (1)
아이디어는 Morris Inorder Traversal 을 사용하는 것입니다.
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 )
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 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;
}
}