540 likes | 780 Views
자료 구조. 제 5 장 : 트리 이 형 원 강릉대학교 컴퓨터공학과. 학습 내용. 기본 용어 이진 트리 이진 트리의 순회 스레드 이진 트리 이진 트리의 응용 히프 이진 탐색 트리 선택 트리 포리스트 집합 표현. 기본 용어. 트리 : 1 개 이상의 노드로 이루어진 유한 집합 하나의 루트 노드 + 0 개 이상의 서브 트리들 서브 트리는 서로 연결될 수 없음 트리의 각 원소는 어떤 서브 트리의 루트가 됨
E N D
자료 구조 제 5 장 : 트리 이 형 원 강릉대학교 컴퓨터공학과
학습 내용 • 기본 용어 • 이진 트리 • 이진 트리의 순회 • 스레드 이진 트리 • 이진 트리의 응용 • 히프 • 이진 탐색 트리 • 선택 트리 • 포리스트 • 집합 표현
기본 용어 • 트리 : 1개 이상의 노드로 이루어진 유한 집합 하나의 루트 노드 + 0개 이상의 서브 트리들 서브 트리는 서로 연결될 수 없음 트리의 각 원소는 어떤 서브 트리의 루트가 됨 • 노드 : 정보 항목 + 가지(edge) • 노드의 차수(degree) : 해당 노드에 대한 서브 트리 수 • 리프(leaf) 노드, 단말(terminal) 노드 : 차수가 0인 노드 • 비단말(non-terminal) 노드 • 부모(parent) , 자식(children), 형제(sibling) • 조상 : 루트에서 해당 노드까지의 경로상에 있는 노드들 • 자손 : 한 노드의 서브 트리에 속한 모든 노드들 • 노드 레벨(level) : 부모의 노드 레벨 + 1(루트의 레벨은 1) • 트리 높이(height)/깊이(depth) : 최대 노드 레벨
I A B C D G F E K H M L 트리의 예 레벨 루트(차수=3) 1 노드 G의 조상 2 부모 자식 자식 J 3 단말 형제 형제 4 트리 높이 노드 D의 자손
트리의 표현 • 리스트 표현 • 노드 구조 : 데이터 링크 1 링크 2 ..… 링크 n • 왼쪽 자식-오른쪽 형제 표현 • 일정 크기의 노드를 다루는 것이 편리 • 노드 구조 : 데이터 왼쪽 자식 오른쪽 형제 • 일반적인 트리에서는 자식의 순서는 중요하지 않음 • 차수가 2인 트리 (왼쪽 자식-오른쪽 자식) 표현 • 왼쪽 자식-오른쪽 형제 트리를 45도 시계방향으로 비틈
J J I I A B C D G F E H M L A L B C D G F E H M K K 트리의 표현(계속)
이진 트리: 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 FALSEBinTree 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 에러else bt의 오른쪽 서브 트리를 반환
I A B C D E A B C D G F E H 이진 트리: 개요 • 어떤 트리도 이진(binary) 트리로 표현할 수 있음 리스트 표현 왼쪽 자식-오른쪽 형제 표현 왼쪽 자식-오른쪽 자식 표현 • 일반 트리와의 차이점 ① 노드의 차수가 2이하 ② 이진 트리는 공백 트리 가능 ③ 이진 트리는 자식의 순서를 구분 • 특수 이진 트리 • 경사(skewed) 트리 • 완전(complete) 이진 트리
9 6 7 4 8 2 1 5 3 13 12 11 15 14 10 이진 트리: 개요(계속) • 특성 • 이진 트리의 레벨 i에서의 최대 노드 수 = 2i-1 (i≥1) • 깊이가 k인 이진 트리의 최대 노드 수 = 2k - 1 (k≥1) • 단말 노드 수 = 차수가 2인 노드 수 + 1 • 포화(full) 이진 트리: 트리의 깊이가 k일 때 노드 수가 2k - 1
이진 트리: 배열 표현 • 일차원 배열에 그림 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 완전 이진 트리
이진 트리: 링크 표현 • 노드 구조: left_child data right_child ↓ ↓ root of left subtree 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 ?
C + * E * / D A B 이진 트리: 순회 • 트리 순회(traversal) • 트리에 있는 모든 노드를 한번씩 방문 • 노드와 서브 트리를 같은 방법으로 처리하는 것이 효율적 • L : 왼쪽으로 이동, V : 노드 방문, R : 오른쪽으로 이동 예제 트리 A/B*C*D+E
이진 트리: 중위 순회 • 방법 : 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
이진 트리: 전위 순회 • 방법 : 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
이진 트리: 후위 순회 • 방법 : 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 +
이진 트리: 반복적 중위 순회 • 방법 : 순환을 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. 시간 복잡도 ?
이진 트리: 레벨 순서 순회 • 루트로부터 단말 노드까지 차례대로 순회 : 큐를 사용 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
이진 트리: 복사 • 후위 순회 함수를 변경 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;}
이진 트리: 동일성 검사 • 전위 순회 함수를 변경 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)); }
┐ ∨ ∨ ┐ ∧ ∧ ┐ x3 x3 x1 x2 x1 이진 트리: 만족성 문제 • 명제식의 값이 참이 되도록 할 수 있는가 변수 x1, .., xn과 연산자 ∧(and), ∨(or), ┐(not)으로 이루어진 식 • Ο(g2n) : g는 변수에 값을 대입 & 식을 계산하는 시간 • 예: (x1∧┐x2)∨(┐x1∧x3)∨┐x3에 대한 이진 트리 중위 순회 결과 x1∧┐x2∨┐x1∧x3∨┐x3
이진 트리: 만족성 문제(계속) • 방법 • 트리를 후위 순회 • 노드 방문 시 두 서브 트리의 값을 이용하여 계산 • 노드 구조 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는 이 노드가 나타내는 변수의 현재 값을 갖는다고 가정
이진 트리: 만족성 문제(계속) • 초기 알고리즘 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");
이진 트리: 만족성 문제(계속) 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; }} }
I A B C D G F E H 스레드(threaded) 이진 트리 • 스레드 • null link를 다른 노드를 가리키는 ptr인 스레드로 대치 • 스레드 구성의 규칙 ptr->left_child가 널이면 ptr->left_child=ptr의 중위 선행자로의 포인터 ptr->right_child가 널이면 ptr->right_child=ptr의 중위 후속자로의 포인터 root 중위 순회 H D I B E A F C G
스레드 이진 트리: 노드 구조 • 스레드와 정상적인 포인터를 구별 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;}; • 분실 스레드 : 헤드 노드로 해결
f • A • f f • B • f f • – • f f • C • f f • D • f t • I • t t • F • t t • G • t t • H • t t • E • t 스레드 이진 트리: 예 left_thread left_child data right_child right_thread _ TRUE FALSE ° ° root
스레드 이진 트리: 중위 순회 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. 시간 복잡도 ? Q. 전위 및 후위 순회 ?
C C A A B D B D 스레드 이진 트리: 노드 삽입 • 노드 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 parent child
C C E E A F D D F A X X B B 스레드 이진 트리: 노드 삽입(계속) ⑵ 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 parent child
스레드 이진 트리: 노드 삽입(계속) 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; }}
14 2 7 4 12 7 10 10 8 8 6 6 히프(heap): 정의 • 최대 히프와 최소 히프 • 최대 트리 : 각 노드의 키 값이 자식의 키 값보다 작지 않은 트리 • 최대 히프 : 최대 트리인 완전 이진 트리 (루트 : 가장 큰 키 값) • 최소 트리 : 각 노드의 키 값이 자식의 키 값보다 크지 않은 트리 • 최소 히프 : 최소 트리인 완전 이진 트리 (루트 : 가장 작은 키 값) • 히프의 표현 : 배열 Q. Why ? [1] [1] [2] [3] [2] [3] [4] [5] [6] [4] [5] [6]
히프(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 FALSEMaxHeap Insert(heap, item, n) ::= if (!HeapFull(heap, n)) item을 삽입하고 그 히프를 반환 else return 에러Boolean HeapEmpty(heap, n) ::= if (n>0) return TRUE else return FALSEElement Delete(heap, n) ::= if (!HeapEmpty(heap, n)) 히프의 가장 큰 원소를 제거하고 반환else return 에러
히프: 우선 순위 큐 • 우선 순위 큐 • 우선 순위가 가장 높은(낮은) 원소를 먼저 삭제하는 큐 예) OS의 작업 스케쥴러 • 우선 순위 큐의 표현 방법 표현 방법 삽입 삭제 순서없는 배열 Θ(1) Θ(n) 순서없는 연결 리스트 Θ(1) Θ(n) 정렬된 배열 O(n) Θ(1) 정렬된 연결 리스트 O(n) Θ(1) 최대 히프 O(log2n) O(log2n)
21 20 20 20 2 2 15 15 15 10 10 2 10 14 14 14 히프: 최대 히프에서의 삽입 • 방법 • 완전이진 트리를 유지 : 삽입 위치는 일정 • 최대 트리의 유지 : 한 원소에서 부모를 찾아갈 수 있어야 함 연결 리스트 사용 : 부모 필드 첨가배열 사용 : 추가 필드없이 부모 찾기가 용이 [1] [1] [1] [2] [3] [2] [3] [2] [3] [4] [5] [4] [5] [4] [5] [6] [6]
히프: 최대 히프에서의 삽입(계속) #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. 시간 복잡도 ?
10 20 15 2 2 2 2 15 14 15 15 10 10 14 10 14 14 히프: 최대 히프에서의 삭제 • 방법 • 항상 히프의 루트에서 삭제 • 완전 이진 트리 및 최대 트리를 만족하도록 재구성 [1] [1] [1] [1] [2] [3] [2] [3] [2] [3] [2] [3] [4] [5] [4] [5] [4] [4]
히프: 최대 히프에서의 삭제(계속) 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. 시간 복잡도 ?
20 30 60 15 5 10 22 80 25 40 70 12 2 65 이진 탐색 트리: 개요 • 이진 탐색 트리 • 임의의 원소 탐색/삽입/삭제에 적합 Q. 히프 ? • 정의 • 모든 원소는 유일한 값의 키를 가짐 • 왼쪽 서브트리 키 < 그 서브트리의 루트 키 < 오른쪽 서브트리 키 • 왼쪽과 오른쪽 서브 트리도 이진 탐색 트리 • 이진 트리의 하나 : C 선언 및 기존 연산이 모두 적용
이진 탐색 트리: 탐색 • 순환 탐색과 반복 탐색 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;}
30 30 30 5 5 5 40 40 40 2 35 2 80 2 80 이진 탐색 트리: 삽입 • 방법 동일한 키 값을 갖는 원소가 존재하는지 확인하기 위해 탐색 탐색이 실패하면 탐색이 종료된 지점에 원소 삽입
이진 탐색 트리: 삽입(계속) 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: }} Q. 시간 복잡도 ?
30 30 30 5 5 5 80 40 40 35 80 2 80 2 2 이진 탐색 트리: 삭제 • 삭제 노드의 형태 ① 리프 노드 L L 삭제 L의 부모 노드의 자식 필드를 NULL ② 하나의 자식을 갖는 비단말 노드 S S 삭제 S의 자식을 삭제된 노드의 자리에 위치
60 55 40 40 20 20 50 50 10 70 30 55 45 70 52 10 30 45 52 이진 탐색 트리: 삭제(계속) ③ 두개의 자식을 갖는 비단말 노드 D D를 (left subtree의 largest 노드 or right subtree의 smallest 노드)로 대체 선택된 subtree에서 치환된 원소(항상 차수가 0 또는 1)의 삭제 Q. 알고리즘의 복잡도 ? 삭제 노드
이진 탐색 트리: 높이 • n개의 원소를 갖는 이진 탐색 트리의 높이 • 최악의 경우 n : 함수 insert_node를 이용하여 1,2,...,n을 삽입 • 평균적으로 Ο(log2 n) : 삽입과 삭제가 random • 균형 탐색 트리 • 최악의 경우에도 높이가Ο(log2 n)인 탐색 트리 • 예 : AVL 트리,2-3 트리, 레드-블랙 트리 • 탐색, 삽입, 삭제가 Ο(h)
선택 트리 • 문제 • K 개의 순서 순차를 하나의 순서 순차(비감소)로 합병 • 가정 • 각 순차(런)는 키 필드에 따라 비감소 순서로 정렬된 레코드들로 구성 • k개의 런에 있는 레코드의 총 수는 n • 방법 1 for ( i=1; i<= n; i++) { k개의 런의 첫번째 레코드들 중 가장 작은 키 값의 레코드를 선택 해당 레코드를 출력 해당 레코드가 속해 있는 런에서 그 레코드를 삭제 } Q. 시간 복잡도 ?
선택 트리(계속) • 방법 2 : 선택 트리 사용 • 선택 트리 • 각 노드가 두 개의 자식 노드 중 더 작은 노드를 표현하는 이진 트리 • 더 작은 키 값을 갖는 레코드가 승자가 되는 토너먼트와 동일 • 각 노드는 그 노드를 표현하는 레코드에 대한 포인터만 포함 : 배열 사용 • 토너먼트는 형제 노드간 시행, 결과는 부모 노드에 위치 • 배열 표현이므로 형제, 부모 노드의 주소 계산이 효율적 • 알고리즘 선택 트리 초기화 for ( i=1; i<= n; i++) 루트 노드를 출력하고 선택 트리를 재구성 • 시간 복잡도 : Ο(n log2 k)
100110… 100110… 8 6 8 6 9 8 8 9 8 9 90 9 15 20 17 9 90 17 9 6 9 20 15 6 17 17 1516… 1550… 2030… 1116… 1525… 2038… 2530… 1820… 1550… 2038… 1516… 2030… 1116… 1820… 런7 런7 10 8 8 10 런5 런3 런4 런2 런1 런8 런8 런6 런5 런4 런3 런2 런1 런6 선택 트리(계속) 1 1 2 2 3 3 6 7 6 7 4 5 4 5 8 8 9 10 11 12 13 14 15 9 10 11 12 13 14 15
G A E E G A B H H B D I D I F C C F 포리스트 • n≥0개의 트리들의 집합 (트리의 루트 제거) • 포리스트의 이진 트리 변환 방법 • 포리스트의 각 트리를 이진 트리로 변환 • 첫 번째 트리의 루트로부터 오른쪽 서브 트리를 따라 나머지 트리들의 루트들을 차례대로 연결
0 4 2 6 1 3 8 9 5 7 집합 표현 • 개요 • 집합의 원소는 0,1,...,n-1 등의 수 • 모든 집합들은 서로 분리(disjoint) 관계 • 집합의 포리스트 표현 (링크의 방향 주의) S1 = {0, 6, 7, 8}, S2 = {1, 4, 9}, S3= {2, 3, 5} • 수행 연산 ⑴ Union(i,j) : SiU Sj ⑵ Find(i) : 원소 i가 포함된 집합 찾기 S1 S2 S3
집합 포인터 이름 S1 S2 S3 4 0 4 0 2 1 6 3 1 6 9 8 5 8 9 7 7 집합 표현(계속) • union 연산 • 트리를 다른 트리의 서브 트리로 • 구현: 한 루트의 부모 필드가 다른 트리의 루트를 가리키게 함 • find 연산 • 각 루트가 집합 이름에 대한 포인터를 갖게 함 • 구현: 루트로 연결된 부모 링크를 따라 올라가서 집합 이름을 가리키는 포인터를 찾음 • 편의상, 트리의 루트를 집합이름으로 사용 S1 U S2