티스토리 뷰

Index Tree 는 말 그대로 tree를 index로 관리하는 것을 의미한다. 

즉, left, right child, parent 를 직접적으로 연결하지 않고, balanced binary tree 라는 가정 하에 수식으로 부모와 자식 index 를 계산한다. 

 

Index Tree 는 이진 트리 역할을 하면서 주로 구간 최대값, 최소값, 구간합 등을 구할 때 사용된다. 

이진 트리이므로 트리 전체를 만드는데는 O(N log N), 트리를 업데이트 하는데는 O(log N) 이 소요된다. 

 

단순히 특정 구간의 최대, 최소, 합 등을 구할 때 O(M) (구간 길이 M) 이지만, 쿼리가 매우 많다면 O(QM) 이므로 이를 O(Q log M) 로 줄일 수 있다. 

또한 기존 입력의 값이 수시로 업데이트 되어도 트리 업데이트 시에는 O(log N) 이 소요되므로 

잦은 업데이트, 잦은 쿼리 시에 아주 유용하다. 

 


 

Index Tree 구성은 다음과 같다. 

 

1. 트리 만들기

입력으로 주어진 값이 N 개일 때, 입력의 첫 번째 값을 트리 마지막 depth 의 첫 번째 값으로 간주한다. 

즉, 입력이 20개라면 int(log_2 20) = 4 이므로 4+1 = 5 depth 까지 존재한다. (depth 0 = root) 

 

ex) 입력 20 개 일 때 tree의 index 구분

depth 0 : 1

depth 1 : 2 ~ 3

depth 2 : 4 ~ 7

depth 3 : 8 ~ 15

depth 4 : 16 ~ 31

depth 5 : 32 ~ 63 

 

(그림판으로 대충 그린 그림.....)

입력 8개인 경우를 예로 들면,

검은 숫자는 기존 입력 index, 빨간 숫자는 tree의 index, 초록 숫자는 tree의 depth 이다. 

 

이와 같이 balanced binary tree 인 환경에서는 굳이 child, parent 연결 없이 단순 계산으로 부모와 자식을 연결할 수 있다.

(그냥 index / 2 하면 부모)

 

최대값, 최소값, 구간합 경우 모두 tree 만드는 논리는 동일하다. 

그냥 index / 2 하면 부모 노드이므로 index / 2 번째 값과 업데이트를 진행하면 된다. 

이를 전체 리프 노드에 대해 진행하면 된다.

 

코드는 다음과 같다. (32는 예시로 input을 20개로 잡았기 때문에 tree 기준 index 계산 시 +32를 한 것 뿐)

void updateTree(int arrIdx)
{
	// 트리 업데이트 하기 
	int treeIdx = 32 + arrIdx;
	int num = arr[arrIdx];
	
	while(treeIdx > 0) {
		indexTree[treeIdx] = max(indexTree[treeIdx], num);
		treeIdx /= 2;	
	}
}

 

구간합일 때 업데이트 하는 코드

void updateTree(int arrIdx)
{
	// 트리 업데이트 하기 
	int treeIdx = 32 + arrIdx;
	int num = arr[arrIdx];
	indexTree[treeIdx] = num;
	
	treeIdx /= 2;
	
	while(treeIdx > 0) {
		indexTree[treeIdx] = indexTree[2 * treeIdx] + indexTree[2 * treeIdx + 1];
		treeIdx /= 2;
	}
}

 


 

2. 입력 업데이트 하기

 

기존 배열의 값이 업데이트되어도 위 updateTree 에 동일하게 index 만 넣으면 되므로 생략한다. 

 


 

3. 최대값, 최소값, 구간합 구하기

 

최대값, 최소값을 구하는 방식은 max, min 만 차이가 있으며, 구간합을 구하는 것도 sum을 할 것이냐 말것이냐의 차이이다. 

그러므로 최대값인 경우로 설명을 하면 다음과 같다.

 

start ~ end 범위까지의 최대값을 구할 때, 

start 가 짝수인 경우, 부모에 자신의 정보가 포함된 값이 기록되어 있으므로 바로 부모 노드로 간다. 

(start = start / 2)

홀수인 경우, 부모에 left child 정보가 포함되어 있으므로, 자신과 최대값을 비교하여 업데이트 후, 옆 부모로 간다.

(start = (start + 1) / 2)

 

end 가 짝수 인 경우, 부모에 right child 정보가 포함되어 있으므로, 자신과 최대값을 비교하여 업데이트 후, 옆 부모로 간다. 

