Inorder 및 preorder 시퀀스에서 이진 트리의 후위 순회 찾기

마지막 업데이트: 2022년 2월 25일 | 0개 댓글
  • 네이버 블로그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 트위터 공유하기
  • 카카오스토리 공유하기
kimtaehyun98

[탐색 알고리즘] 이진 탐색 알고리즘(Binary Search)

배열 < 1, 2, 3, 7, 9, 12, 21, 23, 27 >에서 3을 찾는 경우 알고리즘 진행 방식은 아래와 같다.

배열 시작 인덱스와 마지막 인덱스 값을 합하여 2로 나눈다. 그 인덱스에 해당하는 값이 3인지 확인한다.

arr[4]의 값 9와 찾는 값 3의 대소를 비교한다.

3이 Inorder 및 preorder 시퀀스에서 이진 트리의 후위 순회 찾기 9보다 작으므로 인덱스 4~8은 탐색의 범위에서 제외한다.(정렬된 데이터여서 가능)

0과 3을 더하고 2로 나눈다.(나머지는 버린다.)

결과가 1이므로 arr[1]의 값 2와 찾는 값 3이 일치하는지 비교한다.

arr[1]의 값 2와 찾는 값 3의 대소를 비교한다.

3이 2보다 크므로 인덱스 0~1은 탐색의 범위에서 제외한다.

2와 3을 더해서 2로 나눈다.

결과가 2이므로 arr[2]의 값 3과 찾는 값 3이 일치하는지 비교한다.

일치하므로 알고리즘을 종료한다.

탐색 대상이 절반씩 줄어드므로 시간 복잡도는 $O(logn)$을 가진다.

탐색 실패시 시작 인덱스가 마지막 인덱스보다 큰 경우 탐색이 종료된다.

이해가 가지 않는다면 아래 코드를 보고 탐색에 실패하는 경우를 생각해 보면 이해가 갈 것이다.

'컴퓨터 공학 > 알고리즘' 카테고리의 다른 글

[재귀] 재귀 vs 꼬리 재귀 (0) 2015.07.08
[탐색 알고리즘] 순차 탐색 알고리즘 vs 이진 탐색 알고리즘 (0) 2015.07.07
[탐색 알고리즘] 이진 탐색 알고리즘(Binary Search) (1) 2015.07.07
[탐색 알고리즘] 순차 탐색 알고리즘(Linear Search) (0) 2015.07.07
[컴퓨터 알고리즘 성능분석] 시간 복잡도 vs 공간 복잡도 (4) 2015.07.07
[컴퓨터 알고리즘 성능분석] 점근적 표기법 (Asymptotic Notation) (1) 2015.07.04

'컴퓨터 공학/알고리즘' Related Articles

  • [재귀] 재귀 vs 꼬리 재귀
  • [탐색 알고리즘] 순차 탐색 알고리즘 vs 이진 탐색 알고리즘
  • [탐색 알고리즘] 순차 탐색 알고리즘(Linear Search)
  • [컴퓨터 알고리즘 성능분석] 시간 복잡도 vs 공간 복잡도

김동규 2017.08.20 17:12

안녕하세요. 코드 중에 궁금한 사항이 있어서 글 남깁니다.
재귀 바이너리 서치 함수에서 BSearchRecur(ar, first, last, target);
이 부분에 return 을 사용하지 않은 이유와, 사용하지 않아도 결과값이 나오는 이유가 궁금합니다.

inorder 및 preorder 시퀀스에서 이진 트리의 후위 순회 찾기

효율적인 알고리즘 Inorder 및 preorder 시퀀스에서 이진 트리의 후위 순회 찾기 작성 포스트 오더 순회 주어진 바이너리 트리에서 무질서한 그리고 선주문 순서.

예를 들어 다음 트리를 고려하십시오.

Print Binary Tree

Input:

Inorder traversal is < 4, 2, 1, 7, 5, 8, 3, 6 >
Preorder traversal is


Output:

Postorder traversal is 4 2 7 8 5 6 3 1

간단한 해결책은 주어진 inorder 및 preorder 시퀀스에서 이진 트리를 구성한 다음 트리를 순회하여 postorder traversal을 인쇄하는 것입니다.


재귀적 postorder 호출에서 몇 가지 추가 정보를 전달하여 트리 구성을 피할 수 있습니다. 간단한 재귀적 후위 호출에서 왼쪽 하위 트리가 먼저 처리되고 그 다음 오른쪽 하위 트리가 처리되고 마지막으로 노드가 인쇄됩니다. postorder traversal을 인쇄하기 위해 유사한 작업을 수행할 수 있습니다.

