2016-12-12 6 views
2

나는 목표에 가장 가까운있는 BST에서 K 값을 찾는 방법에 대한 연구, 그리고 규칙에 다음과 같은 구현 건너 온거야 : 비어 있지 않은 이진 검색 트리 감안할 때자바 : BST에서 타겟에 가장 가까운 k 값을 찾는 방법은 무엇입니까?

과 목표 값 , 목표에 가장 가까운 BST에서 k 값을 찾으십시오.

참고 :

감안할 때 목표 값은 부동 소수점입니다. k가 항상 유효하다고 가정 할 수 있습니다. 즉, k ≤ 총 노드입니다. BST에는 대상과 가장 가까운 k 개의 고유 한 값 집합 만있을 수 있습니다. BST가 균형 잡힌 것으로 가정합니다.

그리고 구현의 생각은 :

, 우리는 우리가하는 일처럼 전임자와 후임자를 추적하는 두 개의 스택을 사용할 수있는 대상에 전임자와 가장 가까운 노드의 후계자 비교 병합 정렬에서는 대상과 가장 가까운 것을 비교하여 결과 목록에 배치합니다. 우리가 알고 있듯이, inorder traversal은 이전의 정렬 된 것을 제공하는 반면 reverse-inorder traversal은 정렬 된 후임을 제공합니다.

코드 :

import java.util.*; 

class TreeNode { 
    int val; 
    TreeNode left, right; 

    TreeNode(int x) { 
     val = x; 
    } 
} 


public class ClosestBSTValueII { 
    List<Integer> closestKValues(TreeNode root, double target, int k) { 
      List<Integer> res = new ArrayList<>(); 

      Stack<Integer> s1 = new Stack<>(); // predecessors 

      Stack<Integer> s2 = new Stack<>(); // successors 

      inorder(root, target, false, s1); 
      inorder(root, target, true, s2); 

      while (k-- > 0) { 
      if (s1.isEmpty()) { 
       res.add(s2.pop()); 
      } else if (s2.isEmpty()) { 
       res.add(s1.pop()); 
      } else if (Math.abs(s1.peek() - target) < Math.abs(s2.peek() - target)) { 
       res.add(s1.pop()); 
      } else { 
       res.add(s2.pop()); 
      } 
      } 

      return res; 
     } 

    // inorder traversal 
    void inorder(TreeNode root, double target, boolean reverse, Stack<Integer> stack) { 

     if (root == null) { 
      return; 
     } 

     inorder(reverse ? root.right : root.left, target, reverse, stack); 
     // early terminate, no need to traverse the whole tree 
     if ((reverse && root.val <= target) || (!reverse && root.val > target)) { 
      return; 
     } 
     // track the value of current node 
     stack.push(root.val); 
     inorder(reverse ? root.left : root.right, target, reverse, stack); 
    } 

    public static void main(String args[]) { 
     ClosestBSTValueII cv = new ClosestBSTValueII(); 

     TreeNode root = new TreeNode(53); 
     root.left = new TreeNode(30); 
     root.left.left = new TreeNode(20); 
     root.left.right = new TreeNode(42); 
     root.right = new TreeNode(90); 
     root.right.right = new TreeNode(100); 

     System.out.println(cv.closestKValues(root, 40, 2)); 
    } 
} 

그리고 내 질문에, 어떤 두 스택을 가진 이유는 그리고 방법의 순서 좋은 방법입니다? 각각의 목적은 무엇입니까? 하나의 스택으로는 충분하지 않을까요?

과 같이 reverse 부울 값은 무엇입니까? if ((reverse && root.val <= target) || (!reverse && root.val > target))의 경우 왜 일찍 해지합니까?

감사합니다. 미리 답변 해 주시고 답변을 드리겠습니다.

+0

코드가 이미 작동하는 경우이 질문은 [코드 검토] (http://codereview.stackexchange.com) 사이트에서 더 잘 맞을 수 있습니다. –

+0

귀하의 질문을 이해하고 있는지 확실하지 않습니다. 후임 노드를 찾으시겠습니까? 재귀 적 접근으로이를 수행 할 수 있습니다. – Baxtex

+0

@Baxtex 나는이 접근법을 이해하려고 노력하고 있습니다. –

답변

0

두 개의 스택이 필요한 이유는 트리를 두 방향으로 탐색해야하며 각 스택의 현재 값을 검색하는 값과 비교해야합니다. 검색된 값 또는 k/2보다 크고 k/2보다 낮음).

당신은 Stack of Stack 대신 StackNote를 사용해야한다고 생각합니다. 재귀를 피할 수 있습니다.

UPDATE : 동시에 초기 스택을 구축 할

1) 트리에서 가장 가까운 값을 찾습니다 :

내가 알고리즘의 두 단계를 참조하십시오.

2) 스택 사본을 만들고, 하나의 요소를 뒤로 이동시켜 두 번째 스택을 제공합니다. 최대 k 번 반복합니다. 각 스택의 맨 위에있는 두 요소 중 검색된 값과 가장 가까운 것을 확인하고 결과 목록에 추가 한 다음 스택을 앞뒤로 이동합니다.

업데이트 2 : 약간의 코드