(end = (end - 1) / 2)

홀수인 경우, 부모에 자신의 정보가 포함된 값이 기록되어 있으므로 바로 부모 노드로 간다. 

(end = end / 2)

 

이에 대한 코드는 다음과 같다. (32는 예시로 input을 20개로 잡았기 때문에 tree 기준 index 계산 시 +32를 한 것 뿐)

int getMaxNumInRange(int arrStartIdx, int arrEndIdx)
{
	// startIdx, endIdx : tree 기준 
	int startIdx = 32 + arrStartIdx;
	int endIdx = 32 + arrEndIdx;
	
	int res = 0;
	
	// start index 짝수 : 부모에 자신 정보 포함하여 기록 중 
	// 바로 부모 노드로 index 변경 (start / 2)
	// start index 홀수 : 부모에 부모의 left child 정보 포함하여 기록 중
	// 자신과 비교 후 (start + 1) / 2 노드로 index 변경 (옆 부모 노드로 이동)
	
	// end index 짝수 : 부모에 부모의 right child 정보 포함하여 기록 중 
	// 자신과 비교 후 (end - 1) / 2 노드로 index 변경 (옆 부모 노드로 이동)
	// end index 홀수 : 부모에 자신 정보 포함하여 기록 중 
	// 바로 부모 노드로 index 변경 (end / 2)
	
	// start <= end 일 때 동안 반복  
	
	while(startIdx <= endIdx) {
		if(startIdx % 2 == 0)
			startIdx /= 2;
		else {
			res = max(res, indexTree[startIdx]);
			startIdx = (startIdx + 1) / 2;
		}
		
		if(endIdx % 2 == 0) {
			res = max(res, indexTree[endIdx]);
			endIdx = (endIdx - 1) / 2;
		}
		else {
			endIdx /= 2;
		}
	} 
	
	return res;
}

 

구간합 구하기 코드

int getSumInRange(int arrStartIdx, int arrEndIdx)
{
	// startIdx, endIdx : tree 기준 
	int startIdx = 32 + arrStartIdx;
	int endIdx = 32 + arrEndIdx;
	
	int res = 0;
	
	// start index 짝수 : 부모에 자신 정보 포함하여 기록 중 
	// 바로 부모 노드로 index 변경 (start / 2)
	// start index 홀수 : 부모에 부모의 left child 정보 포함하여 기록 중
	// 자신과 합한 후 (start + 1) / 2 노드로 index 변경 (옆 부모 노드로 이동)
	
	// end index 짝수 : 부모에 부모의 right child 정보 포함하여 기록 중 
	// 자신과 합한 후 (end - 1) / 2 노드로 index 변경 (옆 부모 노드로 이동)
	// end index 홀수 : 부모에 자신 정보 포함하여 기록 중 
	// 바로 부모 노드로 index 변경 (end / 2)
	
	// start <= end 일 때 동안 반복  
	
	while(startIdx <= endIdx) {
		if(startIdx % 2 == 0)
			startIdx /= 2;
		else {
			res = res + indexTree[startIdx];
			startIdx = (startIdx + 1) / 2;
		}
		
		if(endIdx % 2 == 0) {
			res = res + indexTree[endIdx];
			endIdx = (endIdx - 1) / 2;
		}
		else {
			endIdx /= 2;
		}
	} 
	
	return res;
}

 


 

따라서 전체 코드는 다음과 같다. 

#include <iostream>
#include <cstdlib>
#include <algorithm>

using namespace std;

// 구간 최대값 구하기  

int arr[20] = {0, }; // 기존 입력 값
int indexTree[64] = {0, }; // 인덱스 트리 값 (20개 입력 => depth 5 : 32개 => 전체 트리 : 1+2+4+..+32=63) 
// 1번째 값이 tree의 root (depth 0) 

void init()
{
	for(int i = 0; i < 20; i++) {
		arr[i] = rand() % 9 + 1;
	}
	
	// 길이 n 배열에 대해 인덱스 트리 만들기
	// 전체 depth = int(log_2 n) + 1 
	// 20인 경우, log_2 20 = 4.xxxx
	// depth : 0 ~ 5
	// depth 0 : indexTree[1]
	// depth 1 : indexTree[2:4]
	// depth 2 : indexTree[4:8]
	// depth 3 : indexTree[8:16]
	// depth 4 : indexTree[16:32]
	// depth 5 : indexTree[32:64]
	
	// 마지막 depth 먼저 채우기 
	for(int i = 0; i < 20; i++) {
		indexTree[32 + i] = arr[i];
	}	
}

