B트리는 이진트리에서 발전되어 모든 리프노드들이 같은 레벨을 가질 수 있도록 자동으로 벨런스를 맞추는 트리입니다. 또한 정렬된 순서를 보장하고, 멀티레벨 인덱싱을 통한 빠른 검색을 할 수 있기 때문에 DB에서 사용하는 자료구조 중 한 종류라고 합니다.
실제 DB에서는 B트리에서 발전한 B+트리를 실제로 사용한다고 합니다. 2번에 걸쳐 B트리와 B+트리에 대해 포스팅 해보겠습니다.
B트리는 이진트리와 다르게 하나의 노드에 많은 수의 정보를 가지고 있을 수 있습니다. 최대 개의 자식을 가질 수 있는 B트리를 차 B트리라고 하며 다음과 같은 특징을 같습니다.
다음은 차수가 3인 B트리 입니다. 파란색 부분은 각 노드의 key를 나타내며, 빨간색 부분은 자식 노드들을 가르키는 포인터입니다. key들은 노드 안에서 항상 정렬된 값을 가지며, 이진검색 트리처럼 각 key들의 왼쪽 자식들은 항상 key보다 작은 값을, 오른쪽은 큰 값을 가집니다.
루트노드에서 시작하여 하향식으로 검색을 수행합니다. 검색하고자 하는 key를 라고 하였을 때 검색 과정입니다.
루트 노드에서 시작하여 key들을 순회하면서 검사합니다.
1-1. 만일 와 같은 key를 찾았다면 검색을 종료합니다.
1-2. 검색하는 값과 key들의 대소관계를 비교해봅니다. 어떠한 key들 사이에 가 들어간다면 해당 key들 사이의 자식노드로로 내려갑니다.
해당 과정을 리프노드에 도달할 때까지 반복합니다. 만일 리프노드에도 와 같은 key가 없다면 검색을 실패합니다.
key를 삽입하기 위해서는 1. 요소 삽입에 적절한 리프 노드를 검색하고, 2. 필요한 경우 노드를 분할해야 합니다. 리프노드 검색은 하향식이지만 노드 분할의 과정은 상향식으로 이루어진다고 볼 수 있습니다. 삽입하고자 하는 값을 로 하였을 때 삽입 과정입니다.
트리가 비어있으면 루트 노드를 할당하고 를 삽입합니다. 만일 루트노드가 가득 찼다면, 노드를 분할하고 리프노드가 생성됩니다.
이후부터는 삽압하기에 적절한 리프노드를 찾아 를 삽입합니다. 삽입위치는 노드의 key값과 값을 검색 연산과 동일한 방법으로 비교하면서 찾습니다.
이후 두가지 케이스로 나뉘게 됩니다.
리프노드가 가득차지 않았다면, 오름차순으로 를 삽입합니다.
만일 리프노드에 key노드가 가득 찬 경우, 노드를 분할해야 합니다.
오름차순으로 요소를 삽입합니다. 노드가 담을 수 있는 최대 key 개수를 초과하게 됩니다.
중앙값에서 분할을 수행합니다. 중앙값은 부모 노드로 병합하거나 새로 생성됩니다. 왼쪽 키들은 왼쪽 자식으로, 오른쪽 키들은 오른쪽 자식으로 분할됩니다.
부모 노드를 검사해서 또 다시 가득 찼다면, 다시 부모노드에서 위 과정을 반복합니다.
요소를 삭제하기 위해선 1. 삭제할 키가 있는 노드 검색, 2. 키 삭제, 3. 필요한 경우, 트리 균형 조정을 해야합니다.
삭제 과정을 이해하기 위해서 몇가지 용어를 정의하였습니다.
inorder predecessor
: 노드의 왼쪽 자손에서 가장 큰 keyinorder successor
: 노드의 오른쪽 자손에서 가장 작은 key다른 노드들에 영향 없이 해당 를 단순 삭제합니다.
부모노드를 루트노드로 한 부분 트리의 높이가 줄어드는 경우이기 때문에 재구조화의 과정이 일어납니다. case3의 2번 과정으로 이동합니다.
inorder predecessor
또는 inorder successor
와 의 자리를 바꿉니다. 삭제할 key 가 있는 노드도 최소, 자식노드들도 최소의 key 개수를 가지므로, 를 삭제하면 트리의 높이가 줄어들어 재구조화가 일어나는 케이스입니다. 재구조화의 과정은 다음과 같습니다.
진짜 대박입니당... 용욱쓰 파이팅!!