B-Tree
개념
B 트리는 균형잡힌 트리이며 많은 수의 키를 가진 노드로 구성되어 다방향 탐색이 가능한 트리이다. balanced tree이자 multi-way search tree이다. 루트를 제외한 한 노드의 키수가 많은 만큼 자식 노드의 수도 많이 가질 수 있다. 그렇기 때문에 B-트리의 높이는 avl tree에 비해 상대적으로 낮다. 한 노드에 많은 키가 들어간다면 상대적으로 많은 데이터가 있어도 상대적으로 적은 노드의 수가 만들어진다는 것을 의미하고 데이터에 빠른 접근이 가능해진다는 것을 의미한다. B-트리는 많은 데이터베이스 시스템에서 정보를 저장하기 위해 B-트리나 B-트리 변형을 사용한다.
특징
- 노드의 특징
- 내부 노드의 키는 오름차순으로 정렬되어 있는 배열로 저장된다.
- 내부노드의 키 탐색에 있어서 앞이나 뒤부터 순차적으로 찾거나, 혹은 더 효율적으로 중간값을 찾아 탐색할 수도 있다.
- 노드의 키값은 자식 노드들의 키 값을 분할하는 범위가 된다.
- 리프 노드는 자식 노드가 없는 노드이며, 루트에서 리프 노드까지 높이가 트리 전체 높이가 된다.
- 내부 노드의 키는 오름차순으로 정렬되어 있는 배열로 저장된다.
- 포인터
- 트리 내의 모든 노드는 자식 노드를 가리키는 포인터를 가진다. 키의 개수가 n개라면 포인터의 개수는 n+1개이다. 리프 노드는 자식 노드가 없으므로 포인터를 갖지 않는다.
- 노드의 키의 개수 제한
- B 트리의 최소 차수 t는 2 이상이다. 그리고 루트를 제외한 모든 노드는 t-1개 이상의 키를 갖는다.
- 때문에 트리가 비어있지 않다면, 루트 노드는 적어도 한 개의 키(t-1 = 2-1)를 가진다.
- B 트리에서 가득찬 노드에 키를 삽입 시, 노드 분할 과정에서도 B 트리의 특성(위 특성)을 위반하지 않기 위해서는 한 노드에서는 최대 2t-1개의 키를 가진다.
- 높이
- B 트리의 최소 차수를 t라고 할 때, 한 노드에서 최소한 가질 수 있는 자식수는 t개이다. n개의 키를 가진 B 트리의 높이는 log_t n의 높이를 가진다.
- 자식 노드가 t개 있으면, 그 노드의 자식 노드 또한 t개를 가질 수 있게된다. t값이 크다면, B-트리의 높이는 같은 키의 개수 n개가 있는 완전이진탐색트리의 높이에 비해 상당히 낮은 높이가 될 수 있다. 트리의 낮은 높이는 자식 노드의 개수가 많다는 것이고 이는 빠른 탐색이 가능해진다.
검색
- 루트 노드에서 시작하여 하향식으로 값을 비교해 검색 연산을 수행한다.
- log_t n 의 높이와 한 노드에서 키의 비교 시간 O(t)을 곱한 CPU 시간은 O(t log_tn)이다.
- 루트 노드에서 시작해 key들을 순회하면서 비교한다.
- k와 같은 key를 찾았다면 키가 속한 노드 y와 인덱스i 쌍을 리턴하며 검색을 종료한다.
- 리프 노드에 도달했다면, 더 이상 찾을 수 없으므로 null을 반환하며 검색을 종료한다.
- k를 찾을 때 까지 루트에서 자식 노드로 재귀적으로 검색한다.
삽입
- 새로운 키가 삽입될 노드의 위치를 찾는다. 그리고 삽입한다. 삽입 과정에서 해당 노드가 가득 차 있을 경우, 그렇지 않을 경우를 나누어 삽입한다.
- 삽입 연산은 분할, 노드가 가득 차 있지 않을 경우 삽입 연산으로 나눈다. 삽입 연산은 두 가지 경우를 호출한다.
- 분할 연산은 O(1), 삽인 연산은 검색하여 삽입되므로 O(t log_t n) 이다.
- 빈 트리라면 루트 노드를 할당하고 k를 삽입한다. 루트 노드가 가득 찼다면 루트가 분할되고 새 노드 s가 자식을 두 개 가지는 루트가 된다.
- 그 이후 부터는 k값의 삽입 위치를 검색하고 삽입한다.
- 리프 노드가 가득 찼지 않았다면 오름차순으로 k를 삽입한다.
- 리프 노드가 아니라면 자식 노드로 내려가 k가 삽입 될 자리를 검색한다.
- 리프 노드가 가득 찼다면 노드의 중간키 근처에서 t-1, t개의 키를 가지는 두 노드로 분할한다. 그리고 분할 지점이 되는 중간키는 부모 노드로 병합되거나 새로 생성된다.
삭제
- 삭제 연산 또한 삭제할 키를 탐색하여 삭제하는 연산이므로 걸리는 시간은 O(t log_t n) 이다.
- k가 리프에 있으면, k를 노드에서 삭제한다.
- k가 리프 노드에 있다면 다음과 같이 삭제한다.
- 왼쪽 또는 오른쪽 형제 노드의 키가 최소 키수보다 많다면, k에 부모키로 대체한 후 후, 최소키 개수 이상을 가진 형제 노드가 왼쪽이라면 가장 큰 값을, 오른쪽이라면 가장 작은 값을 부모 키에 넣는다. 그리고 그 키를 삭제한다.
- 왼쪽, 오른쪽 형제 노드의 키가 최소 키수이고, 부모 노드의 키가 최소 키수 이상히면, k를 삭제한 후 부모키를 형제 노드와 병합한다. 부모노드의 키의 개수를 줄이고 자식 수도 줄인다.
- k가 내부 노드에 있으면 다음과 같이 삭제한다. - 노드나 자식에 키가 최소 키수(t-1)보다 많을 경우, 현재 노드의 선행 또는 후행키를 찾아 k와 자리를 바꾸고 삭제한다. - 노드와 노드의 자식 노드의 키가 최소 키수(t-1)을 가질 경우, 재구조화가 일어난다.
- k를 삭제하고 k의 자식을 병합하여 하나의 노드로 만든다.
- k의 부모 key를 인접한 k의 형제 노드로 병합시킨다. 그리고 k의 자식을 병합하여 만든 노드를 그의 자식 노드로 정한다.
- 새로 구성된 인접한 형제 노드가 최대 키 수를 넘겼다면, 노드 분할 과정을 수행한다.
- k의 부모 노드가 최소 키의 개수보다 작아진다면, 그 부모 노드에 대하여 2번 과정을 다시 수행한다.