이진탐색트리
왼쪽 부분트리에 있는 노드 값은 모두 루트의 값보다 작다.
오른쪽 부분트리에 있는 노드 값은 모두 루트의 값보다 크다.
왼쪽 부분트리와 오른쪽 부분트리는 이진탐색트리이다.
연산 알고리즘
– 삽입
– 삭제
– 탐색
– 부모 노드 탐색
– 중위 순회 (정렬)
– 가장 작은 키 값을 가진 노드 탐색
– size_field 부여 (부분 트리의 노드 개수)
– k번째 노드 탐색 (크기 순)
시간복잡도
: 상수 시간 안에 레벨을 한 칸씩 내려가면서 연산 처리 가능 -> O(h)
최악의 경우: O(n) -> 트리가 편향된 상태
평균의 경우: O(logn) -> 트리가 균형된 형태
코딩 테스트 예시
size_field: [3:1], [13:1], [5:3], [15:10], [20:6], [19:3], [16:2], [17:1], [25:2], [30:1]
#include <stdio.h> #include <stdlib.h> typedef struct TreeNode { int key; int size_field; // 부분 트리의 노드 개수 struct TreeNode* left, * right; }TreeNode; typedef struct TreeNode* TreeNode_ptr; // 트리에 size_field(부분트리에 속한 노드의 개수) 부여 // postorder를 응용해서 제작함 void get_size_field(TreeNode_ptr T) { if (T) { get_size_field(T->left); get_size_field(T->right); if ((T->left == NULL) && (T->right == NULL)) // 잎 노드 T->size_field = 1; else { // 내부 노드 if ((T->left != NULL) && (T->right != NULL)) T->size_field = 1 + (T->left->size_field) + (T->right->size_field); else if ((T->left != NULL) && (T->right == NULL)) T->size_field = 1 + (T->left->size_field); else T->size_field = 1 + (T->right->size_field); } } } // 이진 탐색 트리 전용 노드 생성 TreeNode_ptr make_node(int key) { TreeNode_ptr tmp = (TreeNode_ptr)malloc(sizeof(TreeNode)); tmp->key = key; tmp->size_field = 0; // k-selection 사용하기 전에는 update 안 함 tmp->left = tmp->right = NULL; return tmp; } // 가장 작은 키 값을 가진 노드 찾기 TreeNode_ptr min_val(TreeNode_ptr T) { if (T->left == NULL) return T; else return min_val(T->left); } // 이진 탐색 트리 삽입 TreeNode_ptr insert_node(TreeNode_ptr T, int key) { if (T == NULL) return make_node(key); if (key < T->key) T->left = insert_node(T->left, key); else if (key > T->key) T->right = insert_node(T->right, key); return T; // 삽입한 뒤 루트 반환 } // 이진 탐색 트리 탐색 TreeNode_ptr search_node(TreeNode_ptr T, int key) { if (T == NULL) return NULL; if (key == T->key) return T; else if (key < T->key) return search_node(T->left, key); else return search_node(T->right, key); } // 이진 탐색 트리의 부모 탐색 (루트가 오면 루트 반환) // 순환 호출할 때 현재 노드를 같이 넘겨주면, 부모 노드를 받는 것이 됨 -> 값이 같으면 부모 노드 리턴 가능! // is_root는 루트 노드의 size_field값으로, 루트를 구분해주는 역할을 함 TreeNode_ptr search_parent_node(TreeNode_ptr T, int key, TreeNode_ptr parent,int is_root) { if (T == NULL) return NULL; if (key == T->key) { // T는 루트 노드 if (T->size_field == is_root) return T; // T는 루트 노드가 아님 else return parent; } else if (key < T->key) return search_parent_node(T->left, key,T,is_root); else return search_parent_node(T->right, key,T,is_root); } // 이진 탐색 트리 삭제 TreeNode_ptr delete_node(TreeNode_ptr T, int key) { // 못 찾았을 때 if (T == NULL) return T; if (key < T->key) T->left = delete_node(T->left, key); else if (key > T->key) T->right = delete_node(T->right, key); // 찾았을 때 else { // 오른쪽 서브트리만 존재한 경우 혹은 잎 노드 if (T->left == NULL) { TreeNode_ptr tmp = T->right; free(T); return tmp; } // 왼쪽 서브트리만 존재한 경우 혹은 잎 노드 else if (T->right == NULL) { TreeNode_ptr tmp = T->left; free(T); return tmp; } // 두 서브트리 모두 존재한 경우 // 오른쪽 서브트리에서 가장 작은 노드로 대체 후 해당 노드 삭제 else { TreeNode_ptr tmp = min_val(T->right); T->key = tmp->key; T->right = delete_node(T->right, tmp->key); } } return T; // 삭제한 뒤 루트 반환 } // 크기 순으로 정렬했을 때 k번째 노드 찾기 TreeNode_ptr k_selection(TreeNode_ptr T, int k) { // size_field 이용 static int temp = 0; if ((temp++) == 0) get_size_field(T); // k가 노드의 전체 개수보다 크면 k_selection 불가 if (k > T->size_field) return 0; else { // 왼쪽 size (왼쪽 서브트리의 사이즈) int left_size = (T->left) ? T->left->size_field : 0; // 왼쪽 size가 k이상이면 왼쪽 서브트리에서 조사 if (left_size >= k) return k_selection(T->left, k); // 왼쪽 size가 k미만 else { // 왼쪽 size+자신(1)이 k이다 -> 해당 노드가 정답 if ((k - (left_size + 1)) == 0) return T; // 왼쪽 size+자신(1)이 k미만 -> k수정해서 오른쪽 서브트리에서 조사 else return k_selection(T->right, (k - (left_size + 1))); } } } // 중위 순회 (정렬) void inorder(TreeNode_ptr T) { if (T) { inorder(T->left); printf("[%d] ", T->key); inorder(T->right); } } // 이진탐색트리 연산 테스트 int main() { TreeNode_ptr T = NULL; // 삽입 테스트 T = insert_node(T, 15); T = insert_node(T, 5); T = insert_node(T, 3); T = insert_node(T, 13); T = insert_node(T, 20); T = insert_node(T, 19); T = insert_node(T, 25); T = insert_node(T, 16); T = insert_node(T, 17); T = insert_node(T, 30); // 정렬 테스트 printf("이진 탐색 트리 중위 순회 결과 \n"); inorder(T); printf("\n\n"); // k번째 노드 탐색 테스트 TreeNode_ptr k_what = 0; for (int i = 1; (k_what = k_selection(T, i)) != 0; i++) printf("크기순으로 정렬했을 때 %d번째 노드의 키 값은? : %d\n", i, k_what->key); printf("\n"); // 부모 노드 탐색 테스트 printf("(15)노드의 부모 노드는 (%d)이다.\n", search_parent_node(T, 15, T,T->size_field)->key); printf("(17)노드의 부모 노드는 (%d)이다.\n",search_parent_node(T,17,T, T->size_field)->key); printf("(30)노드의 부모 노드는 (%d)이다.\n\n", search_parent_node(T, 30, T, T->size_field)->key); // 삭제 테스트 T = delete_node(T, 17); T = delete_node(T, 19); T = delete_node(T, 20); printf("삭제 후 중위 순회 결과 \n"); inorder(T); printf("\n\n"); // 탐색 테스트 if (search_node(T, 17)) printf("이진 탐색 트리에서 17을 발견함"); else printf("이진 탐색 트리에서 17을 발견못함"); printf("\n"); if (search_node(T, 15)) printf("이진 탐색 트리에서 15를 발견함"); else printf("이진 탐색 트리에서 15를 발견못함"); printf("\n"); }