public static List<Integer> closest(TreeNode root, int val, int k) { 
    Stack<TreeNode> right = locate(root, val); 
    Stack<TreeNode> left = new Stack<>(); 
    left.addAll(right); 
    moveLeft(left); 
    List<Integer> result = new ArrayList<>(); 
    for (int i = 0; i < k; ++i) { 
     if (left.isEmpty()) { 
      if (right.isEmpty()) { 
       break; 
      } 
      result.add(right.peek().val); 
      moveRight(right); 
     } else if (right.isEmpty()) { 
      result.add(left.peek().val); 
      moveLeft(left); 
     } else { 
      int lval = left.peek().val; 
      int rval = right.peek().val; 
      if (Math.abs(val-lval) < Math.abs(val-rval)) { 
       result.add(lval); 
       moveLeft(left); 
      } else { 
       result.add(rval); 
       moveRight(right); 
      } 
     } 
    } 
    return result; 
} 

private static Stack<TreeNode> locate(TreeNode p, int val) { 
    Stack<TreeNode> stack = new Stack<>(); 
    while (p != null) { 
     stack.push(p); 
     if (val < p.val) { 
      p = p.left; 
     } else { 
      p = p.right; 
     } 
    } 
    return stack; 
} 

private static void moveLeft(Stack<TreeNode> stack) { 
    if (!stack.isEmpty()) { 
     TreeNode p = stack.peek().left; 
     if (p != null) { 
      do { 
       stack.push(p); 
       p = p.right; 
      } while (p != null); 
     } else { 
      do { 
       p = stack.pop(); 
      } while (!stack.isEmpty() && stack.peek().left == p); 
     } 
    } 
} 

private static void moveRight(Stack<TreeNode> stack) { 
    if (!stack.isEmpty()) { 
     TreeNode p = stack.peek().right; 
     if (p != null) { 
      do { 
       stack.push(p); 
       p = p.left; 
      } while (p != null); 
     } else { 
      do { 
       p = stack.pop(); 
      } while (!stack.isEmpty() && stack.peek().right == p); 
     } 
    } 
} 

UPDATE 3

은 충분 하나의 스택을 통과하지 않을까요?

inorder (역? ...)와 같은 반대 부울 값은 무엇입니까? 경우와 경우 ((! (||) & & root.val < = 대상 역 & & root.val> 대상) 역), 왜 일찍 을 종료 할을?

당신이 제공 한 솔루션이 어디서 왔는지는 모르겠지만 요약하면 정수의 두 목록 즉, 하나는 역순으로 나열합니다. 검색된 값에 도달하면 "초기"종료됩니다. 이 솔루션은 트리 전체를 순회해야하므로 매우 비효율적입니다. 물론 내 것은 훨씬 낫다. 그리고 그것은 주어진 규칙을 따른다.

0

발견 한 알고리즘에 대한 아이디어는 매우 간단합니다. 그들은 단지 target이 삽입되어야하는 곳에서 나무를 순서대로 탐색합니다. 그들은 선행자와 후계자를 저장하기 위해 두 개의 스택을 사용합니다. 트리를 예로 들어 보겠습니다.

 5 
    /\ 
    3 9 
/\ \ 
2 4 11 

대상을 8으로합시다. inorder 메서드 호출이 모두 완료되면 스택은 s1 = {2, 3, 4, 5}, s2 = {11, 9}이됩니다. 보시다시피, s1은 모든 후임자 인 targets2을 모두 포함합니다. 더욱이, 두 스택은 어떤 방식으로 정렬되어 각 스택의 top은 스택의 다른 모든 값보다 target에 더 가깝습니다. 결과적으로 우리는 k 값이 될 때까지 스택의 상단을 항상 비교하고 가장 가까운 값을 팝핑하는 것만으로 k 가장 가까운 값을 쉽게 찾을 수 있습니다. 알고리즘의 실행 시간은 O(n)입니다.

이제 질문에 대해. 나도 몰라,이 알고리즘을 효과적으로 스택을 사용하여 구현하는 방법. 스택의 문제점은 우리가 스택의 꼭대기에만 액세스 할 수 있다는 것입니다. 그러나 하나의 배열로 알고리즘을 구현하는 것은 매우 쉽습니다. 보통의 순서대로 트리를 탐색 할 수있게합니다. 예를 들면 다음과 같습니다 : arr = {2, 3, 4, 5, 9, 11}. 그런 다음 및 r 색인을 양쪽면의 목표 값에 가장 가까운 곳에 놓습니다. l = 3, r = 4 (arr[l] = 5, arr[r] = 9). 남은 것은 arr[l]arr[r]을 항상 비교하고 결과에 추가 할 항목을 선택하는 것입니다 (2 개 스택과 완전히 동일). 이 골재에는 또한 작업이 O(n) 걸립니다.

문제에 대한 접근 방식이 다소 우아하기는하지만 코드에서 이해하기가 너무 어려워 보입니다.

또 다른 실행 시간으로 문제에 대한 다른 접근법을 소개하고자합니다. 이 알고리즘은 k에 더 좋으며 이전 알고리즘보다 큰 알고리즘에 대해서는 시간이 더 걸리는 O(k*logn) 시간이 소요됩니다.

또한 TreeNode 클래스에 부모 노드에 대한 포인터를 저장합니다.그러면 우리는 노드의 전신 또는 후계자를 쉽게 찾을 수 있습니다. O(logn) 시간 (if you don't know how). 그래서, 먼저 traversals을하지 않고 tree의 전신과 후계자를 찾는다. 그런 다음 스택과 마찬가지로 수행하십시오. 선행 작업 \ 후속 작업을 비교하고 가장 가까운 것을 선택하고 가장 가까운 작업은 선행 작업 \ 후속 작업으로 이동하십시오.

나는 당신의 질문에 대답했고, 당신은 나의 설명을 이해했습니다. 그렇지 않다면 언제든지 물어보십시오!

+0

부모에게 포인터를 추가 할 수 없거나 포인터를 추가하고 싶지 않으면 두 개의 스택을 사용할 수있다. –

관련 문제