Q. 시간 복잡도

Download Report

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