Transcript Q. 시간 복잡도
자료 구조
제 5 장 : 트리
이형원
강릉대학교 컴퓨터공학과
학습 내용
기본 용어
이진 트리
이진 트리의 순회
스레드 이진 트리
이진 트리의 응용
히프
이진 탐색 트리
선택 트리
포리스트
집합 표현
2
기본 용어
트리 : 1개 이상의 노드로 이루어진 유한 집합
하나의 루트 노드 + 0개 이상의 서브 트리들
서브 트리는 서로 연결될 수 없음
트리의 각 원소는 어떤 서브 트리의 루트가 됨
노드 : 정보 항목 + 가지(edge)
노드의 차수(degree) : 해당 노드에 대한 서브 트리 수
리프(leaf) 노드, 단말(terminal) 노드 : 차수가 0인 노드
비단말(non-terminal) 노드
부모(parent) , 자식(children), 형제(sibling)
조상 : 루트에서 해당 노드까지의 경로상에 있는 노드들
자손 : 한 노드의 서브 트리에 속한 모든 노드들
노드 레벨(level) : 부모의 노드 레벨 + 1(루트의 레벨은 1)
3
트리 높이(height)/깊이(depth) : 최대 노드 레벨
트리의 예
레벨
루트(차수=3)
노드 G의 조상
부모 B
자식
E
형제
K
L
D
C
자식
F
형제
1
A
G
H
I
2
J
3
단말
M
노드 D의 자손
4
트리 높이
4
트리의 표현
리스트 표현
노드 구조 :
데이터 링크 1 링크 2
..… 링크 n
왼쪽 자식-오른쪽 형제 표현
일정 크기의 노드를 다루는 것이 편리
노드 구조 :
데이터 왼쪽 자식 오른쪽 형제
일반적인 트리에서는 자식의 순서는 중요하지 않음
차수가 2인 트리 (왼쪽 자식-오른쪽 자식) 표현
왼쪽 자식-오른쪽 형제 트리를 45도 시계방향으로 비틈
5
트리의 표현(계속)
A
A
B
B
E
C
F
G
D
H
I
C
E
J
K
F
G
H
L
K
L
D
M
M
I
J
6
이진 트리: ADT
structure Binary_Tree
objects: 공백이거나 (루트 노드, 왼쪽 Binary_Tree, 오른쪽
Binary_Tree)로 구성되는 노드들의 유한집합
functions: 모든 bt, bt1, bt2 ∈ BinTree, item ∈ element
BinTree Create() ::= 공백 이진 트리를 생성
Boolean IsEmpty(bt) ::= if (bt == 공백 이진 트리) return TRUE
else return FALSE
BinTree MakeBT(bt1, item, bt2) ::= 왼쪽 서브 트리가 bt1, 오른쪽
서브 트리가 bt2, 루트는 데이타를 갖는 이진 트리를 반환
BinTree Lchild(bt) ::= if (IsEmpty(bt)) return 에러
else bt의 왼쪽 서브 트리를 반환
element Data(bt) ::= if (IsEmpty(bt)) return 에러
else bt의 루트에 있는 데이타를 반환
BinTree Rchild(bt) ::= if (IsEmpty(bt)) return 에러
7
else bt의 오른쪽 서브 트리를 반환
이진 트리: 개요
어떤 트리도 이진(binary) 트리로 표현할 수 있음
리스트 표현
왼쪽 자식-오른쪽 형제 표현
왼쪽 자식-오른쪽 자식 표현
A
일반 트리와의 차이점
① 노드의 차수가 2이하
② 이진 트리는 공백 트리 가능
③ 이진 트리는 자식의 순서를 구분
특수 이진 트리
경사(skewed) 트리
완전(complete) 이진 트리
A
B
B
C
C
D
E
F
G
D
E
H
I
8
이진 트리: 개요(계속)
특성
이진 트리의 레벨 i에서의 최대 노드 수 = 2i-1 (i≥1)
깊이가 k인 이진 트리의 최대 노드 수 = 2k - 1 (k≥1)
단말 노드 수 = 차수가 2인 노드 수 + 1
포화(full) 이진 트리: 트리의 깊이가 k일 때 노드 수가 2k - 1
1
2
4
8
3
5
9
10
6
11
12
7
13
14
15
9
이진 트리: 배열 표현
일차원 배열에 그림 5.10의 번호 할당 기법에 의해 저장
배열의 0번째 원소는 사용하지 않음
n 개의 노드를 가진 완전 이진 트리의 경우
① if (i≠ 1) parent(i)는 i/2 의 위치, o.w. 부모가 없다
② if (2i≤n) left_child(i)는 2i의 위치, o.w. i의 왼쪽 자식이 없다
③ if(2i+1≤n) right_child(i)는 2i+1의 위치, o.w. i의 오른쪽 자식이 없다
삽입/삭제가 없는 완전 이진 트리에 적합
[1] [2] [3] [4] [5] [6] [7] [8] [9] ….. [16]
A B
C
D
….. E
경사 트리
[1] [2] [3] [4] [5] [6] [7] [8] [9]
A B C D E F G H I
완전 이진 트리
10
이진 트리: 링크 표현
노드 구조:
left_child data
↓
root of left subtree
right_child
↓
root of right subtree
C 언어 정의
typedef struct node *tree-pointer;
typedef struct node {
int data;
tree_pointer left_child, right_child;
};
단점
difficult to find parent : Q. How to solve ?
too many null pointers : Q. How many ?
11
이진 트리: 순회
트리 순회(traversal)
트리에 있는 모든 노드를 한번씩 방문
노드와 서브 트리를 같은 방법으로 처리하는 것이 효율적
L : 왼쪽으로 이동, V : 노드 방문, R : 오른쪽으로 이동
+
예제 트리
*
*
A/B*C*D+E
D
C
/
A
E
B
12
이진 트리: 중위 순회
방법 : LVR
void inorder(tree_pointer ptr) {
if (ptr) {
inorder(ptr->left_child); /* traverse left subtree in
inorder */
printf("%d", ptr->data); /* visit */
inorder(ptr->right_child); /* traverse right subtree in
inorder */
}
}
순회 결과 : A / B * C * D + E
13
이진 트리: 전위 순회
방법 : VLR
void preorder(tree_pointer ptr) {
if (ptr) {
printf("%d", ptr->data); /* visit */
preorder(ptr->left_child); /* traverse left subtree in preorder */
preorder(ptr->right_child); /* traverse right subtree in preorder */
}
}
순회 결과 : + * * / A B C D E
14
이진 트리: 후위 순회
방법 : LRV
void postorder(tree_pointer ptr) {
if (ptr) {
postorder(ptr->left_child); /* traverse left subtree in postorder */
postorder(ptr->right_child); /* traverse right subtree in postorder */
printf("%d", ptr->data);
/* visit */
}
}
순회 결과 : A B / C * D * E +
15
이진 트리: 반복적 중위 순회
방법 : 순환을 simulate하기 위한 스택 필요
void iter_inorder(tree_pointer node) {
int top = -1;
tree_pointer stack[MAX_STACK_SIZE];
for (;;) {
for (; node; node = node->left_child) add(&top, node);
node = delete(&top);
if (!node) break; /* 공백 스택 */
printf("%d", node->data);
node = node->right_child;
}
}
Q. 시간 복잡도 ?
16
이진 트리: 레벨 순서 순회
루트로부터 단말 노드까지 차례대로 순회 : 큐를 사용
void level_order(tree_pointer ptr) {
int front = rear = 0;
tree_pointer queue[MAX_QUEUE_SIZE];
if (!ptr) return; /* 공백 트리 */
addq(front, &rear, ptr);
for (;;) {
ptr = deleteq(&front, rear);
if (ptr) {
printf("%d", ptr->data);
if(ptr->left_child) addq(front, &rear, ptr->left_child);
if (ptr->right_child) addq(front, &rear, ptr->right_child);
}
else break;
}
}
순회 결과 : + * E * D / C A B
17
이진 트리: 복사
후위 순회 함수를 변경
tree_pointer copy(tree_pointer original) { /*복사된 트리의 tree_pointer 반환 */
tree_pointer temp;
if (original) {
temp = (tree_pointer) malloc(sizeof(node));
if (IS_FULL(temp)) {
fprintf(stderr, "The memory is full");
exit(1);
}
temp->left_child = copy(original->left_child);
temp->right_child = copy(original->right_child);
temp->data = original->data;
return temp;
}
return NULL;
}
18
이진 트리: 동일성 검사
전위 순회 함수를 변경
int equal(tree_pointer first, tree_pointer second)
/* 두 이진 트리가 같으면 TRUE, 그렇지 않으면 FALSE 반환
*/
{
return ((!first && !second) || (first && second &&
(first->data == second->data) &&
equal(first->left_child, second->left_child) &&
equal(first->right_child, second->right_child));
}
19
이진 트리: 만족성 문제
명제식의 값이 참이 되도록 할 수 있는가
변수 x1, .., xn과 연산자 ∧(and), ∨(or), ┐(not)으로 이루어진 식
Ο(g2n) : g는 변수에 값을 대입 & 식을 계산하는 시간
예: (x1∧┐x2)∨(┐x1∧x3)∨┐x3에 대한 이진 트리
∨
┐
∨
∧
x1
x3
∧
┐
x2
┐
x3
x1
중위 순회 결과
x1∧┐x2∨┐x1∧x3∨┐x3
20
이진 트리: 만족성 문제(계속)
방법
트리를 후위 순회
노드 방문 시 두 서브 트리의 값을 이용하여 계산
노드 구조
typedef enum {not, and, or, true, false} logical;
typedef struct node *tree_pointer;
typedef struct node {
tree_pointer left_child;
logical
data; /* 변수 값 또는 연산자 */
short int value; /* T/F */
tree_pointer right_child;
};
리프 노드의 node->data는 이 노드가 나타내는 변수의 현
21
재 값을 갖는다고 가정
이진 트리: 만족성 문제(계속)
초기 알고리즘
for (all 2n possible combinations) {
generate the next combination;
replace the variables by their values;
evaluate root by traversing it in postorder;
if (root->value) {
printf(<combination>);
return;
}
}
printf("No satisfiable combination");
22
이진 트리: 만족성 문제(계속)
void post_order_eval(tree_pointer node) {
if (node) {
post_order_eval(node->left_child);
post_order_eval(node->right_child);
switch (node->data) {
case not : node->value = !node->right_child->value;
break;
case and: node->value=node->right_child->value && node->left_child->value;
break;
case or: node->value = node->right_child->value || node->left_child->value;
break;
case true: node->value = TRUE;
break;
case false: node->value = FALSE;
}
}
}
23
스레드(threaded) 이진 트리
스레드
null link를 다른 노드를 가리키는 ptr인 스레드로 대치
스레드 구성의 규칙
ptr->left_child가 널이면 ptr->left_child=ptr의 중위 선행자로의 포인터
ptr->right_child가 널이면 ptr->right_child=ptr의 중위 후속자로의 포인터
root
중위 순회
H D I B EAF C G
B
D
H
A
C
E
I
F
G
24
스레드 이진 트리: 노드 구조
스레드와 정상적인 포인터를 구별
left_thread
left_child
data
right_child
right_thread
left_thread가 T이면, left_child : thread o.w. left_child : normal pointer
right_thread가 T이면, right_child : thread o.w. right_child : normal pointer
typedef struct threaded_tree *thread_pointer;
typedef struct threaded_tree {
short int left_thread, right_thread;
threaded_pointer left_child, right_child;
char data;
};
분실 스레드 : 헤드 노드로 해결
25
스레드 이진 트리: 예
left_thread
left_child
TRUE
°
data
_
right_child
right_thread
FALSE
°
root
f • – • f
f • A • f
f • B • f
f • D • f
t • H • t
t • E • t
t • I • t
f • C • f
t • F • t
t • G • t
26
스레드 이진 트리: 중위 순회
threaded_pointer insucc(threaded_pointer tree) { /* 중위 후속자를 찾는다. */
threaded_pointer temp;
temp = tree->right_child;
if (!tree->right_thread)
while (!temp->left_thread) temp = temp->left_child;
return temp;
}
void tinorder(threaded_pointer tree) {/* 스레드 이진 트리의 중위 순회 */
threaded_pointer temp = tree;
for (;;) {
temp = insucc(temp);
if (temp = tree) break;
printf("%3c", temp->data);
}
} Q. 시간 복잡도 ?
27
Q. 전위 및 후위 순회 ?
스레드 이진 트리: 노드 삽입
노드 child를 노드 parent의 오른쪽 자식으로 삽입
⑴ parent의 오른쪽 subtree가 공백인 경우
child->right_child = parent->right_child, child->right_thread = TRUE
child->left_child = parent, child->left_thread = TRUE
parent->right_child = child, parent->right_thread = FALSE
A
B
C
A
parent
D
child
C
B
D
28
스레드 이진 트리: 노드 삽입(계속)
⑵ parent의 오른쪽 subtree가 공백이 아닌 경우
child->right_child = parent->right_child, child->right_thread = FALSE
child->left_child = parent , child->left_thread = TRUE
parent->right_child = child, parent->right_thread = FALSE
insucc(child)->left_child = child
A
A
parent
B
C
D
E
C
F
X
child
B
X
D
E
F
29
스레드 이진 트리: 노드 삽입(계속)
void insert_right(threaded_pointer parent, threaded_pointer child) {
/* 스레드 이진 트리에서 child를 parent의 오른쪽 자식으로 삽입 */
threaded_pointer temp;
child->right_child = parent->right_child; /* - 1 */
child->right_thread = parent->right_thread; /* - 2 */
child->left_child = parent; child->left_thread = TRUE; /* */
parent->right_child = child; parent->right_thread = FALSE; /* */
if (!child->right_thread) { /* */
temp = insucc(child);
temp->left_child = child;
}
}
30
히프(heap): 정의
최대 히프와 최소 히프
최대 트리 : 각 노드의 키 값이 자식의 키 값보다 작지 않은 트리
최대 히프 : 최대 트리인 완전 이진 트리 (루트 : 가장 큰 키 값)
최소 트리 : 각 노드의 키 값이 자식의 키 값보다 크지 않은 트리
최소 히프 : 최소 트리인 완전 이진 트리 (루트 : 가장 작은 키 값)
히프의 표현 : 배열 Q. Why ?
[1]
[2]
[4]
10
[1]
14
[3]
12
[5]
8
[6]
6
7
[2]
[4]
10
2
[3]
7
[5]
8
4
[6]
6
31
히프(heap): 최대 히프 ADT
structure MaxHeap
objects : 각 노드의 값이 그 자식들보다 작지 않은 완전 이진 트리
functions : heap ∈ MaxHeap, item ∈ Element, n, max_size ∈ integer
MaxHeap Create(max_size) ::= 최대 max_size개의 원소를 갖는
공백 히프를 생성
Boolean HeapFull(heap, n) ::= if (n == max_size) return TRUE
else return FALSE
MaxHeap Insert(heap, item, n) ::=
if (!HeapFull(heap, n)) item을 삽입하고 그 히프를 반환
else return 에러
Boolean HeapEmpty(heap, n) ::= if (n>0) return TRUE
else return FALSE
Element Delete(heap, n) ::=
if (!HeapEmpty(heap, n)) 히프의 가장 큰 원소를 제거하고
반환
32
else return 에러
히프: 우선 순위 큐
우선 순위 큐
우선 순위가 가장 높은(낮은) 원소를 먼저 삭제하는 큐
예) OS의 작업 스케쥴러
우선 순위 큐의 표현 방법
표현 방법
삽입
순서없는 배열
Θ(1)
삭제
Θ(n)
순서없는 연결 리스트
Θ(1)
Θ(n)
정렬된 배열
O(n)
Θ(1)
정렬된 연결 리스트
O(n)
Θ(1)
최대 히프
O(log2n)
O(log2n)
33
히프: 최대 히프에서의 삽입
방법
완전이진 트리를 유지 : 삽입 위치는 일정
최대 트리의 유지 : 한 원소에서 부모를 찾아갈 수 있어야
함
연결 리스트 사용 : 부모 필드 첨가
배열 사용 : 추가 필드없이 부모 찾기가 용이
[1]
[2]
[4]
14
[1]
20
[3]
15
[5]
10
2
[2]
[4]
14
[1]
20
[3]
15
[5]
10
2
[6]
[2]
[4]
14
21
[3]
15
[5]
10
20
[6]
2
34
히프: 최대 히프에서의 삽입(계속)
#define MAX_ELEMENTS 200 /* 최대 히프 크기 + 1 */
#define HEAP_FULL(n) (n == MAX_ELEMENTS-1)
#define HEAP_EMPTY(n) (!n)
typedef struct { int key; …. /* 다른 필드들 */ } element;
element heap[MAX_ELEMENTS];
int n = 0;
void insert_max_heap(element item, int *n) {
/* n개의 원소를 갖는 최대 히프에 item을 삽입한다. */
int i;
if (HEAP_FULL(*n)) { fprintf(stderr, "The heap is full.\n"); exit(1); }
i = ++(*n);
while ((i != 1) && (item.key > heap[i/2].key)) {
heap[i] = heap[i/2];
i /= 2;
}
heap[i] = item;
} Q. 시간 복잡도 ?
35
히프: 최대 히프에서의 삭제
방법
항상 히프의 루트에서 삭제
완전 이진 트리 및 최대 트리를 만족하도록 재구성
[1]
[2]
[4]
14
[1]
20
[3]
15
[5]
10
2
[2]
[4]
14
[1]
[3]
15
[5]
10
2
[2]
[4]
14
15
[1]
10
[3]
2
[2]
14
15
[3]
2
[4]
10
36
히프: 최대 히프에서의 삭제(계속)
element delete_max_heap(int *n) { /* 가장 큰 값의 원소를 히프에서 삭제 */
int parent, child;
element item, temp;
if (HEAP_EMPTY(*n)) { fprintf(stderr, "The heap is empty"); exit(1); }
item = heap[1]; /* 가장 큰 키값을 저장 */
/* 히프를 재구성하기위해 마지막 원소를 이용 */
temp = heap[(*n)--]; parent = 1; child = 2;
while (child <= *n) {
/* 현 parent의 가장 큰 자식을 탐색 */
if ((child < *n) && (heap[child].key) < heap[child+1].key)) child++;
if (temp.key >= heap[child].key) break;
/* 아래 단계로 이동 */
heap[parent] = heap[child]; parent = child; child *= 2;
}
heap [parent] = temp;
return item;
} Q. 시간 복잡도 ?
37
이진 탐색 트리: 개요
이진 탐색 트리
임의의 원소 탐색/삽입/삭제에 적합 Q. 히프 ?
정의
• 모든 원소는 유일한 값의 키를 가짐
• 왼쪽 서브트리 키 < 그 서브트리의 루트 키 < 오른쪽 서브트리 키
• 왼쪽과 오른쪽 서브 트리도 이진 탐색 트리
이진 트리의 하나 : C 선언 및 기존 연산이 모두 적용
20
15
12
10
30
25
22
5
2
60
40
70
65
80
38
이진 탐색 트리: 탐색
순환 탐색과 반복 탐색 Q. 시간 복잡도 ?
tree_pointer search(tree_pointer root, int key) {
if (!root) return NULL;
if (key == root->data) return root;
if (key < root->data) return search(root->left_child, key);
return search(root->right_child, key);
}
tree_pointer search2(tree_pointer tree, int key) {
while (tree) {
if (key == tree->data) return tree;
if (key < tree->data) tree = tree->left_child;
else tree = tree->right_child;
}
return NULL;
39
}
이진 탐색 트리: 삽입
방법
동일한 키 값을 갖는 원소가 존재하는지 확인하기
위해 탐색
탐색이 실패하면 탐색이 종료된 지점에 원소 삽입
30
5
2
30
40
5
2
30
40
40
5
80
2
35
80
40
이진 탐색 트리: 삽입(계속)
void insert_node(tree_pointer *node, int num) {
/* 트리내의 노드가 num을 가리키고 있으면 아무 일도 하지 않음;
그렇지 않은 경우는 data=num인 새 노드를 첨가 */
tree_pointer ptr, temp = modified_search(*node, num);
if (temp || !(*node)) { /* num이 트리내에 없음 */
ptr = (tree_pointer)malloc(sizeof(node)) ;
if (IS_FULL(ptr)) {fprintf(stderr, "The memory is full"); exit(1); }
ptr->data = num;
ptr->left_child = ptr->right_child = NULL;
if (*node) /* temp의 자식으로 삽입 */
if (num < temp->data) temp->left_child = ptr;
else temp->right_child = ptr;
else *node = ptr:
}
41
} Q. 시간 복잡도 ?
이진 탐색 트리: 삭제
삭제 노드의 형태
① 리프 노드 L
L 삭제
L의 부모 노드의 자식 필드를 NULL
② 하나의 자식을 갖는 비단말 노드 S
S 삭제
S의 자식을 삭제된 노드의 자리에 위치
30
40
5
2
30
35
5
80
2
30
40
5
80
80
2
42
이진 탐색 트리: 삭제(계속)
③ 두개의 자식을 갖는 비단말 노드 D
D를 (left subtree의 largest 노드 or right subtree의 smallest 노드)로 대체
선택된 subtree에서 치환된 원소(항상 차수가 0 또는 1)의 삭제
Q. 알고리즘의 복잡도 ?
삭제 노드
40
60
20
10
40
30
70
50
55
45
52
55
20
10
30
70
50
45
52
43
이진 탐색 트리: 높이
n개의 원소를 갖는 이진 탐색 트리의 높이
최악의 경우 n : 함수 insert_node를 이용하여
1,2,...,n을 삽입
평균적으로 Ο(log2 n) : 삽입과 삭제가 random
균형 탐색 트리
최악의 경우에도 높이가 Ο(log2 n)인 탐색 트리
예 : AVL 트리,2-3 트리, 레드-블랙 트리
탐색, 삽입, 삭제가 Ο(h)
44
선택 트리
문제
K 개의 순서 순차를 하나의 순서 순차(비감소)로 합병
가정
• 각 순차(런)는 키 필드에 따라 비감소 순서로 정렬된 레코드들로 구성
• k개의 런에 있는 레코드의 총 수는 n
방법 1
for ( i=1; i<= n; i++) {
k개의 런의 첫번째 레코드들 중 가장 작은 키 값의 레코드를
선택
해당 레코드를 출력
해당 레코드가 속해 있는 런에서 그 레코드를 삭제
}
45
Q. 시간 복잡도 ?
선택 트리(계속)
방법 2 : 선택 트리 사용
선택 트리
• 각 노드가 두 개의 자식 노드 중 더 작은 노드를 표현하는 이진 트리
• 더 작은 키 값을 갖는 레코드가 승자가 되는 토너먼트와 동일
• 각 노드는 그 노드를 표현하는 레코드에 대한 포인터만 포함 : 배열
사용
• 토너먼트는 형제 노드간 시행, 결과는 부모 노드에 위치
• 배열 표현이므로 형제, 부모 노드의 주소 계산이 효율적
알고리즘
선택 트리 초기화
for ( i=1; i<= n; i++) 루트 노드를 출력하고 선택 트리를 재구성
시간 복잡도 : Ο(n log2 k)
46
선택 트리(계속)
1
1
6
2
2
3
6
8
4
5
9
6
6
3
9
7
17
8
8
8
4
5
9
6
15
7
17
8
8
10
9 10
9 20
11 12
6
8
13 14
9 90
15
17
8
10
15
16
…
20
38
…
15
25
…
11 100
16 110
… …
18
20
…
15
16
…
런1 런2 런3 런4 런5 런6 런7
런8
런1 런2 런3 런4 런5 런6 런7 런8
20
30
…
15
50
…
9 10
9 20
11 12
15 8
13 14
9 90
20
38
…
25
30
…
11 100 18
16 110 20
… … …
20
30
…
15
50
…
47
15
17
포리스트
n≥0개의 트리들의 집합 (트리의 루트 제거)
포리스트의 이진 트리 변환 방법
포리스트의 각 트리를 이진 트리로 변환
첫 번째 트리의 루트로부터 오른쪽 서브 트리를 따라 나머
지 트리들의 루트들을 차례대로 연결
A
B
A
E
E
G
C
B
C
D
F
H
F
G
I
D
H
I
48
집합 표현
개요
집합의 원소는 0,1,...,n-1 등의 수
모든 집합들은 서로 분리(disjoint) 관계
집합의 포리스트 표현 (링크의 방향 주의)
S1 = {0, 6, 7, 8}, S2 = {1, 4, 9}, S3= {2, 3, 5}
0
6
수행 연산
7
S1
4
8
2
9
1
S2
5
3
S3
⑴ Union(i,j) : SiU Sj
⑵ Find(i) : 원소 i가 포함된 집합 찾기
49
집합 표현(계속)
union 연산
트리를 다른 트리의 서브 트리로
구현: 한 루트의 부모 필드가 다른
트리의 루트를 가리키게 함
0
6
S1 U S2
8
7
4
1
find 연산
집합
각 루트가 집합 이름에 대한
이름
포인터를 갖게 함
S1
구현: 루트로 연결된 부모 링크를
S2
따라 올라가서 집합 이름을
S3
가리키는 포인터를 찾음
편의상, 트리의 루트를 집합
이름으로 사용
9
포인터
0
6
8
7
4
1
2
3
5
9
50
집합 표현(계속)
자료 구조 : 배열 int parent[MAX_ELEMENTS]
i
[0] [1] [2] [3] [4] [5] [6] [7] [8] [9]
parent -1 4 -1 2 -1 2 0 0 0 4
union 및 find 함수의 초기 구현
int find1(int i) {
예) parent[i]=-1, 0≤i<n
union(0,1); find(0);
for (; parent[i]>=0; i=parent[i]) ;
union(1,2); find(0);
return i;
}
int union1(int i, int j) {
parent[i] = j;
}
…..
union(n-2, n-1), find(0);
# 복잡도
n-1번의 union : Ο(n)
n-1번의 find : Ο(n2)
n-1
n-2
..
0
51
집합 표현(계속)
개선 1 : union(i,j)에 가중 법칙을 적용
노드 수가 적은 트리가 서브 트리로 됨
구현 : 트리 노드 수를 루트에 기억 --> parent 필드에 음수로
void union2(int i, int j) {
예) parent[i]=-1, 0≤i<n
int temp = parent[i]+parent[j];
union(0,1); find(0);
if (parent[i]>parent[j] {
union(0,2); find(0);
parent[i]=j;
…..
parent[j]=temp;
union(0, n-1), find(0);
}
1
else {
# 복잡도
parent[j]=i;
n-1번의 union : Ο(n)
parent[i]=temp;
n-1번의 find : Ο(n)
}
# 일반화
}
find 연산 시간 : Ο(log2n)
n-1번 union과 m번 find :
Ο(n+mlog2n)
0
2 ... n-1
52
집합 표현(계속)
개선 2 : find(i)에 붕괴 법칙 적용
j가 i에서 루트로 가는 경로 위에 있다면 j를 루트의 자식으로
int find2(int i) {
int root, trail, lead;
for (root=i; parent[root]>=0; root=parent[root]) ;
for (trail=i; trail!=root; trail=lead) {
lead = parent[trail];
parent[trail] = root;
}
return root;
}
분석
• find 수행 시 약 2배의 시간, but 연속적인 find 수행 시 최악 경우 회피
53
집합 표현(계속)
예) parent[i]=-1, 0≤i<7에 대해
union(0,1);
union(2,3);
union(4,5);
union(6,7);
union(0,2);
union(4,6);
union(0,4);
n번의 find(7);
[-8]
0
4
3
5
6
7
[-8]
0
* 분석
find1 이용 : 3n회의 이동
find2 사용 : n+2회의 이동
3회의 링크 변경
2
1
1
2
4
3
6
7
5
54