-> 블로그 이전

[Data Structure] 이진 탐색 트리

2021. 12. 15. 18:23Major`/자료구조

이진 탐색 트리

- root 기준으로 왼쪽 → root보다 작은 값

- root 기준으로 오른쪽 → root보다 큰 값

- 중위 순회오름차순으로 정렬된 값 반환

 

 

1. 탐색 연산

  1. node == NULL || key == node->key → 해당 node 리턴
  2. key < node->key  → node = node->llink를 통해서 왼쪽 서브트리 탐색
  3. key > node->key → node = node->rlink를 통해서 오른쪽 서브트리 탐색

- 순환 탐색 vs 반복 탐색 → 효율성 : 반복 

treenode* search_recur(treenode* node, element data) {
	// 순환 탐색 함수
	if (node == NULL || node->data == data) return node;
	else if (data < node->data) search_recur(node->left, data);
	else search_recur(node->right, data);
}

treenode* search_iter(treenode* node, element data) {
	// 반복 탐색 함수
	while (node != NULL) {
		if (data == node->data) return node;
		else if (data < node->data) node = node->left;
		else node = node->right;
	}
	return node;
}

 

 

2. 삽입 연산

- 탐색해가면서 각 root를 가리키는 cur / cur가 이전에 가리키고 있는 노드를 가리키는 pre 

  1. Tree에 노드가 없을 경우 → new_node를 root로 설정
  2. insert하려는 key를 cur->data를 기준으로
    •  → 작으면 (key<cur->data) :  왼쪽 서브트리 탐색 (cur이 NULL이 될 때 까지)
    •  → 크면 (key>cur->data) :  오른쪽 서브트리 탐색 (cur이 NULL이 될 때 까지)
  3. 탐색 중, key == cur->data일 경우 → 이미 있는 key이기 때문에 insert 불가 → 함수 종료 
  4. 탐색을 완료하고, key < pre->data일 경우 → pre->left에 new_node 삽입
  5. 탐색을 완료하고, key > pre->data일 경우 → pre->right에 new_node 삽입
void insert_node(treenode** root, element data) {
	treenode* new_node = (treenode*)malloc(sizeof(treenode));
	new_node->data = data; new_node->left = NULL; new_node->right = NULL;

	treenode* cur = *root; // 현재 root, 진행해가면서 계속 바뀜
	treenode* pre = NULL; // cur가 이전에 가리키고 있는 노드

	if (cur == NULL) { // tree에 아무 노드도 없을 때
		*root = new_node;
		return;
	}
	else {
		while (cur != NULL) {
			pre = cur;
			if (data == cur->data) {
				printf("%d : 이미 존재하기 때문에 insert 실패\n\n", cur->data);
				return; // data가 이미 존재하면 함수 종료
			}
			else if (data < cur->data)
				cur = cur->left; // 왼쪽 서브트리로
			else
				cur = cur->right; // 오른쪽 서브트리로
		}
		if (data < pre->data)
			pre->left = new_node; // pre의 left자식에 new_node 삽입
		else
			pre->right = new_node; // pre의 right자식에 new_node 삽입
	}
}

 

 

※ Example

- (1) new_node->data (14)를 insert 할 때, cur, pre의 변화  

pre NULL node(18) node(10) node(15) 
cur node(18) node(10) node(15) NULL (node(15)의 left) 
  • data (14)는 현재 pre->data인 15보다 작기 때문에 pre->left에 new_node가 삽입된다

 

- (2) new_node->data (8)를 insert할 때, cur, pre의 변화 

pre NULL node(18) node(10) 
cur node(18) node(10) node(8) 
 - new_node->data와 cur->data가 같기 때문에
insert되지 못하고 그대로 함수 종료

 

 

3. 삭제 연산

- 탐색해가면서 각 root를 가리키는 cur

- cur가 이전에 가리키고 있는 노드를 가리키는 pre 

- cur의 서브트리들을 가리키는 child 

- 후계자를 가리키는 succ 

- succ가 이전에 가리키고 있는 노드를 가리키는 succ_pre 

while (cur != NULL && cur->data != key) {
			// 트리가 비어있지 않고, cur->data가 key가 아닐동안 (삭제해야할 key 못찾을 동안)
			pre = cur;
			cur = (key < cur->data) ? cur->left : cur->right;
		}
  • 삭제하려는 key를 가지고 있는 node 탐색

 

① 삭제하려는 노드가 단말노드일 경우

  • 삭제하려는 node의 pre의 link를 끊으면 된다 (NULL로 설정)
if (cur->left == NULL && cur->right == NULL) {
	// 삭제하려는 node가 단말노드일 경우
	if (pre == NULL) { // Tree에 해당 node 하나만 존재할 경우
		cur = NULL;
	}
	else {
		//    17
		// 11    32
		// 11 or 32삭제 -> pre = node(17) 가리킴
		if (pre->left == cur) { // cur = node(11) 가리킬 경우
			pre->left = NULL;
		}
		else { // cur = node(32) 가리킬 경우
			pre->right = NULL;
		}
	}
}

 

② 삭제하려는 노드가 1개의 서브트리를 가지고 있을 경우

  • 삭제하려는 node의 pre의 link를 child를 가리키게 하면 된다
  • child : 삭제하려는 node(cur)의 서브트리를 가리키는 포인터
else if (cur->left == NULL || cur->right == NULL) {
	// 삭제 하려는 node가 서브트리 1개를 가지고 있을 경우
	child = (cur->left != NULL) ? cur->left : cur->right;
	// 삭제 하려는 node(cur)가 왼쪽 서브트리 갖고 있으면 child = cur->left
	// 오른쪽 서브트리 갖고 있으면 child = cur->right
	if (pre == NULL) {
		//     35(cur)삭제  |    35(cur)삭제
		// 20                      |          50
		cur = child;
	}
	else {
		if (pre->right == cur)
			//    35(pre)               | 35(pre)
			//         68(cur)삭제   |      68(cur)삭제
			//     45                        |            90
			pre->right = child;
		else
			//        35(pre)       |     35(pre)
			//     20(cur)삭제   | 20(cur)삭제
			// 15                        |     26
			pre->left = child;
	}
}

 

③ 삭제하려는 노드가 2개의 서브트리를 가지고 있을 경우

  • node 18을 삭제할 경우
  • 후계자 대상 노드 = 왼쪽 서브트리에서 가장 큰 값 or 오른쪽 서브트리에서 가장 작은 값
  • 삭제하려는 node를 후계자로 바꾸면 된다
else {
	// 삭제하려는 node가 2개의 서브트리를 가질 경우
	// 삭제 node의 후계자 -> 왼쪽 서브트리에서 가장 큰 값 or 오른쪽 서브트리에서 가장 작은 값
	succ_pre = cur;
	succ = cur->right;
	while (succ->left != NULL) {
		succ_pre = succ;
		succ = succ->left;
	}
	if (succ_pre->left == succ) succ_pre->left = succ->right;
	else succ_pre->right = succ->right;
	cur->data = succ->data;
	cur = succ;
}

 

 

※ Example

#include <stdio.h>
#include <stdlib.h>
// 이진 탐색 트리
#define max(a, b) ((a>b)?a:b)

typedef int element;
typedef struct treenode {
	element data;
	struct treenode* left;
	struct treenode* right;
}treenode;

treenode n1 = { 2, NULL, NULL };
treenode n2 = { 9, NULL, NULL };
treenode n3 = { 17, NULL, NULL };
treenode n4 = { 34, NULL, NULL };
treenode n5 = { 8, &n1 , &n2 };
treenode n6 = { 15, NULL, &n3 };
treenode n7 = { 29, NULL, NULL };
treenode n8 = { 35, &n4, NULL };
treenode n9 = { 10, &n5, &n6 };
treenode n10 = { 32, &n7, &n8 };
treenode n11 = { 18, &n9, &n10 };
treenode* root = &n11;

void inorder(treenode* root) {
	if (root != NULL) {
		inorder(root->left);
		printf("[%d] ", root->data);
		inorder(root->right);
	}
}

treenode* search_recur(treenode* node, element key) {
	// 순환 탐색 함수
	if (node == NULL || node->data == key) return node;
	else if (key < node->data) search_recur(node->left, key);
	else search_recur(node->right, key);
}

treenode* search_iter(treenode* node, element key) {
	// 반복 탐색 함수
	while (node != NULL) {
		if (key == node->data) return node;
		else if (key < node->data) node = node->left;
		else node = node->right;
	}
	return node;
}

void insert_node(treenode** root, element key) {
	treenode* new_node = (treenode*)malloc(sizeof(treenode));
	new_node->data = key; new_node->left = NULL; new_node->right = NULL;

	treenode* cur = *root; // 현재 root, 진행해가면서 계속 바뀜
	treenode* pre = NULL; // cur가 이전에 가리키고 있는 노드

	if (cur == NULL) { // tree에 아무 노드도 없을 때
		*root = new_node;
		return;
	}
	else {
		while (cur != NULL) {
			pre = cur;
			if (key == cur->data) {
				printf("%d : 이미 존재하기 때문에 insert 실패\n\n", cur->data);
				return; // data가 이미 존재하면 함수 종료
			}
			else if (key < cur->data)
				cur = cur->left; // 왼쪽 서브트리로
			else
				cur = cur->right; // 오른쪽 서브트리로
		}
		if (key < pre->data)
			pre->left = new_node; // pre의 left자식에 new_node 삽입
		else
			pre->right = new_node; // pre의 right자식에 new_node 삽입
	}
}

void delete_node(treenode** root, element key) {
	treenode* cur, * pre, * child, * succ, * succ_pre;
	cur = *root;
	pre = NULL;

	if (cur == NULL) { 
		// 트리가 비어있을 경우
		printf("트리가 비어있기 때문에 delete 불가\n\n");
		return;
	}
	else {
		while (cur != NULL && cur->data != key) {
			// 트리가 비어있지 않고, cur->data가 key가 아닐동안 (삭제해야할 key 못찾을 동안)
			pre = cur;
			cur = (key < cur->data) ? cur->left : cur->right;
		}
		if (cur == NULL) {
			printf("%d : Tree에 존재 X\n\n", key);
			return;
		}
		else {
			if (cur->left == NULL && cur->right == NULL) {
				// 삭제하려는 node가 단말노드일 경우
				if (pre == NULL) { // Tree에 해당 node 하나만 존재할 경우
					cur = NULL;
				}
				else {
					//     17
					// 11    32
					// 11 or 32삭제 -> pre = node(17) 가리킴
					if (pre->left == cur) { // cur = node(11) 가리킬 경우
						pre->left = NULL;
					}
					else { // cur = node(32) 가리킬 경우
						pre->right = NULL;
					}
				}
			}
			else if (cur->left == NULL || cur->right == NULL) {
				// 삭제 하려는 node가 서브트리 1개를 가지고 있을 경우
				child = (cur->left != NULL) ? cur->left : cur->right;
				// 삭제 하려는 node(cur)가 왼쪽 서브트리 갖고 있으면 child = cur->left
				// 오른쪽 서브트리 갖고 있으면 child = cur->right
				if (pre == NULL) {
					//     35(cur)삭제  |    35(cur)삭제
					// 20                      |          50
					cur = child;
				}
				else {
					if (pre->right == cur)
						//    35(pre)               | 35(pre)
						//         68(cur)삭제   |      68(cur)삭제
						//     45                        |            90
						pre->right = child;
					else
						//        35(pre)       |     35(pre)
						//     20(cur)삭제   | 20(cur)삭제
						// 15                        |     26
						pre->left = child;
				}
			}
			else {
				// 삭제하려는 node가 2개의 서브트리를 가질 경우
				// 삭제 node의 후계자 -> 왼쪽 서브트리에서 가장 큰 값 or 오른쪽 서브트리에서 가장 작은 값
				succ_pre = cur;
				succ = cur->right;
				while (succ->left != NULL) {
					succ_pre = succ;
					succ = succ->left;
				}
				if (succ_pre->left == succ) succ_pre->left = succ->right;
				else succ_pre->right = succ->right;
				cur->data = succ->data;
				cur = succ;
			}
		}
	}
}

int get_tree_node(treenode* root) {
	int count = 0;
	if (root != NULL)
		count = 1 + get_tree_node(root->left) + get_tree_node(root->right);
	return count;
}

int get_terminal_node(treenode* root) {
	int count = 0;
	if (root != NULL){
		if (root->left == NULL && root->right == NULL) return 1;
	else
		count = get_terminal_node(root->left) + get_terminal_node(root->right);
		}
	return count;
}

int get_height(treenode* root) {
	int height = 0;
	if (root != NULL)
		height =1+ max(get_height(root->left), get_height(root->right));
	return height;
}

int main(void) {
	treenode* root2 = NULL;
	printf("중위 순회 : ");
	inorder(root);
	printf("\n\n");
	printf("노드 개수 : %d\n\n", get_tree_node(root));
	printf("단말 노드 개수 : %d\n\n", get_terminal_node(root));
	printf("트리 높이 : %d\n\n", get_height(root));
	printf("----------------------------------------------\n\n");

	printf(">> 탐색 연산\n\n");
	printf("Tree 안에 10 존재 유무 : ");
	if (search_recur(root, 10) == NULL)
		printf("NO..\n\n");
	else
		printf("Yes!!\n\n");

	printf("Tree 안에 50 존재 유무 : ");
	if (search_recur(root, 50) == NULL)
		printf("NO..\n\n");
	else
		printf("Yes!!\n\n");
	printf("----------------------------------------------\n\n");
	
	printf(">> 삽입 연산\n\n");
	printf("## insert 15\n\n");
	insert_node(&root, 15);
	inorder(root);
	printf("\n\n## insert 100\n\n");
	insert_node(&root, 100);
	inorder(root);
	printf("\n\n");
	printf("## insert 27\n\n");
	insert_node(&root, 27);
	inorder(root);
	printf("\n\n");

	printf("----------------------------------------------\n\n");
	printf(">> 삭제 연산\n\n");
	printf("## delete 200\n\n");
	delete_node(&root, 200);
	inorder(root);
	printf("\n\n## delete 18\n\n");
	delete_node(&root, 18);
	inorder(root);
	printf("\n\n");
	printf("## delete 32\n\n");
	delete_node(&root, 32);
	inorder(root);
	printf("\n\n");
	printf("## delete 2\n\n");
	delete_node(&root, 2);
	inorder(root);
	printf("\n\n");
	printf("## delete 100\n\n");
	delete_node(&root, 100);
	inorder(root);
	printf("\n\n");

	return 0;
}