void updateTree(int arrIdx)
{
	// 트리 업데이트 하기 
	int treeIdx = 32 + arrIdx;
	int num = arr[arrIdx];
	
	while(treeIdx > 0) {
		indexTree[treeIdx] = max(indexTree[treeIdx], num);
		treeIdx /= 2;	
	}
}

int getMaxNumInRange(int arrStartIdx, int arrEndIdx)
{
	// startIdx, endIdx : tree 기준 
	int startIdx = 32 + arrStartIdx;
	int endIdx = 32 + arrEndIdx;
	
	int res = 0;
	
	// start index 짝수 : 부모에 자신 정보 포함하여 기록 중 
	// 바로 부모 노드로 index 변경 (start / 2)
	// start index 홀수 : 부모에 부모의 left child 정보 포함하여 기록 중
	// 자신과 비교 후 (start + 1) / 2 노드로 index 변경 (옆 부모 노드로 이동)
	
	// end index 짝수 : 부모에 부모의 right child 정보 포함하여 기록 중 
	// 자신과 비교 후 (end - 1) / 2 노드로 index 변경 (옆 부모 노드로 이동)
	// end index 홀수 : 부모에 자신 정보 포함하여 기록 중 
	// 바로 부모 노드로 index 변경 (end / 2)
	
	// start <= end 일 때 동안 반복  
	
	while(startIdx <= endIdx) {
		if(startIdx % 2 == 0)
			startIdx /= 2;
		else {
			res = max(res, indexTree[startIdx]);
			startIdx = (startIdx + 1) / 2;
		}
		
		if(endIdx % 2 == 0) {
			res = max(res, indexTree[endIdx]);
			endIdx = (endIdx - 1) / 2;
		}
		else {
			endIdx /= 2;
		}
	} 
	
	return res;
}

void printTree()
{
	// depth 0
	cout << "depth 0 : " << indexTree[1] << '\n'; 
	// depth 1
	cout << "depth 1 : ";
	for(int i = 2; i < 4; i++) {
		cout << indexTree[i] << " ";
	}
	cout << '\n';
	// depth 2
	cout << "depth 2 : ";
	for(int i = 4; i < 8; i++) {
		cout << indexTree[i] << " ";
	}
	cout << '\n';
	// depth 3
	cout << "depth 3 : ";
	for(int i = 8; i < 16; i++) {
		cout << indexTree[i] << " ";
	}
	cout << '\n';
	// depth 4
	cout << "depth 4 : ";
	for(int i = 16; i < 32; i++) {
		cout << indexTree[i] << " ";
	}
	cout << '\n';
	// depth 5
	cout << "depth 5 : ";
	for(int i = 32; i < 64; i++) {
		cout << indexTree[i] << " ";
	}
	cout << '\n';
	
	cout << "\n================\n";
}

int main()
{
	init();
	
	for(int i = 0; i < 20; i++) {
		updateTree(i);
	}
	
	// 초기 트리 출력 
	printTree();
	
	// 범위에 대해 계산 
	cout << "5 ~ 10 : " << getMaxNumInRange(5, 10) << '\n';
	cout << "\n================\n";
	
	// 숫자 업데이트 
	int cnt = 2;
	while(cnt--) {
		int idx = rand() % 20;
		arr[idx] = rand() % 99 + 1;
		updateTree(idx);
		
		printTree();
		
		cout << "5 ~ 10 : " << getMaxNumInRange(5, 10) << '\n';
		cout << "10 ~ 15 : " << getMaxNumInRange(10, 15) << '\n';
		cout << "15 ~ 19 : " << getMaxNumInRange(15, 19) << '\n';
		cout << "\n================\n";
	}
}

 

결과

 

 

구간합 전체 코드

#include <iostream>
#include <cstdlib>
#include <algorithm>

using namespace std;

// 구간합 구하기  

int arr[20] = {0, }; // 기존 입력 값
int indexTree[64] = {0, }; // 인덱스 트리 값 (20개 입력 => depth 5 : 32개 => 전체 트리 : 1+2+4+..+32=63) 
// 1번째 값이 tree의 root (depth 0) 

