자료구조론 9장 트리(tree)
이 장에서 다룰 내용 트리 이진 트리 이진 트리의 구현 이진 트리의 순회 이진 탐색 트리 힙
트리 트리(tree) 원소들 간에 1:多 관계를 가지는 비선형 자료구조 원소들 간에 계층관계를 가지는 계층형 자료구조 상위 원소에서 하위 원소로 내려가면서 확장되는 트리(나무) 모양의 구조 루트(root) 하나의 뿌리에서 가지가 뻗어나가면서 확장되어 잎이 달리는 구조 리프(leaf) 리프(leaf) 리프(leaf) 리프(leaf)
트리 트리 자료구조의 예 – 가계도 가계도의 자료 : 가족 구성원 자료를 연결하는 선 : 부모-자식 관계 표현
트리 준식의 자식 – 성호, 영주, 진호 성호, 영주, 진호의 부모 – 준식 성호, 영주, 진호는 형제관계 수영의 조상 : 승완, 성호, 준식 성호의 자손 : 승우, 승완, 수영, 수철 선을 따라 내려가면서 다음 세대로 확장 가족 구성원 누구든지 자기의 가족을 데리고 분가하여 독립된 가 계를 이룰 수 있다.
트리 트리 A
트리 노드 – 트리의 원소 트리 A의 노드 - A,B,C,D,E,F,G,H,I,J,K,L 루트(root) 노드 – 트리의 시작 노드 트리 A의 루트노드 - A 간선(edge) – 노드를 연결하는 선. 부모 노드와 자식 노드를 연 결 형제(sibling) 노드 – 같은 부모 노드의 자식 노드들 H, I, J 는 형제 노드 조상(ancestor) 노드 – 간선을 따라 루트 노드까지 이르는 경로 에 있는 모든 노드들 K의 조상 노드 : F, B, A 서브 트리(subtree) – 부모 노드와 연결된 간선을 끊었을 때 생성 되는 트리 각 노드는 자식 노드의 개수 만큼 서브 트리를 가진다. 자손(descendant) 노드 – 서브 트리에 있는 하위 레벨의 노드들 B의 자손 노드 – E,F,K,L
트리 노드의 차수(degree) : 노드에 연결된 자식 노드의 수. A의 차수=3, B의 차수=2, C의 차수=1 단말(terminal) 노드 또는 리프(leaf) 노드: 차수가 0인 노드. 자식이 없는 노드 노드의 높이(height) : 루트에서 노드에 이르는 간선의 수. 노드 의 레벨 B의 높이=1, F의 높이=2 트리의 높이(height) : 트리에 있는 노드의 높이 중에서 가장 큰 값. 최대 레벨 트리 A의 높이=3
트리 tree 포리스트(forest) : 서브 트리의 집합 트리A에서 노드 A를 제거하면, A의 자식 노드 B, C, D에 대 한 서브 트리가 생기고, 이들의 집합은 포리스트가 된다. A tree B C D E F G H I J K L
트리 forest 루트 트리1 트리2 트리3 B C D E F G H I J K L
이진트리 이진트리(binary tree) 트리의 모든 노드가 왼쪽 자식 노드와 오른쪽 자식 노드 만을 가지도 록 함으로써 트리의 차수가 2 이하가 되도록 제한한 트리 이와 같이 노드 구조를 일정하게 정의하면 트리의 구현과 연산이 단 순화 된다. root root root left child right child left child right child
이진트리 이진트리의 순환적 구성 루트 노드의 왼쪽 자식 노드를 루트로 하는 왼쪽 서브트리도 이 진 트리 루트 노드의 오른쪽 자식 노드를 루트로 하는 오른쪽 서브 트리 도 이진 트리 루트 A A A의 왼쪽 서브트리 A의 오른쪽 서브트리 루트 루트 B C B의 왼쪽 서브트리 B의 오른쪽 서브트리 C의 왼쪽 서브트리 C의 오른쪽 서브트리 D E F G H I J K L
이진트리 이진 트리에 대한 추상 자료형 ADT BinaryTree 데이터 : 공백이거나 루트 노드, 왼쪽 서브 트리, 오른쪽 서브 트리로 구성된 노드들의 유한 집합 연산 : bt, bt1, bt2∈BinaryTree; item∈Element; createBT() ∷= create an empty binary tree; // 공백 이진 트리를 생성하는 연산 isEmpty(bt) ∷= if (bt is empty) then return true else return false; // 이진 트리가 공백인지 아닌지를 확인하는 연산 makeBT(bt1, item, bt2) ∷= return {item을 루트로 하고 bt1을 왼쪽 서브 트리, bt2를 오른쪽 서브 트리로 하는 이진 트리} // 두개의 이진 서브 트리를 연결하여 하나의 이진 트리를 만드는 연산 leftSubtree(bt) ∷= if (isEmpty(bt)) then return null else return left subtree of bt; // 이진 트리의 왼쪽 서브 트리를 구하는 연산 rightSubtree(bt) ∷= if (isEmpty(bt)) then return null else return right subtree of bt; // 이진 트리의 오른쪽 서브 트리를 구하는 연산 data(bt) ∷= if (isEmpty(bt)) then return null else return the item in the root node of bt; // 이진 트리에서 루트 노드의 데이터(item)를 구하는 연산 End BinaryTree [ADT 9-1]
이진트리 이진트리의 특성 정의1) n개의 노드를 가진 이진 트리는 (n-1)개의 간선을 가진다. 정의2) 높이가 h인 이진 트리가 가질 수 있는 노드의 최소 개수는 (h+1)개가 되며, 최대 개수는 (2h+1-1)개가 된다. 한 레벨에 최소한 한 개의 노드가 있어야 하므로 높이가 h인 이 진 트리의 최소 노드의 개수는 (h+1)개 한 노드는 최대 2개의 자식노드를 가지므로 레벨 i의 노드 개수는 최대 2i 높이가 h인 이진트리의 노드 개수는 ∑2i = 2h+1-1 최소 최대
이진트리 이진 트리의 종류 포화 이진 트리(full binary tree) 모든 레벨에 노드가 꽉 차서 포화 상태인 이진 트리 높이가 h일 때, 최대의 노드 개수인 (2h+1-1)개의 노드를 가진 이 진 트리 루트를 1로 하여 다음과 같이 순서대로 번호를 매길 수 있다.
이진트리 완전 이진 트리(complete binary tree) 트리의 높이가 h일 때, 레벨 0부터 레벨 h-1까지는 포화 상태이고, 마지막 레벨 h는 왼쪽부터 차례로 노드가 채워진 이진 트리 A 1 B 2 C 3 D 4 E 5 F 6 G 7 H 8 I 9 J 10 K 11 L 12 13 14 15
이진트리 편향 이진 트리(skewed binary tree) 높이 h에 대한 최소 개수의 노드를 가지면서 한쪽 방향의 자식 노드만을 가진 이진 트리 좌편향 이진 트리 모든 노드가 왼쪽 자식 노드만을 가진 편향 이진 트리 우편향 이진 트리 모든 노드가 오른쪽 자식 노드만을 가진 편향 이진 트리
이진트리의 구현 - 배열 1차원 배열을 이용한 이진트리의 구현 높이가 h인 포화 이진 트리의 노드번호를 배열의 인덱스로 사용 인덱스 0은 비워두고, 인덱스 1에 루트를 저장 인덱스 0에 루트를 저장하도록 구현해도 되지만, 이 경우 인덱스 계산이 교재와 달라짐 예) 완전 이진 트리의 1차원 배열 표현
이진트리의 구현 - 배열 좌편향 이진 트리의 1차원 배열 표현
이진트리의 구현 - 배열 이진 트리의 1차원 배열에서의 인덱스 관계 A B C D H I E F G J K L 1 [0] A B C D E F G H I J K L 1 [1] A [2] 부모노드의 인덱스 = 2 [3] 2 3 [4] B C [5] 왼쪽 자식노드의 인덱스 = 10 오른쪽 자식노드의 인덱스 = 11 [6] 4 5 6 7 D H I E F G [7] [8] 8 9 10 11 12 [9] J K L [10] [11] [12]
이진트리의 구현 - 배열 이진 트리를 1차원 배열로 표현하는 경우 단점 포화 이진트리 또는 완전 이진트리가 아닌 경우, 배열 중간 중간 에 사용하지 않는 원소들을 비워두어야 하므로 메모리 공간 낭비 편향 이진 트리인 경우 가장 낭비가 심하다. 트리의 원소 삽입/삭제에 따라 트리 높이가 커지는 경우 배열의 크기 변경이 어렵다.
이진트리의 구현 - 연결 자료구조 연결 자료구조를 이용한 이진트리의 구현 이진 트리의 모든 노드는 2개의 자식 노드를 가지므로 다음과 같이 일정한 구조의 노드를 사용 이진 트리의 노드 구조 정의 class TreeNode{ Object data; TreeNode left; TreeNode right; }
이진트리의 구현 - 연결 자료구조 완전 이진 트리의 연결 자료구조 표현
이진트리의 구현 - 연결 자료구조 좌편향 이진 트리의 연결 자료구조 표현
이진트리의 순회 이진 트리의 순회(traversal) 계층적 구조로 저장되어있는 트리의 모든 노드를 방문하여 데이터를 처리하는 연산 순회를 위해 수행할 수 있는 작업을 다음과 같이 정의하자. ⑴ 현재 노드를 방문하여 데이터를 처리하는 작업 D ⑵ 현재 노드의 왼쪽 서브트리를 처리하는 작업 L ⑶ 현재 노드의 오른쪽 서브트리를 처리하는 작업 R 이진 트리가 순환적으로 정의되므로, 순회도 순환적으로 이루어짐 순회의 종류 전위 순회 : DLR 중위 순회 : LDR 후위 순회 : LRD 현재 노드 작업 D 작업 L 작업 R
이진트리의 순회 전위 순회(preorder traversal) 수행 방법 ① 현재 노드 n을 방문하여 처리: D ② 현재 노드 n의 왼쪽 서브트리를 처리 : L ③ 현재 노드 n의 오른쪽 서브트리를 처리 : R 전위 순회 알고리즘 preorder(T) if (T≠null) then { visit T.data; preorder(T.left); preorder(T.right); } end preorder() [알고리즘 9-1]
이진트리의 순회 전위 순회의 예 A-B-D-H-E-I-J-C-F-G-K A B C D E F G K H I J
이진트리의 순회 수식 이진 트리의 전위 순회 수식을 이진 트리로 구성한 수식 이진 트리를 전위 순회하면, 수 식에 대한 전위 표기식을 구할 수 있다. 예 A * B – C / D - * A B / C D - * / A B C D
이진트리의 순회 중위 순회(inorder traversal) 수행 방법 ① 현재 노드 n의 왼쪽 서브트리를 처리 : L ② 현재 노드 n을 방문하여 처리 : D ③ 현재 노드 n의 오른쪽 서브트리를 처리 : R 중위 순회 알고리즘 inorder(T) if (T≠null) then { inorder(T.left); visit T.data; inorder(T.right); } end inorder() [알고리즘 9-2]
이진트리의 순회 중위 순회의 예 H-D-B-I-E-J-A-F-C-G-K A B C D E F G K H I J
이진트리의 순회 수식 이진 트리의 중위 순회 수식 이진 트리를 중위 순회하면, 수식에 대한 중위 표기식을 구 할 수 있다. 예 A * B – C / D A * B – C / D - * / A B C D
이진트리의 순회 후위 순회(postorder traversal) 수행 방법 ① 현재 노드 n의 왼쪽 서브트리를 처리 : L ② 현재 노드 n의 오른쪽 서브트리를 처리 : R ③ 현재 노드 n을 방문하여 처리 : D 후위 순회 알고리즘 postorder(T) if (T≠null) then { postorder(T.left); postorder(T.right); visit T.data; } end postorder() [알고리즘 9-3]
이진트리의 순회 후위 순회의 예 H-D-I-J-E-B-F-K-G-C-A A B C D E F G K H I J
이진트리의 순회 수식 이진 트리의 후위 순회 수식 이진 트리를 후위 순회하면, 수식에 대한 후위 표기식을 구 할 수 있다. 예 A * B – C / D A B * C D / - - * / A B C D
이진트리의 순회 이진 트리의 순회 프로그램 public class LinkedTree{ private TreeNode root = null; public TreeNode makeBT(TreeNode bt1, Object data, TreeNode bt2){ TreeNode root = new TreeNode(); root.data = data; root.left = bt1; root.right = bt2; return root; } public void preorder(TreeNode root){ if(root != null){ System.out.print(root.data); preorder(root.left); preorder(root.right); [예제 9-1] public class TreeNode{ Object data; TreeNode left; TreeNode right; }
이진트리의 순회 public void inorder(TreeNode root){ if(root != null){ inorder(root.left); System.out.print (root.data); inorder(root.right); } public void postorder(TreeNode root){ postorder(root.left); postorder(root.right); System.out.print(root.data); [예제 9-1]
이진트리의 순회 public class Ex9_1{ public static void main(String[] args){ LinkedTree T = new LinkedTree(); TreeNode n7 = T.makeBT(null, 'D', null); TreeNode n6 = T.makeBT(null, 'C', null); TreeNode n5 = T.makeBT(null, 'B', null); TreeNode n4 = T.makeBT(null, 'A', null); TreeNode n3 = T.makeBT(n6, '/', n7); TreeNode n2 = T.makeBT(n4, '*', n5); TreeNode n1 = T.makeBT(n2, '-', n3); System.out.print("\n Preorder : "); T.preorder(n1); System.out.print("\n Inorder : "); T.inorder(n1); System.out.printf("\n Postorder : "); T.postorder(n1); } [예제 9-1]
이진 탐색 트리 이진 탐색 트리(binary search tree) “이진 검색 트리” 이진 트리에 탐색을 위한 조건을 추가한 자료구조로서 다음과 같이 정의된다. ⑴ 모든 원소는 서로 다른 유일한 키(key)를 갖는다. ⑵ 왼쪽 서브트리 원소의 키들은 그 루트의 키보다 작다. ⑶ 오른쪽 서브트리 원소의 키들은 그 루트의 키보다 크다. ⑷ 왼쪽 서브트리와 오른쪽 서브트리도 이진 탐색 트리이다.
이진 탐색 트리 이진 탐색 트리의 탐색 연산 루트에서 시작한다. 탐색할 키값 x를 루트 노드의 키값과 비교한다. 원하는 원소를 찾았으므로 탐색연산 성공 (x < 루트노드의 키 값)인 경우 : 루트노드의 왼쪽 서브트리에 대해서 탐색연산 수행 (x > 루트노드의 키 값)인 경우 : 루트노드의 오른쪽 서브트리에 대해서 탐색연산 수행 서브트리에 대해서 순환적으로 탐색 연산을 반복한다.
이진 탐색 트리 탐색 알고리즘 예) 원소 11 탐색 searchBST(bsT, x) // bsT를 루트로 하는 이진탐색트리에서 // 키값이 x인 노드를 찾아 리턴;. 탐색에 실패하면 null 리턴 if (bsT = null) then return null; if (x = bsT.key) then return bsT; if (x < bsT.key) then return searchBST(bsT.left, x); else return searchBST(bsT.right, x); end searchBST()
이진 탐색 트리 이진 탐색 트리의 삽입 연산 1) 먼저 탐색 연산을 수행 삽입할 원소와 같은 원소가 트리에 있으면 삽입할 수 없으므로, 같은 원소가 트리에 있는지 탐색하여 확인한다. 탐색에서 탐색 실패가 결정되는 위치가 원소를 삽입할 자리가 된 다. 2) 탐색 실패한 위치에 원소를 삽입한다.
이진 탐색 트리 삽입 알고리즘 insertBST(bsT, x) // bsT를 루트로 하는 이진탐색트리에 키값 x 인 노드를 삽입하고, // 삽입된 트리의 루트 노드를 리턴 p ← bsT; while (p ≠ null) do { if (x = p.key) then return; q ← p; if (x < p.key) then p ← p.left; else p ← p.right; } newNode ← getNode(); newNode.key ← x; newNode.left ← null; newNode.right ← null; if (bsT = null) then return newNode else if (x < q.key) then q.left ← newNode; else q.right ← newNode; return bsT; end insertBST() 삽입할 자리 탐색 삽입할 노드 만들기 탐색한 자리에 노드연결
이진 탐색 트리 예) 원소 4 삽입 q newNode
이진 탐색 트리 연결 자료구조로 구현한 이진트리에서의 원소 4 삽입 q q 탐색실패! q
이진 탐색 트리 이진 탐색 트리의 삭제 연산 1) 먼저 탐색 연산을 수행하여 삭제할 노드를 찾는다. 2) 찾은 노드를 삭제한다. 다음과 같은 3가지 경우로 나눌 수 있다. [경우1] 삭제할 노드가 단말노드인 경우 [경우2] 삭제할 노드가 하나의 자식노드를 가진 경우 [경우3] 삭제할 노드가 두개의 자식노드를 가진 경우 노드의 삭제 후에도 이진 탐색 트리를 유지해야 하므로 이진 탐 색 트리의 재구성 작업(후속 처리)이 필요하다.
이진 탐색 트리 [경우1] 단말 노드의 삭제 – 간단 예) 노드 4 삭제
이진 탐색 트리 노드 4를 삭제하는 경우에 대한 단순 연결 리스트 표현 노드를 삭제하고, 삭제한 노드의 부모 노드의 링크 필드를 null로 설정
이진 탐색 트리 [경우2] 자식 노드가 하나인 노드의 삭제 노드 p를 삭제하면, p의 자식 노드는 트리에서 연결이 끊어져서 고아가 된다. 후속 처리 : 삭제한 노드의 자리를 자식 노드에게 물려준다. 예) 노드 10 삭제 1단계: 삭제할 노드 탐색 탐색시작 2단계: 탐색한 노드 삭제 3단계: 후속처리 8 ① 10 > 8 8 3 10 ② 10 = 10 3 자식노드 이동 11 16 14 2 5 11 16 14 2 5 4 4
이진 탐색 트리 [경우3] 자식 노드가 둘인 노드의 삭제 노드 p를 삭제하면, p의 자식 노드들은 트리에서 연결이 끊어져 고아가 된다. 후속 처리 : 삭제한 노드의 자리를 자손 노드들 중에서 선택한 후 계자에게 물려준다. 두가지 후계자 선택 방법 왼쪽 서브트리에서 가장 큰 자손노드 선택 : 왼쪽 서브트리 의 오른쪽 링크를 따라 계속 이동하여 오른쪽 링크 필드가 NULL인 노드 즉, 가장 오른쪽에 있는 노드가 후계자가 된 다. 오른쪽 서브트리에서 가장 작은 자손노드 선택 : 오른쪽 서 브트리에서 왼쪽 링크를 따라 계속 이동하여 왼쪽 링크 필 드가 NULL인 노드 즉, 가장 왼쪽에 있는 노드가 후계자가 된다.
이진 탐색 트리 삭제한 노드의 자리를 물려받을 수 있는 후계자 노드 삭제한 노드
이진 탐색 트리 예) 노드 8 삭제
이진 탐색 트리 노드 5를 후계자로 선택한 경우 ① 후계자 노드 5를 삭제하여 삭제노드 8의 자리로 옮긴다. ② 이 때 후계자 노드는 하나의 자식 노드만을 가지므로 노드 삭제 [경우2]에 해당하는 후속 처리를 해 준다. 즉, 5의 원래자리는 자식노드 4에게 물려준다. (후계자 노드가 단말 노드이면 별도의 후속 처리 필요 없다) 노드 삭제 8 3 10 2 5 14 4 11 16
이진 탐색 트리 노드 10을 후계자로 선택한 경우 (앞의 예와 대칭임) ① 후계자 노드 10을 삭제하여 삭제노드 8의 자리로 옮긴다. ② 이 때 후계자 노드는 하나의 자식 노드만을 가지므로 노드 삭제 [경우2]에 해당하는 후속 처리를 해 준다. 즉, 10의 원래자리는 자식노드 14에게 물려준다. (후계자 노드가 단말 노드이면 별도의 후속 처리 필요 없다) 노드 삭제 8 3 10 2 5 11 16 14 4
이진 탐색 트리 삭제 알고리즘 deleteBST(bsT, x) // bsT를 루트로 하는 이진탐색트리에서 키값 x 인 노드를 // 삭제하고, 삭제된 트리의 루트 노드를 리턴 p ← 삭제할 노드; parent ← 삭제할 노드의 부모 노드; if (p = null) then 삭제 실패; if (p.left = null and p.right = null) then { // [경우1] if (parent = null) then return null; else if (parent.left = p) then parent.left ← null; else parent.right ← null; }
이진 탐색 트리 else if (p.left = null or p.right = null) then { // [경우2] if (p.left ≠ null) then { if (parent = null) return p.left; else if (parent.left = p) then parent.left ← p.left; else parent.right ← p.left; } else { if (parent = null) return p.right; else if (parent.left = p) then parent.left ← p.right; else parent.right ← p.right; else { // [경우3] 방법 1 q ← maxNode(p.left); p.key ← q.key; p.left ← deleteBST(p.left, p.key); return bsT; end deleteBST()
이진 탐색 트리 이진 탐색 트리의 연산 프로그램 public class BinarySearchTree{ private TreeNode root = new TreeNode(); public TreeNode insertKey(TreeNode root, char x){ TreeNode p = root; TreeNode newNode = new TreeNode(); newNode.data = x; newNode.left = null; newNode.right = null; if(p == null) return newNode; else if(newNode.data < p.data){ p.left = insertKey(p.left, x); return p; } else if(newNode.data > p.data){ p.right = insertKey(p.right, x); else return p; [예제 9-2] public class TreeNode{ char data; TreeNode left; TreeNode right; }
이진 탐색 트리 public void insertBST(char x){ root = insertKey(root, x); } public TreeNode searchBST(char x){ TreeNode p = root; while(p != null){ if(x < p.data) p = p.left; else if (x > p.data) p = p.right; else return p; return p; public void inorder(TreeNode root){ if(root != null){ inorder(root.left); System.out.print(" " + root.data); inorder(root.right); public void printBST(){ inorder(root); System.out.println(); [예제 9-2]
이진 탐색 트리 class Ex9_2{ public static void main(String args[]){ BinarySearchTree bsT = new BinarySearchTree(); bsT.insertBST('G'); bsT.insertBST('I'); bsT.insertBST('H'); bsT.insertBST('D'); bsT.insertBST('B'); bsT.insertBST('M'); bsT.insertBST('N'); bsT.insertBST('A'); bsT.insertBST('J'); bsT.insertBST('E'); bsT.insertBST('Q'); System.out.print("\nBinary Tree >>> "); bsT.printBST(); System.out.print("Is There \"A\" ? >>> "); TreeNode p1 = bsT.searchBST('A'); if(p1 != null) System.out.println("Searching Success! Searched key : "+ p1.data); else System.out.println("Searching fail!! There is no " + p1.data); System.out.print("Is There \"Z\" ? >>> "); TreeNode p2 = bsT.searchBST('Z'); if(p2 != null) System.out.println("Searching Success! Searched key : " + p2.data); System.out.println("Searching fail!"); } [예제 9-2]
힙 힙(heap) 완전 이진 트리에 있는 노드 중에서 키 값이 가장 큰 노드 또는 키 값 이 가장 작은 노드를 찾기 위해서 만든 자료구조 최대 힙(max heap) 키 값이 가장 큰 노드를 찾기 위한 완전 이진 트리 {부모노드의 키 값 ≥ 자식노드의 키 값} 루트 노드 : 키 값이 가장 큰 노드 최소 힙(min heap) 키 값이 가장 작은 노드를 찾기 위한 완전 이진 트리 {부모노드의 키 값 ≤ 자식노드의 키 값} 루트 노드 : 키 값이 가장 작은 노드
힙 힙의 예
힙 힙이 아닌 이진 트리의 예
힙 힙의 추상 자료형 ADT MaxHeap 데이터 : n개의 원소로 구성된 완전 이진 트리로서 각 노드의 키값은 그의 자식 노드의 키값보다 크거나 같다.(부모 노드의 키값 ≥ 자식 노드의 키값) 연산 : heap∈Heap; item∈Element; createHeap() ∷= create an empty heap; // 공백 힙의 생성 연산 isEmpty(heap) ∷= if (heap is empty) then return true; else return false; // 힙이 공백인지를 검사하는 연산 insertHeap(heap, item) ∷= insert item into heap; // 힙의 적당한 위치에 원소(item)를 삽입하는 연산 deleteHeap(heap) ∷= if (isEmpty(heap)) then return error; else { item ← 힙에서 가장 큰 원소; remove {힙에서 가장 큰 원소}; return item; } // 힙에서 키값이 가장 큰 원소를 삭제하고 반환하는 연산 End Heap() [ADT 9-2]