- 이진 트리 기반의 탐색을 위한 자료 구조이다.
- 예) 전화번호 찾기, 사전에서 단어 찾기 등
- 컴퓨터 프로그램에서 탐색은 레코드(파일을 액세스 할 때 실제로 읽고 쓰는 단위)의 집합에서 특정한 레코드를 찾아내는 작업을 의미한다.
- 레코드는 하나 이상의 필드(field)로 구성된다.
- 레코드들의 집합을 테이블(table)이라 하고 레코드들은 보통 키(key)라고 불리는 하나의 필드에 의해 식별할 수 있다.
- 다른 키와 중복되지 않는 키인 주요키(primary key)로 특정한 키를 가진 레코드를 찾는다.
- 모든 원소의 키는 유일한 키를 가진다.
- 왼쪽 서브 트리 키들은 루트 키보다 작다.
- 오른쪽 서브 트리의 키들은 루트의 키보다 크다.
- 왼쪽과 오른쪽 서브 트리도 이진 탐색 트리이다.
찾고자 하는 키값이 이진트리의 루트 노드의 킷값과 비교하여 루트 노드보다 작으면 원하는 키값은 왼쪽 서브트리에 있고 루트노드보다 크면 원하는 키값은 오른쪽 서브트리에 있다.
이진 탐색 트리를 중위순회 방법으로 순회하면 오름차순 정렬이 된다.
- 비교한 결과가 같으면 탐색이 성공적으로 끝난다.
- 비교한 결과가, 주어진 키 값이 루트 노드의 키값보다 작으면 탐색은 이 루트 노드의 왼쪽 자식을 기준으로 다시 시작한다.
- 비교한 결과가, 주어진 키 값이 루트 노드의 키값보다 크면 탐색은 이 루트 노드의 오른쪽 자식을 기준으로 다시 시작한다.
//순환적인 탐색 함수
Treenode * search(TreeNode * node, int key)
{
if (node == NULL) return NULL;
if (key == node->key) return node;
else if (key < node->key)
return search(node->left, key);
else
return search(node->right, key);
}
- 매개변수 node가 NULL이 아니면 반복을 계속한다. 반복 루프 안에서는 현재 node의 키값이 key와 같은지 검사한다.
- 같으면 탐색 성공으로 현재 노드 포인터를 반환.
- 만약 key가 현재 노드 키값보다 작으면 node변수를 node의 왼쪽 자식을 가리키도록 변경
- 만약 key가 현재 노드 키값보다 크면 node변수를 node의 오른쪽 자식을 가리키도록 변경
- 이러한 반복은 node가 결국 단말노드까지 내려가서 NULL값이 될 때까지 반복한다.
// 반복적인 탐색 함수
TreeNode * search(TreeNode * node, int key)
{
while (node != NULL) {
if (key == node->key) return node;
else if (key < node->key)
node = node->left;
else
node = node->right;
}
return NULL; // 탐색에 실패했을 경우 NULL 반환
}
- 원소를 삽입하기 위해서는 먼저 탐색을 수행하는 것이 필요
- 이유는 이진 탐색 트리에서는 같은 키값을 갖는 노드가 없어야 하기 때문이고 또한 탐색에 실패한 위치가 새로운 노드를 삽입하는 위치가 되기 때문이다.
- 새로운 노드는 항상 단말노드에 추가된다.
- 단말 노드를 발견할 때까지 루트에서 키를 검색하기 시작하고 단말 노드가 발견되면 새로운 노드가 단말 노드의 하위 노드로 추가된다.
TreeNode * insert_node(TreeNode * node, int key)
{
// 트리가 공백이면 새로운 노드를 반환한다.
// new_node는 새로운 노드를 연결하는 함수이다.
if (node == NULL) return new_node(key);
//그렇지 않으면 순환적으로 트리를 내려간다.
if (key < node->key)
node->left = insert_node(node->left, key);
else if (key > node->key)
node->right = insert_node(node->right, key);
// 변경된 루트 포인터를 반환한다.
return node;
}
- 삭제하려는 노드가 단말 노드일 경우
- 삭제하려는 노드가 하나의 왼쪽이나 오른쪽 서브 트리중 하나만 가지고 있는 경우
- 삭제하려는 노드가 두개의 서브 트리 모두 가지고 있는 경우
자신 노드는 삭제하고 서브 트리는 자기 노드의 부모 노드에 붙여주면 된다
// 이진 탐색 트리와 키가 주어지면 키가 저장된 노드를 삭제하고
// 새로운 루트 노드를 반환한다.
TreeNode * delete_node(TreeNode * root, int key)
{
if (root == NULL) return root;
// 만약 키가 루트보다 작으면 왼쪽 서브 트리에 있는 것임
if (key < root->key)
root->left = delete_node(root->left, key);
// 만약 키가 루트보다 크면 오른쪽 서브 트리에 있는 것임
else if (key > root->key)
root->right = delete_node(root->right, key);
// 키가 루트와 같으면 이 노드를 삭제하면 됨
else{
// 첫 번째나 두 번째 경우
if (root->left == NULL){
TreeNode * temp = root->right;
free(root);
return temp;
}
else if (root->right == NULL){
TreeNode * temp = root->right;
free(root);
return temp;
}
// 세 번째 경우
//min_value_node 함수는 최소 키값을 가지는 노드를 찾아서 반환해주는 함수임
TreeNode * temp = min_value_node(root->right)
// 중위 순회시 후계 노드를 복사한다.
root->key = temp->key;
// 중위 순회시 후계 노드를 삭제한다.
root->right = delete_node(root->right, temp->key);
}
return root;
}
- 이진 탐색 트리에서의 탐색, 삽입, 삭제 연산의 시간 복잡도는 트리의 높이를 h라고 했을 때 가 된다.
- n개의 노드를 가지는 이진 탐색 트리의 경우, 일반적인 이진 트리의 높이는 이므로 이진 탐색 트리의 연산의 평균적인 경우의 시간 복잡도는 이다.
- 최악의 경우에는 한쪽으로 치우치는 경사 트리가 되어서 트리의 높이가 n이 된다
- 최악의 경우에는 탐색, 삭제, 삽입시간이 O(n)이 된다.