void init()
{
	for(int i = 0; i < 20; i++) {
		arr[i] = rand() % 9 + 1;
	}
	
	// 길이 n 배열에 대해 인덱스 트리 만들기
	// 전체 depth = int(log_2 n) + 1 
	// 20인 경우, log_2 20 = 4.xxxx
	// depth : 0 ~ 5
	// depth 0 : indexTree[1]
	// depth 1 : indexTree[2:4]
	// depth 2 : indexTree[4:8]
	// depth 3 : indexTree[8:16]
	// depth 4 : indexTree[16:32]
	// depth 5 : indexTree[32:64]
	
	// 마지막 depth 먼저 채우기 
	for(int i = 0; i < 20; i++) {
		indexTree[32 + i] = arr[i];
	}	
}

void updateTree(int arrIdx)
{
	// 트리 업데이트 하기 
	int treeIdx = 32 + arrIdx;
	int num = arr[arrIdx];
	indexTree[treeIdx] = num;
	
	treeIdx /= 2;
	
	while(treeIdx > 0) {
		indexTree[treeIdx] = indexTree[2 * treeIdx] + indexTree[2 * treeIdx + 1];
		treeIdx /= 2;
	}
}

int getSumInRange(int arrStartIdx, int arrEndIdx)
{
	// startIdx, endIdx : tree 기준 
	int startIdx = 32 + arrStartIdx;
	int endIdx = 32 + arrEndIdx;
	
	int res = 0;
	
	// start index 짝수 : 부모에 자신 정보 포함하여 기록 중 
	// 바로 부모 노드로 index 변경 (start / 2)
	// start index 홀수 : 부모에 부모의 left child 정보 포함하여 기록 중
	// 자신과 합한 후 (start + 1) / 2 노드로 index 변경 (옆 부모 노드로 이동)
	
	// end index 짝수 : 부모에 부모의 right child 정보 포함하여 기록 중 
	// 자신과 합한 후 (end - 1) / 2 노드로 index 변경 (옆 부모 노드로 이동)
	// end index 홀수 : 부모에 자신 정보 포함하여 기록 중 
	// 바로 부모 노드로 index 변경 (end / 2)
	
	// start <= end 일 때 동안 반복  
	
	while(startIdx <= endIdx) {
		if(startIdx % 2 == 0)
			startIdx /= 2;
		else {
			res = res + indexTree[startIdx];
			startIdx = (startIdx + 1) / 2;
		}
		
		if(endIdx % 2 == 0) {
			res = res + indexTree[endIdx];
			endIdx = (endIdx - 1) / 2;
		}
		else {
			endIdx /= 2;
		}
	} 
	
	return res;
}

void printTree()
{
	// depth 0
	cout << "depth 0 : " << indexTree[1] << '\n'; 
	// depth 1
	cout << "depth 1 : ";
	for(int i = 2; i < 4; i++) {
		cout << indexTree[i] << " ";
	}
	cout << '\n';
	// depth 2
	cout << "depth 2 : ";
	for(int i = 4; i < 8; i++) {
		cout << indexTree[i] << " ";
	}
	cout << '\n';
	// depth 3
	cout << "depth 3 : ";
	for(int i = 8; i < 16; i++) {
		cout << indexTree[i] << " ";
	}
	cout << '\n';
	// depth 4
	cout << "depth 4 : ";
	for(int i = 16; i < 32; i++) {
		cout << indexTree[i] << " ";
	}
	cout << '\n';
	// depth 5
	cout << "depth 5 : ";
	for(int i = 32; i < 64; i++) {
		cout << indexTree[i] << " ";
	}
	cout << '\n';
	
	cout << "\n================\n";
}

int main()
{
	init();
	
	for(int i = 0; i < 20; i++) {
		updateTree(i);
	}
	
	// 초기 트리 출력 
	printTree();
	
	// 범위에 대해 계산 
	cout << "5 ~ 10 : " << getSumInRange(5, 10) << '\n';
	cout << "\n================\n";
	
	// 숫자 업데이트 
	int cnt = 2;
	while(cnt--) {
		int idx = rand() % 20;
		arr[idx] = rand() % 99 + 1;
		updateTree(idx);
		
		printTree();
		
		cout << "5 ~ 10 : " << getSumInRange(5, 10) << '\n';
		cout << "10 ~ 15 : " << getSumInRange(10, 15) << '\n';
		cout << "15 ~ 19 : " << getSumInRange(15, 19) << '\n';
		cout << "\n================\n";
	}
}

 

결과

'알고리즘' 카테고리의 다른 글

C++ lower_bound, upper_bound  (0) 2024.06.22
C++ map, set struct에 대해 만들기  (0) 2024.06.22
combination, permutation 정리  (0) 2024.05.01
KMP 알고리즘 정리  (1) 2024.05.01
Trie 정리  (1) 2024.05.01
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
TAG
more
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
글 보관함