아이디어는 루트 노드로 시작하는 것입니다. 루트 노드의 값은 사전 주문 시퀀스의 첫 번째 항목입니다. inorder 시퀀스에서 현재 루트 노드의 왼쪽 및 오른쪽 하위 트리의 경계를 찾습니다. 왼쪽 및 오른쪽 하위 트리 경계를 찾으려면 inorder 시퀀스에서 루트 노드 인덱스를 검색하십시오. inorder 시퀀스에서 루트 노드 이전의 모든 키는 왼쪽 하위 트리의 일부가 되고 루트 노드 이후의 모든 키는 오른쪽 하위 트리의 일부가 됩니다. 모든 트리 노드에 대해 이것을 재귀적으로적으로 반복하면 결국 트리에서 후위 순회를 수행하게 됩니다. 이 알고리즘은 C++, Inorder 및 preorder 시퀀스에서 이진 트리의 후위 순회 찾기 Java 및 Python에서 아래에 설명되어 있습니다.

코딩스토리

프로필사진

kimtaehyun98

« 2022/08 » Inorder 및 preorder 시퀀스에서 이진 트리의 후위 순회 찾기
1 2 3 4 5 6
7 8 910 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
    (1) (9) (1) (3) (2)

코딩스토리

Binary Index Tree(BIT, Fenwick Tree) 본문

Binary Index Tree(BIT, Fenwick Tree)

# 아래 링크의 글에 보충 설명을 더한 글입니다. 먼저 아래 글을 읽고 이해가 잘 안 되신다면 보는 걸 추천드려요

펜윅 트리 (바이너리 인덱스 트리)

블로그: 세그먼트 트리 (Segment Tree) 에서 풀어본 문제를 Fenwick Tree를 이용해서 풀어보겠습니다. Fenwick Tree는 Binary Indexed Tree라고도 하며, 줄여서 BIT라고 합니다. Fenwick Tree를 구현하려면, 어떤 수 X

Binary Index Tree의 정의는 wiki에 가서 찾아보는게 더 좋을 것이다.

BIT의 필요성

BIT란 쉽게 말해 빠른 속도로 구간 합을 구할 수 있게 도와주는 자료구조이다.

이는 Tree를 생성하는데 O(logN), 구간합을 구하는데 O(logN) 시간이 소요된다.

이렇게 접근하면 이해가 잘 안될 수 있으니 문제로 살펴보자.

첫째 줄에 수의 개수 N(1 ≤ N ≤ 1,000,000)과 M(1 ≤ M ≤ 10,Inorder 및 preorder 시퀀스에서 이진 트리의 후위 순회 찾기 000), K(1 ≤ K ≤ 10,000) 가 주어진다. M은 수의 변경이 일어나는 횟수이고, K는 구간의 합을 구하는 횟수이다. 그리고 둘째 줄부터 N+1번째 줄

백준 2042번 구간 합 구하기 문제이다.

일반적으로 구간 합을 구하는 문제에서 나 포함 알린이들은 Prefix Sum을 사용할 것이다.

일반적인 구간 합 문제는 당연히 Prefix Sum으로 해결이 가능하다.

이 알고리즘은 O(N) 시간안에 답을 구할 수 있게 해 준다.

그럼 저 문제도 Prefix Sum으로 구할 수 있을까?

N 제한만 보면 100만이기 때문에 되는 거 아닌가?라고 생각할 수 있지만 문제를 보면 매번 데이터의 변화가 생긴다.

Prefix Sum은 배열이 고정되어있을 때 O(N) 시간 안에 구할 수 있다.

그렇다면 이 문제에서 K번만큼 변한다고 하였으므로 매번 Prefix Sum알고리즘을 사용하려면

시간 복잡도는 O(1000000 * 10000)이 된다. 즉 O(10000000000), 100억의 시간 복잡도를 갖는다.

따라서 문제의 시간제한에 부합하지 않는다.

바로 이런 경우를 위해 우리는 BIT를 알아야 한다.

(갑자기 드는 생각인데 옛날에 코드 포스였는지 카카오 코테였는지 기억은 안 나는데 BIT 문제 나왔던 것 같은데. 그때 내가 Prefix Sum으로 풀다 포기했었는데 그때도 query 때문에 시간 초과였는데 이거였구나..)

BIT 알고리즘

본격적으로 BIT 알고리즘에 대해 알아보자.

1. L[i]는 i를 2진수로 나타냈을 때, 마지막 1의 위치(가장 오른쪽 1)가 나타내는 값 을 저장하고 있는 배열이다.

즉 3을 이진수로 나타냈을 때 가장 마지막 1은 2^0=1 이므로 L[3] = 1이다.

이와 마찬가지로 L[5] = 1, L[6] = 2, L[8] = 8, L[9] = 1, L[10] = 2, L[11] = 1, L[12] = 4, L[16] = 16이다.

(나타내는 값이 배열에 들어가야 한다!!)

2. Tree[i]는 A[i]로부터 앞으로 L[i]개의 합 을 저장하고 있는 배열이다.

난 처음에 이 부분이 제일 이해가 안 갔는데 그냥 국어를 못해서였다..

핵심 포인트는 "앞으로"이다.

이해를 돕기 위해 앞에 어떤 것을 나타내는지 추가해보았다.

Tree[1] 은 A[1]로부터 앞으로 L[1]개의 합이다. 즉 1개의 합이니까 위와 같다.

Tree[2] 은 A[2]로부터 앞으로 L[2]개의 합이다. 즉 2개의 합이니까 위와 같다.

