730 likes | 1.07k Views
6 장 인덱스 구조. 인덱스 (index). 특징 화일의 레코드들에 대한 효율적 접근을 위한 조직 < 키 값 , 레코드 주소 ( 포인터 )> 쌍으로 구성 종류 키 값의 유형에 따른 인덱스 기본 인덱스 (primary index) : 키 값이 기본 키인 인덱스 보조 인덱스 (secondary index) : 기본 인덱스 이외의 인덱스 화일 조직에 따른 인덱스 집중 인덱스 (clustered index) : 화일의 데이타 레코드들의 물리적 순서와 인덱스 엔트리 순서가 동일한 인덱스
E N D
인덱스(index) • 특징 • 화일의 레코드들에 대한 효율적 접근을 위한 조직 • <키 값, 레코드 주소(포인터)> 쌍으로 구성 • 종류 • 키 값의 유형에 따른 인덱스 • 기본 인덱스 (primary index) : 키 값이 기본 키인 인덱스 • 보조 인덱스 (secondary index) : 기본 인덱스 이외의 인덱스 • 화일 조직에 따른 인덱스 • 집중 인덱스 (clustered index) : 화일의 데이타 레코드들의 물리적 순서와 인덱스 엔트리 순서가 동일한 인덱스 • 비집중 인덱스(unclustered index) : 집중 형태가 아닌 인덱스 • 데이타 범위에 따른 인덱스 • 밀집 인덱스(dense index) : 데이타 레코드 하나에 하나의 인덱스 엔트리가 만들어지는 인덱스. 역 인덱스(inverted index) • 희소 인덱스(sparse index) : 데이타 레코드 그룹 또는 데이타 블록에 하나의 엔트리가 만들어지는 인덱스
이원 탐색 트리 (binary search tree) • 인덱스를 조직하는 한가지 방법 • 이진 트리(binary tree) • 유한한 수의 노드를 가진 트리 • 공백(empty)이거나 루트와 두 개의 분리된 이진 트리 즉, 왼쪽 서브트리(left subtree)와 오른쪽 서브트리(right subtree)로 구성 • 이원 탐색 트리 • 이진 트리 • 각 노드 Ni는 레코드 키 Ki와 이 키를 가지고 있는 레코드 포인터를 포함 • 공백이 아닌 이원 탐색 트리의 성질 • 모든 노드는 상이한 키 값들을 갖는다. • 루트 노드 Ni의 왼쪽 서브트리(Left(Ni))에 있는 모든 노드의 키 값들은 루트 노드의 키 값보다 작다. • 루트 노드 Ni의 오른쪽 서브트리(Right(Ni))에 있는 모든 노드의 키 값들은 루트 노드의 키 값보다 크다. • 왼쪽 서브트리와 오른쪽 서브트리는 모두 이원 탐색 트리이다.
28 15 30 12 32 11 42 70 14 30 5 37 10 44 (a) (b) (c) 이원 탐색 트리 • 이진 트리와 이원 탐색 트리 • 그림 (a): 이원 탐색 트리가 아님 • 그림 (b), (c): 이원 탐색 트리
▶ 이원 탐색 트리에서의 검색 • 루트 노드가 Ni인 이원 탐색 트리에서 키 값이 K인 노드를 검색하는 방법 • 트리가 공백 : 검색 실패 • K=Ki : 노드 Ni가 원하는 노드 • K<Ki : Ni의 왼쪽 서브트리를 검색, 즉 Ni ← Root(Left(Ni))로 하여 다시 검색 시작 • K>Ki : Ni의 오른쪽 서브트리를 검색 즉, Ni ← Root(Right(Ni))로하여 다시 검색 시작
▶ 이원 탐색 트리에서의 검색 • 연결 리스트로 표현된 이원 탐색 트리에서의 검색 • 노드는 key, left, right 필드로 구성 searchBST(B, s_key) // B는 이원 탐색 트리, s_key는 검색 키 값 p ← B; if (p = null) then // 공백 이진 트리로 실패 return null; if (p.key = s_key) then // 검색 성공 return p; if (p.key < s_key) then // 오른쪽 서브트리 검색 return searchBST(p.right, s_key); elsereturn searchBST(p.left, s_key); // 왼쪽 서브트리 검색 end searchBST()
15 15 15 11 70 11 70 11 70 5 13 5 13 50 5 (a) (b) 13 (c) 50 이원 탐색 트리 키값 을 삽입 키값 을 삽입 ▶ 이원 탐색 트리에서의 삽입 • 루트 노드가 Ni인 이원 탐색 트리에 키 값이 K인 노드를 삽입 • 트리가 공백 : K를 루트 노드로 삽입 • K=Ki : 트리에 똑같은 키 값이 이미 존재하므로 삽입을 거부 • K<Ki : Ni의 왼쪽 서브트리로 이동하여 계속 탐색 • K>Ki : Ni의 오른쪽 서브트리로 이동하여 계속 탐색 • K가 있어야 할 위치에 없어서 탐색이 실패로 끝나는 위치에 키 값이 K인 노드를 삽입 • 삽입 예 : 키 값 13, 50 을 삽입
▶ 이원 탐색 트리에서의 삽입 알고리즘 insertBST(B, new_key) // B는 이원 탐색 트리, new_key는 삽입할 키 값 p ← B; while (p ≠ null) do { // 삽입하려는 키 값을 가진 노드가 이미 있는지 검사 if (new_key = p.key) then return; q ← p; // q는 p의 부모 노드를 지시 if (new_key < p.key) then p ← p.left; else p ← p.right; } newNode ← getNode(); // 삽입할 노드를 만듦 newNode.key ← new_key; newNode.right ← null; newNode.left ← null; if (B = null) then B ← newNode; // 트리가 공백인 경우 elseif (new_key < q.key) then // q는 탐색이 실패로 종료하게 된 원소 q.left ← newNode; else q.right ← newNode; return; end insertBST()
▶ 이원 탐색 트리에서의 삭제 • 노드를 삭제한 뒤에도 트리는 계속 이원 탐색 트리의 성질을 유지해야 됨 • 삭제할 노드의 자식 수에 따라 상이한 삭제 연산 1. 자식이 없는 리프 노드의 삭제 • 단순히 그 노드를 삭제 2. 자식이 하나인 노드의 삭제 • 삭제되는 노드 자리에 그 자식 노드를 위치 3. 자식이 둘인 노드의 삭제 • 삭제되는 노드 자리에 왼쪽 서브트리에서 제일 큰 키 값이나 또는 오른쪽 서브트리에서 제일 작은 키 값으로 대체 • 해당 서브트리에서 대체 노드를 삭제 • 삭제는 포인터 값의 조정으로 구조 변경 • 삭제 표시(논리적 삭제)로 대체 가능 • 물리적으로 즉각 삭제하지 않는 경우 • 다만 검색, 삽입 시 : 삭제된 노드의 키 값을 참고로 이용 가능 • 정상적 키 값이 아니므로 특별 취급이 필요
50 50 50 50 30 55 30 55 30 55 30 55 20 52 60 52 60 25 20 40 52 60 20 52 60 25 25 25 (a) (b) 삭제 전 삭제 후 (a) (b) 삭제 전 삭제 후 이원 탐색 트리에서 자식이 하나인 노드(20)의 삭제 이원 탐색 트리에서 리프 노드(40)의 삭제 50 30 52 25 55 30 55 30 55 52 60 25 60 25 52 60 (a) (b) (c) 삭제 전 왼쪽 서브트리의 오른쪽 서브트리의 최대 키 값(30)으로 대체 최소 키 값(52)으로 대체 ▶ 이원 탐색 트리에서의 삭제 • 삭제 예 : 키 값 40, 20, 50 삭제 이원 탐색 트리에서 자식이 둘인 노드(50)의 삭제
▶ 이원 탐색 트리에서의 삭제 알고리즘 deleteBST(B, d_key) p ← node to be deleted; // 삭제할 키 d_key를 가진 노드 parent ← parent node of p; // 삭제할 노드의 부모 노드 if (p = null) then return; // 삭제할 원소가 없음 case { p.left=null and p.right=null : // 삭제할 노드가 리프 노드인 경우 if (parent.left = p) then parent.left ← null; else parent.right ← null; p.left=null or p.right = null : // 삭제할 노드의 차수가 1인 경우 if (p.left ≠ null) then { if (parent.left = p) then parent.left ← p.left; else parent.right ← p.left; } else { if (parent.left = p) then parent.left ← p.right; else parent.right ← p.right; } p.left ≠ null and p.right ≠ null : // 삭제할 노드의 차수가 2인 경우 q←maxNode(p.left); // 왼쪽 서브트리에서 최대 키 값을 탐색 p.key ← q.key; deleteBST(p.left, p.key); } end deleteBST()
60 25 55 30 52 52 30 55 25 60 ▶ 편향 이원 탐색 트리 • 편향 이원 탐색 트리(skewed binary search tree) • 리프 노드의 탐색 시간은 최악 • N개의 노드로 구성된 이원 탐색 트리에서 최악의 탐색 시간 : N번의 노드 탐색
▶ 이원 탐색 트리의 성능 • 성능 • 이원 탐색 트리의 성능은 트리의 형태와 노드에 대한 접근 빈도에 의존 • 우수한 성능을 위해서는 • 가장 자주 접근되는 노드는 루트에 가장 가깝게 유지 • 이원 탐색 트리를 균형 트리(balanced tree)로 유지 • 모든 노드에 대해 양쪽 서브트리의 노드 수가 가능한 똑같게 만들어 트리의 최대 경로 길이를 최소화 • 이원 탐색 트리의 단점 • 삽입, 삭제 후 효율적 접근을 위한 균형 유지 부담이 큼 • 작은 분기율(branching factor)에 따른 긴 탐색 경로와 검색 시간 • 분기율이 2: 각 노드는 많아야 두 개의 서브트리 • N개의 노드를 갖는 트리의 최소 높이 :
AVL 트리 • 1962년 러시아 수학자 Adelson-Velskii와 Landis가 고안한 높이 균형 이진 트리 (height-balanced binary tree) • 이진 트리의 높이: 루트에서 어떤 리프 까지의 가장 긴 경로의 길이 • AVL 높이 균형 이진 트리 • 삽입, 삭제, 검색 시간 : O(logN) • 트리의 일부만 재균형시키면서 트리 전체가 균형을 계속 유지할 수 있도록 함. • AVL 트리의 정의 • 각 노드의 왼쪽 서브트리의 높이와 오른쪽 서브트리의 높이 차이가 1 이하인 이원 탐색 트리 • ½h(Left(Ni)) – h(Right(Ni))½ ≤ 1, Ni∈ T • 공백 서브트리의 높이 : -1
▶ AVL 트리 • 균형 인수(balance factor,BF) • 왼쪽 서브트리의 높이에서 오른쪽 서브트리의 높이를 뺀 수, BF = h(Left(T)) – h(Right(T)) • 노드의 균형 인수가 • ±1 이하이면 이 노드는 AVL 성질(AVL property)을 만족하고 • ±2 이상이면 AVL 성질을 만족하지 못한다고 말함. • AVL 트리의 모든 노드는 균형 인수가 ±1 이하이다. 즉 AVL 트리의 모든 노드는 이 AVL 성질을 만족하고 있다.
8 8 8 8 8 5 10 12 9 12 4 4 4 3 6 6 10 9 10 16 16 7 10 16 2 3 6 6 3 2 6 4 4 7 15 14 18 17 11 5 5 9 6 12 13 ▶ AVL 트리와 non-AVL 트리 (a) (b) (c) AVL 트리 (a) (b) (c) non-AVL 트리
▶ AVL 트리에서의 검색과 삽입(1) • 검색 • 일반 이원 탐색 트리의 검색 방법과 동일 • 시간 복잡도 : O(logN) • 삽입 • 삽입되는 노드에서부터 루트까지의 경로에 있는 조상 노드들의 균형인수(bf)에 영향을 줄 수 있음 • 노드 삽입으로 AVL 트리가 non-AVL 트리가 되면 삽입된 노드와 가장 가까우면서 불균형이 된 조상 노드의 균형 인수가 ±1 이하로 되게 트리 구조를 조정해야 됨
1 2 12 12 1 1 1 2 16 16 8 8 0 0 0 0 0 1 14 14 4 10 4 10 1 0 0 0 2 2 6 6 0 1 (b) 원소 1의 삽입으로 non-AVL 트리로 변환 (a) AVL 트리 원소 1의 삽입으로 노드 8의 AVL 성질 상실 ▶ AVL 트리에서의 검색과 삽입(2) • 노드 삽입으로 균형 인수가 ± 2로 된 노드 x가 출현 • 다음 4가지 경우 중 하나로 인해 발생(노드x는 균형 인수가 ±2) LL : x의 왼쪽 자식(L)의 왼쪽 서브트리 (L) 에 삽입 RR : x의 오른쪽 자식(R)의 오른쪽 서브트리 (R) 에 삽입 LR : x의 왼쪽 자식 (L) 의 오른쪽 서브트리 (R) 에 삽입 RL : x의 오른쪽 자식(R)의 왼쪽 서브트리 (L) 에 삽입
2 0 A B LL 1 0 ⇒ B A T T 3 1 T T T T 1 2 2 3 ▶ AVL 트리에서의 검색과 삽입(3) • 회전(rotation):불균형이 발생할 때 트리 구조를 변경하여 균형을 잡아주는 것 • 단순 회전(single rotation) • 한 번의 회전만 필요한 균형: LL, RR • 탐색 순서를 유지 하면서 부모와 자식 원소의 위치를 교환 • 이중 회전(double rotation) • 두 번의 회전이 필요한 균형: LR, RL i) LL 회전
- 2 0 A B - 1 0 RR B A ⇒ T T 1 3 T T T T 2 3 1 2 2 0 A C - 1 LR(a) 0 0 B B A ⇒ C ▶ AVL 트리에서의 검색과 삽입(4) ii) RR 회전 0 0 iii) LR(a) 회전
2 0 A C - 1 0 - 1 LR(b) B B A ⇒ 1 C T 4 T T T T T 4 1 1 2 3 T T 2 3 2 0 A C - 1 1 0 B B A LR(c) - 1 ⇒ C T 4 T T T T T 4 1 1 2 3 T T 2 3 v) LR(c) 회전 ▶ AVL 트리에서의 검색과 삽입(5) iv) LR(b) 회전
- 2 0 A C RL(a) - 1 0 0 ⇒ B A B 0 C - 2 0 A C 1 1 0 RL(b) B A B ⇒ - 1 C T 1 T T T T T 4 1 2 3 4 T T 2 3 ▶ AVL 트리에서의 검색과 삽입(6) vi) RL(a) 회전 vii) RL(b) 회전
T T 2 3 T T 2 3 ▶ AVL 트리에서의 검색과 삽입(7) - 2 0 A C 1 0 - 1 RL(c) B A B ⇒ 1 C T 1 T T T 4 1 4 viii) RL(c) 회전 AVL 트리 회전
-1 -2 0 0 8 8 9 RR 8 -1 0 0 0 9 9 8 10 0 10 1 2 1 9 9 9 1 0 2 0 0 0 LL 8 10 8 10 2 10 0 1 0 0 2 2 8 1 0 1 ▶ AVL 트리에서의 검색과 삽입(8) • 키 리스트 (8, 9, 10, 2, 1, 5, 3, 6, 4, 7, 11, 12)를 차례대로 삽입하면서 AVL 트리를 구축하는 예 (a) 키 8 삽입 (b) 키 9 삽입 (c) 키 10 삽입 (d) 키 2 삽입 (e) 키 1 삽입
▶노드 삽입에 따른 균형 인수(BF)조정 • 노드 삽입으로 인한 균형 인수(BF)의 변화 • 새로 노드를 삽입할 때 BF에 영향을 받는 노드는 오직 이 새로운 노드에서부터 루트까지의 경로상에 있는 노드들이다. • 새로운 노드를 왼쪽 서브트리로 삽입하면 그 부모 노드의 BF를 하나 증가(+1)시키고 오른쪽 서브트리로 삽입하면 BF를 하나 감소(-1)시킨다. • 이때 이 BF가 0이 되면 BF 조정은 종료되고 아니면 다시 그의 부모 노드의 BF를 변경한다. 즉, 자기가 부모 노드의 왼쪽 서브트리이면 BF를 하나 증가(+1)시키고 오른쪽 서브트리이면 BF를 하나 감소(-1)시키면서 루트까지의 경로를 따라가면서 BF 조정 작업을 계속한다.
2 0 1 9 8 -1 0 0 -1 -1 -1 LR 2 10 2 9 0 0 0 0 0 1 0 1 1 1 8 5 5 10 10 0 0 5 3 (f) 키 5 삽입 1 2 1 8 8 8 -2 -1 0 -1 -1 -1 2 9 3 9 RL 2 9 0 1 1 0 0 0 0 0 0 1 2 5 5 10 10 0 1 0 5 -1 0 10 0 0 1 0 3 4 6 6 0 3 6 4 ▶ AVL 트리에서의 검색과 삽입(9) 8 2 9 1 (g) 키 3 삽입 (h) 키 6 삽입 (i) 키 4 삽입
2 0 8 5 -1 0 -1 1 9 8 3 3 1 -1 1 -1 0 0 -1 LR 2 5 2 4 6 10 9 0 0 0 -1 0 0 1 1 4 10 6 7 0 7 -1 0 5 5 -1 0 1 1 8 8 3 3 1 1 -1 -1 0 0 -2 0 RR 2 4 2 4 6 6 9 10 0 0 -1 0 0 0 0 1 1 10 11 7 7 9 0 11 ▶ AVL 트리에서의 검색과 삽입(10) (j) 원소 7 삽입 (k) 원소 11 삽입
▶ AVL 트리에서의 검색과 삽입(11) -1 5 -1 1 8 3 -1 0 1 -1 2 4 6 10 0 0 -1 0 1 11 7 9 0 12 (l) 키 12 삽입
▶ AVL 트리에서의 삽입 알고리즘(1) /* AVL 트리 삽입 알고리즘 */ insertAVL(new_key) if (root = new_key) then { //공백 트리인 경우 y ← getTreeNode(); y.key ← newKey; root ←y; root.bf ← 0; //bf는 균형 인수 root.left ← null; root.right ← null; return true; } f ← null; a ← p ← root; q ← null; found ← false; // phase 1: new_key의 삽입 위치(q) 조사 while (p ≠ null and found = false) do if (p.bf ≠ 0) then {a ← p; f ← q;} if (new_key < p.key) then {q ← p; p ← p.left;} //왼쪽 서브트리로 이동 else if (new_key > p.key) then {q ← p; p ← p.right;} //오른쪽 서브트리로 이동 else {y ← p; found ← true;} } // while // phase 2: new_key를 삽입하고 균형화
▶ AVL 트리에서의 삽입 알고리즘(2) // phase 2: new_key를 삽입하고 균형화 if (found = false) then { // new_key는 트리에 없음. // new_key를 q의 적절한 자식으로 삽입 y ← getTreeNode(); y.key ← newKey; y.left ← null; y.right ← null; y.bf ← 0; if (new_key < q.key) then q.left ← y; //q의 왼쪽 자식으로 삽입 else q.right ← y; //q의 오른쪽 자식으로 삽입 // a에서 q까지의 경로에 있는 노드의 균형 인수를 조정. // a의 정의에 따라 이 경로상에 있는 모든 노드들은 그 // 균형 인수가 0이어야 되기 때문에 이들의 균형 인수는 // ±1로 변경된다. 따라서 // d=1은 new_key가 a의 왼쪽 서브트리로 삽입되었다는 // 것을 의미하고 // d=-1은 a의 오른쪽 서브트리로 삽입되었다는 것을 //의미한다.
▶ AVL 트리에서의 삽입 알고리즘(3) if (new_key > a.key) then {p ←a.right; b ← p; d ← -1;} else {p ←a.left; b ← p; d ← 1;} while (p ≠ y) do { if (new_key > p.key) then {p.bf← -1; p ← p.right;} //오른쪽 서브트리의 높이가 1 증가 else {p.bf← 1; p ← p.left;} //왼쪽 서브트리의 높이가 1 증가 } //while (p ≠ y) unbalanced ← true; // 트리가 불균형인지를 검사 if (a.bf = 0 or a.bf+d = 0) then // 트리가 아직 균형 {a.bf ← a.bf+d; unbalanced ← false; } if (unbalanced = true) then // 트리가 불균형. 회전 유형을 결정 if (d = 1) then { //왼쪽 불균형 if (b.bf = 1) then { // LL 회전 타입 a.left ← b.right; b.right ←a; a.bf ← 0; b.bf ← 0;} } else { // LR 회전 타입 c ←b.right; b.right ← c.left; a.left ← c.right; c.left ←b; c.right ← a; switch (c.bf) { case 1 : a.bf ← -1; b.bf ← 0; break; //LR(b) case -1 : b.bf ← 1; a.bf ← 0; break; //LR(c) case 0 : b.bf ← 0; a.bf ← 0; break; //LR(a) } c.bf ← 0; b ← c; // b는 새로운 루트 } // else LR 회전 타입
▶ AVL 트리에서의 삽입 알고리즘(4) if (unbalanced = true) then // 트리가 불균형. 회전 유형을 결정 if (d = 1) then { //왼쪽 불균형 if (b.bf = 1) then { // LL 회전 타입 ************ } else { // LR 회전 타입 ************ } // else LR 회전 타입 } // 왼쪽 불균형 else { // 오른쪽 불균형. 왼쪽 불균형의 대칭 코드 ……………..// RR 회전 타입 ……………..// RL 회전 타입 } if (f = null) then root ← b; // b를 루트로하는 서브트리가 //균형을 맞추고 새로운 서브트리가 됨 else if (a = f.left) then f.right ← b; } // if(unbalanced = true) return true; } // if (found = false return false; end insertAVL()
▶ AVL 트리의 높이 • 높이 균형 이진 트리의 높이 • N개의 노드를 가진 높이 균형 이진 트리는 완전 균형 이진 트리보다 45% 이상 높아지지 않음 • log(N+1) ≤ h ≤ 1.4404log(N+2)-0.328 • 높이 균형 이진 트리 대 완전 균형 이진 트리 • O(1.4 log N) 대 O(log N) • 높이 균형 이진 트리의 탐색 시간이 더 길다 • 이유 : 트리의 전체 재균형을 수행하지 않기 때문 • 수 백만 개의 노드로 구성된 AVL트리가 디스크에 저장된 상태에서의 노드 탐색은 많은 횟 수의 디스크 접근을 요구한다. 그래서 m-원 탐색 트리를 고려하게 된다.
m-원 탐색 트리(m-way search tree) • 이원 탐색 트리보다 높은 분기율: m개 서브트리 • 장점 : 트리의 높이가 감소(특정 노드의 탐색 시간 감소) • 단점 : 삽입, 삭제시 트리의 균형 유지를 위해 복잡한 연산이 필요 • m-원 탐색 트리의 성질 ① 노드 구조는 <n, P0, K1, P1, K2, P2, … , Pn-1, Kn, Pn> (n: 키 값 수, 1≤n<m, Pi: 서브트리에 대한 포인터, Ki: 키 값) ② 한 노드에 있는 키 값들은 오름차순: Ki < Ki+1 , 1 ≤ i ≤ n-1 ③ Pi(0 ≤ i ≤ n-1)가 지시하는 서브트리 모든 노드들의 키 값 < Ki+1 ④ Pn이 지시하는 서브트리의 모든 노드들의 키값 > Kn ⑤ Pi가 지시하는 서브트리는 m-원 서브트리
a 100 140 b c d 30 60 110 120 170 200 e f g h i j 180 ^ ^ 40 50 70 90 150 160 220 ▶ 3-원 탐색 트리 ^ ^ 10 20 ★ 키 값 Ki: (Ki, Ai)를 의미하고, Ai는 키 값 Ki를 포함하고 있는 데이타 레코드의 주소
▶ m-원 탐색 트리의 검색 searchMT(key) // m-원 탐색 트리의 검색 알고리즘 // key : 검색 키 값 // x : 노드 (<n, P0, K1, P1, K2, P2, … , Pn-1, Kn, Pn>) // root : 루트 노드 // n : 노드에 있는 키의 개수 x ← root; while( x != null )do { i ← 1; n ← x.n; while( i <= n && key > x.Ki ) i ← i+1; if ( i <= x.n && key = x.Ki) thenreturn Ai; // 키 값 key를 가진 레코드의 주소를 반환 if (i > n) then x ← x.Pn else x ← x.Pi-1 } //while return null; // key와 일치하는 값이 트리에 없는 경우 end searchMT()
▶ m-원 탐색 트리의 분석 • m-원 탐색 트리의 탐색시간: 탐색 경로 길이(높이)에 비례 • 각 레벨에서는 한 개의 노드만 탐색 • 분기율(m)을 최대로 하면 트리의 높이가 낮아짐 • 한 노드에 m-1개 키 값을 저장하는 m-원 탐색트리 • 높이 h : n = (mh-1)개 키 값 저장 • (예) 4-원 탐색 트리: 높이가 3이면 n=(43-1)=63개의 키 값을 저장 • n개의 키를 가진 m-원 탐색트리 • 최소 높이 h = logm(N+1) • 최대 탐색 시간 : O(logm(N+1)) (예) m=2이면 : 이진 트리 탐색시간
B-트리 • 1972년 Bayer & McCreight가 제안 • 균형 m-원 탐색 트리 • 가장 많이 사용되는 인덱스 방법 • 효율적인 균형 알고리즘을 제공 • 차수(order)가m인 B-tree의 특성 ① B-트리는 공백이거나 높이가 1 이상인 m-원 탐색 트리 ② 루트와 리프를 제외한 내부 노드 - 최소 m/2 , 최대 m개의 서브트리 - 적어도 m/2 - 1개의 키 값(노드의 반 이상이 채워짐) ③ 루트 : 리프가 아니면 적어도 두 개의 서브트리를 가짐 ④ 모든 리프는 같은 레벨
▶ m차 B-트리 노드 구조 • 노드 구조 < n, p0, <K1, A1>, P1, <K2, A2>, P2, … , Pn-1, <Kn, An>, Pn> ① n : 키 값의 수(1≤n<m), P0, … , Pn : 서브트리에 대한 포인터, 각 키 값 Ki는 그 키 값을 가진 레코드에 대한 포인터Ai를 포함 ② 각 노드의 키 값들은 항상 오름차순(1≤i ≤n-1 Ki < Ki+1)을 유지 ③ Pi가 지시하는 서브트리의 키 값들은 모두 Ki+1 보다 작다. ④ Pn이 지시하는 서브트리의 키 값들은 모두 Kn보다 크다. ⑤ Pi(0≤i≤n)가 지시하는 서브트리들은 모두 m-원 서브 탐색 트리이다. • B-트리의 장점 • 삽입, 삭제 뒤에도 트리의 균형 상태를 유지 • 저장장치의 효율성 • 각 노드의 반 이상은 항상 키 값으로 채워짐
a ^ 69 b c 19 43 128 138 d e f g h i ^ ^ ^ ^ ^ 16 26 40 60 100 132 145 p j k l m n o q r s t u v 7 15 18 20 30 36 42 50 58 62 65 70 110 120 130 136 140 150 ▶ 3차 B-트리 구조
▶ B-트리에서의 연산 • 검색 : m-원 탐색 트리의 직접 검색과 같은 과정 • 직접 탐색 : 키 값에 따라 왼쪽 또는 오른쪽 서브트리로 분기 • 노드에서의 검색은 순차 검색 예) 키 값 42 검색 • B-트리 전체의 순차 검색은 트리의 중위 순회(inorder traversal)로 수행 • 삽입 : 새로운 키 값은 항상 리프 노드에 삽입 • 노드에 공간이 있는 경우 : 단순히 순서에만 맞게 삽입 • 노드에 공간이 없는 경우 : overflow로 split 발생 • 해당 노드를 두 개의 노드로 분할 • 해당 노드에 새로운 키 값 삽입했다고 가정 • 중간( m/2번째)키 값을 기준으로 왼쪽 작은 키 값들은 그대로 두고 오른쪽 큰 키 값들은 새로운 노드에 저장 • 중간 키 값은 분할된 두 노드가 왼쪽 서브트리, 오른쪽 서브트리가 되도록 부모 노드에 삽입 • 이 때, 다시 overflow가 발생하면 위와 같은 분할(split) 작업을 반복
l o n 58 22 42 50 41 20 b b l n 20 42 f f · · · · · 60 60 58 ^ p p o o’ 50 59 ▶ B-트리에서의 삽입 • 앞의 B-트리에 새로운 키 값 22, 41, 59, 57, 54, 44, 75, 124, 122, 123 삽입 (a) 노드 l에 키 22의 삽입 (b) 리프 노드 n에 키 42의 삽입 (c) 노드 o에 키 59의 삽입
o o 57 57 50 50 f f f’ · · · · · · · 60 58 54 60 ^ ^ o o’’ o o o’’ o’ p p o o’ 50 57 50 ▶ B-트리에서의 삽입 (d) 노드 o에 키 57의 삽입 (d) 키 54의 삽입으로 노드 o의 분할 (54는 부모 노드 f로 이동) (e) 노드 f에 키 54의 삽입 (58는 부모 노드 b에 삽입)
a a · · · · · 69 69 43 ^ c b b b’ c b b b’ · · · · · · · 43 19 19 58 ^ ^ d e f f’ f d e ▶ B-트리에서의 삽입 (g) 노드 b에 키 58의 삽입 (43은 부모 노드 a에 삽입) (h) 노드 a에 키 43의 삽입 나머지 키 값인 33, 75, 124, 122를 차례로 삽입 : 문제가 발생하지 않음 마지막 키 값인 123을 삽입 : B-트리는 한 레벨 증가됨
o a 69 · · a a’ 128 43 · · · · b b’ c c’ 120 138 33 19 58 · · · · · · · · · d e e’ f f’ g g’ h i 100 123 132 145 16 26 40 54 60 · · · · · · · · · · · · · · · · · · j k l m m’ n o o’’ o’ p q r r’ r’’ s t u v 124 130 136 140 150 18 30 36 50 57 59 110 122 7 15 20 22 41 42 62 65 70 75 ▶ 한 레벨 증가된 B-트리
· · 69 · · 69 19 138 43 43 138 ▶ 3차 B-트리 생성 과정 • 키 값 43, 69, 138, 19 순으로 삽입하여 생성 43 (a) 크기가 2인 공백 루트 노드 (b) 키 값 43의 삽입(노드 1개의 3차 B-트리) 69 43 (c) 키 값 69의 삽입(노드 1개의 3차 B-트리) (d) 키 값 138의 삽입(노드 3개의 3차 B-트리) (e) 키 값 19의 삽입(노드 3개의 3차 B-트리)
▶ B-트리에서의 삽입 알고리즘 B-트리 삽입 알고리즘 // In-key : 삽입할 키 // Finished : 삽입 완료를 나타내는 플래그 // Found : 레코드가 발견되었음을 나타내는 플래그 // P : 노드에 대한 포인터 // Bignode : 오버플로 노드를 위한 변수 // N : 키 카운터 // 노드의 주소를 스택에 저장하면서 In-key가 삽입될 위치를 탐색 Found = false; read root; do { N = number of keys in current node; if (n-key == key in current node) found = true; else if (In-key < key1) P = Po; else if (In-key > keyN) P = PN; else P = Pi-1; /* for some i where keyi-1 < In-key < keyi */ if (P != null) { push onto stack address of current node; read node pointed to by P; } } while (!Found && P is not null);
B-트리 if (Found) report In-key already in tree; else { // In-key를 B-트리에 삽입 P = null; Finished = false; do { if (current node is not full) { put In-key in current node; // 노드 안에서 키 순서를 유지하도록 키를 정렬 Finished = true; } else { copy current node to Bignode; insert In-key and P into Bignode; In-key = center key of Bignode; current node = 1st half of Bignode; get space for new node, assign address to P; new node = 2nd half of Bignode; if (stack not empty) { pop top of stack; read node pointed to; } else { // 트리의 레벨이 하나 증가 get space for new node; new node = pointer to old root, In-key and P; Finished = true; } } } while (!Finished); }
▶ B-트리에서의 삭제 • 삭제할 키 값이 리프 노드에 있는 경우에는 그대로 삭제 • 삭제될 키 값이 내부 노드에 있는 경우 • 이 키 값의 후행 키 값과 교환 후 리프 노드에서 삭제 • B-트리 특성상 이 후행 키 값은 항상 리프 노드에 있음 • 리프 노드에서의 삭제 연산이 더 간단 • 후행 키 값 대신 선행 키 값을 사용할 수 있음 • 삭제 결과로 노드의 키 값 수가 B-트리의 최소 키 값 수(m/2 - 1)보다 작게 되면 underflow가 일어나 재분배나 합병을 수행
▶ B-트리에서의 삭제 • 재분배(redistribution) • 해당 노드의 오른쪽이나 왼쪽 형제 노드 중에서 최소 키 값보다 많은 수의 키 값을 가진 노드에서 키 값 하나를 차출 • 부모 노드에 있는 키 값을 언더플로가 일어난 노드로 이동하고, 이 빈 자리로 차출된 키 값을 이동 • 트리 구조가 변경되지 않음 • 합병(merge) • 재분배가 불가능한 경우(두 형제 노드가 최소의 키 값만을 가짐)에 적용 • 언더플로가 된 노드의 오른쪽(또는 왼쪽) 형제 노드에 있는 키 값들과 이 두 노드를 분리시키는 부모 노드의 키 값을 합치고 트리 구조를 조정. 합병으로 생긴 빈 노드는 제거 • 트리 구조가 변경됨 • 이 합병 작업은 투트 노드까지 연쇄적으로 파급될 수 있음. 이 경우에는 트리의 레벨이 하나 감소될 수도 있음.