Tree[4] 은 A[4]로부터 앞으로 L[4]개의 합이다. 즉 4개의 합이니까 위와 같다.

Tree[8] 은 A[8]로부터 앞으로 L[8]개의 합이다. 즉 8개의 합이니까 위와 같다.

말 그대로 Tree의 정의에 따라 그려놓은 것이다.

3. L[i] = i & -i

위의 트리를 그리려면 결국 L[i]를 알고 있어야 한다.

그 뜻인즉슨 L[i]를 구하는 시간 역시 시간복잡도에 중요한 영향을 끼친다는 것이다.

그럼 L[i]를 어떻게 빠르게 구할까?

먼저 "&" 연산자는 비트 연산에서 and 연산을 뜻한다는 것을 알고 있어야 한다.

and 연산 1 0
1 1 0
0 0 0

이제 여기선 모르는 사람이 있을 수 있지만 현재 우리들의 컴퓨터는 2의 보수 표현법을 사용한다.

아래의 링크에 잘 설명되어 있다. (본의 아니게 내 블로그를 홍보해버렸네ㅎㅎ)

이제 우리는 컴퓨터가 내부적으로 2의 보수 표현법을 사용하고 있다는 것을 알았다.

그럼 아래의 내용이 이해가 안 될 수가 Inorder 및 preorder 시퀀스에서 이진 트리의 후위 순회 찾기 없다.

여기서 ~은 1의 보수를 취한 것이다.

1의 보수를 취한다는 것은 각 비트를 0->1, Inorder 및 preorder 시퀀스에서 이진 트리의 후위 순회 찾기 1->0로 반전한다는 의미이다.

결국 위의 과정을 통해 O(1) 시간만에 L[i]를 구할 수 있게 되었다.

4. 합 구하기

그럼 이제 실전으로 들어가 보자.

아래와 같은 BIT를 만들었다고 가정해보자.

위 트리는 L[i]를 생략한 모습이다.

그럼 여기서 문제는 합은 어떻게 구해야 할까?

예를 들어 Inorder 및 preorder 시퀀스에서 이진 트리의 후위 순회 찾기 A[1] + A[2] + . A[13]를 구해야 한다고 가정해보자.

여기서 다시 기억해내야 하는 사실은 Tree[i]는 A[i]로 부터 앞으로 L[i]개의 을 저장하고 있다는 점이다.

그럼 다시 돌아와서 위의 성질을 사용해보면

A[1] + A[2] + . A[13] = Tree[13] + Prefix Sum[13-L[13]] = Tree[13] + Prefix Sum[12] 이다.

왜냐하면 Tree[13]은 A[13]으로 부터 앞으로 L[13] = 1개의 합을 저장하고 있으므로 A[13]과 같기 때문이다.

따라서 Prefix Sum[X] = Tree[X] + Prefix Sum[X-L[X]]란 식을 얻어낼 수 있다. (구현에 사용된다.)

Tree[13] + Prefix Sum[12] = Tree[13] + Tree[12] + Prefix Sum[12-L[12]] = Tree[13] + Tree[12] + Prefix Sum[8]이다.

Prefix Sum[8] = Tree[8] 이므로 최종적으로 구해야 할 것은 Tree[13] + Tree[12] + Tree[8]이다.

따라서 Prefix Sum[13] = Tree[13] + Tree[12] + Tree[8]이다.

5. 구현 1 - 합 구하기

위의 과정을 그대로 코드로 작성하면 된다.

아주 짧고 간결하다. 이게 Inorder 및 preorder 시퀀스에서 이진 트리의 후위 순회 찾기 과연 고난이도의 알고리즘이 맞는지 의심이 들 정도이다.

결국 while문 안의 아래 코드는

Prefix Sum[X] Inorder 및 preorder 시퀀스에서 이진 트리의 후위 순회 찾기 = Tree[X] + Prefix Sum[X-L[X]]을 그대로 코드로 옮긴 것이다.

6. 구현 2 - 업데이트(Inorder 및 preorder 시퀀스에서 이진 트리의 후위 순회 찾기 Tree 생성)

내가 BIT를 공부하면서 가장 궁금했던 것은 그럼 어떻게 Tree를 만들어 놓지? 였다.

애초에 위의 sum 함수도 Tree가 없으면 못 구하는거 아닌가?

맞는 말이긴 한데 당연히 만드는 방법이 있었다..

Update, 즉 Tree를 바꾸려면 자신이 영향을 끼치는 부분만 바꿔주면 된다.

위 그림을 보면 i를 변경했을 때 어느 부분을 update 해줘야 하는 건지 나와있다.

예를 들어 A[3]이 교체된다면 아래의 빨간 동그라미 친 부분들을 update 해주면 된다.

이를 코드로 나타내면 다음과 같다.

i = 3이고 num = 5라 가정해보자.

여기서 살짝 이해가 안 갈 수 있다.

tree[3] += num 하면 안 되는 거 아닌가?

실제 구현해서는 위의 num에 들어가는 수가 현재 tree를 처음 생성 하는지 or 원래 값을 변경 하는지에 따라 달라져야 한다!


0 개 댓글

답장을 남겨주세요