이진트리(Binary Tree) 클래스 선언

  • 전위순회(preorder)
  • 중위순회(inorder)
  • 후위순회(postorder)
  • 레벨순회(levelorder)
  • 높이(height)
  • 노드 수(size)
In [1]:
class Node:
  def __init__(self, item, left=None, right=None): # 노드 생성자
    self.item  = item 
    self.left  = left 
    self.right = right 

class BinaryTree:
  def __init__(self): # 트리 생성자
    self.root = None 

  def preorder(self, n): # 전위순회
    if n != None:
      print(str(n.item),' ', end='')
      if n.left:
        self.preorder(n.left)
      if n.right:
        self.preorder(n.right)
 
  def inorder(self, n): # 중위순회
    if n != None:
      if n.left:
        self.inorder(n.left)
      print(str(n.item),' ', end='')
      if n.right:
        self.inorder(n.right)

  def postorder(self, n): # 후위순회
    if n != None:
      if n.left:
        self.postorder(n.left)
      if n.right:
        self.postorder(n.right)
      print(str(n.item),' ', end='')
        
  def levelorder(self, root): # 레벨순회
    q = []
    q.append(root)
    while len(q) != 0:  
      t = q.pop(0) 
      print(str(t.item), ' ', end='')
      if t.left != None: 
        q.append(t.left)  
      if t.right != None: 
        q.append(t.right)  

  def height(self, root): # 트리 높이 계산
    if root == None:
      return 0
    return max(self.height(root.left), self.height(root.right)) + 1
  
  def size(self, root): # 트리 노드 수 계산
    if root is None:
      return 0
    else:
      return 1 + self.size(root.left) + self.size(root.right)  
   
  def copy_tree(self, n): # 트리 복제
    if n == None: 
      return None
    else: 
      left  = self.copy_tree(n.left)
      right = self.copy_tree(n.right)
      return self.Node(n.item, left, right)
   
  def is_equal(self, n, m): # 두 트리의 동일성 검사
    if n == None or m == None: 
      return n == m    
    if n.item != m.item: 
      return False    
    return( self.is_equal(n.left,  m.left) and  
      self.is_equal(n.right, m.right) )

이진트리 순회 적용

In [2]:
#from binary_tree import BinaryTree, Node
if __name__ == '__main__':
  t = BinaryTree() # 이진트리 객체 t 생성 
  n1 = Node(100)   # 8개의 노드 생성  
  n2 = Node(200)
  n3 = Node(300)  
  n4 = Node(400)  
  n5 = Node(500)  
  n6 = Node(600)
  n7 = Node(700)  
  n8 = Node(800)
  n1.left  = n2  # n1의 왼쪽 자식->  n2
  n1.right = n3  # n1의 오른쪽 자식-> n3
  n2.left  = n4  # n2의 왼쪽 자식->  n4
  n2.right = n5  # n2의 오른쪽 자식-> n5
  n3.left  = n6  # n3의 왼쪽 자식->  n6
  n3.right = n7  # n3의 오른쪽 자식-> n7
  n4.left  = n8  # n4의 왼쪽 자식->  n8     
  t.root = n1  # t의 루트노드를 n1으로
  print('트리 높이 =', t.height(t.root))
  print('전위순회:\t', end='')
  t.preorder(t.root)
  print('\n중위순회:\t', end='')
  t.inorder(t.root)
  print('\n후위순회:\t', end='')
  t.postorder(t.root)
  print('\n레벨순회:\t', end='')
  t.levelorder(t.root)
트리 높이 = 4
전위순회:	100  200  400  800  500  300  600  700  
중위순회:	800  400  200  500  100  600  300  700  
후위순회:	800  400  500  200  600  700  300  100  
레벨순회:	100  200  300  400  500  600  700  800  

스레드 이진트리(Threaded Binary Tree)

  • 이진트리는 스택을 사용 -> 스택에 사용되는 메모리는 트리의 높이에 비례
  • 스택없이 이진트리 연산 방법
    • Node 객체에 부모를 가리키는 레퍼런스 추가 선언
    • 노드의 None 레퍼런스들을 활용(스레드 이진트리)
  • 스레드는 운영체제에서 스케줄러가 운영하는 독립적인 수행 단위인 스레드와는 전혀 관계 없는 단어

이진힙(Binary Heap)

  • 우선순위큐(Priority Queue)를 구현하는 가장 기본적인 자료구조이다.
  • 우선순위큐(Priority Queue)
    • 가장 높은 우선순위를 가진 항목에 접근, 삭제와 임의의 우선순위를 가진 항목을 삽입을 지원하는 자료구조
    • 스택이나 큐도 일종의 우선순위큐
      • 스택: 가장 마지막으로 삽입된 항목이 가장 높은 우선순위를 가지므로, 최근 시간일수록 높은 우선순위를 부여
      • 큐: 먼저 삽입된 항목이 우선순위가 더 높다. 따라서 이른 시간일수록 더 높은 우선순위를 부여
    • 스택과 큐와 같은 우선순위큐가 있는데, 왜 또 다른 우선순위큐 자료구조가 필요할까?
      • 스택에 삽입되는 항목의 우선순위는 스택에 있는 모든 항목들의 우선순위보다 높음
      • 큐에 삽입되는 항목의 우선순위는 큐에 있는 모든 항목들의 우선수위보다 낮음
      • 삽입되는 항목이 임의의 우선순위를 가지면 스택이나 큐는 새 항목이 삽입될 때마다 저장되어 있는 항목들을 우선순위에 따라 정렬해야 하는 문제점이 있음
  • 이진힙의 종류:
    • 최소힙(Minimum Heap): 키 값이 작을수록 높은 우선순위
    • 최대힙(Maximum Heap): 키 값이 클수록 더 높은 우선순위
  • 최소힙의 루트에는 항상 가장 작은 키가 저장됨
    • 부모에 저장된 키가 자식의 키보다 작다는 규칙 때문
    • 루트는 a[1]에 있으므로, O(1) 시간에 min 키를 가진 노드 접근

최소힙 클래스 구성

In [3]:
class BHeap:      
  def __init__(self, a): # 생성자  
    self.a = a  # a[0] 사용 안함
    self.N = len(a) - 1 # 힙의 항목 수
    
  def create_heap(self): # 초기 힙 만들기   
    for i in range(self.N//2, 0, -1):
      self.downheap(i)
      
  def insert(self, key_value): # 삽입 연산
    self.N += 1
    self.a.append(key_value) # 새로운 키(항목)를 맨 끝에 저장
    self.upheap(self.N) # 위로 올라가며 힙속성 회복시키기 위해
     
  def delete_min(self): # 최솟값 삭제
    if self.N == 0:  # underflow 경우
      print('힙이 비어 있음')
      return None
    minimum = self.a[1]  # a[1]의 최솟값을 minimum에 저장하여 리턴
    self.a[1], self.a[-1] = self.a[-1], self.a[1]  # 힙의 마지막 항목과 교환
    del self.a[-1]  # 힙의 마지막 항목 삭제
    self.N -= 1
    self.downheap(1) # 힙속성을 회복시키기 위해
    return minimum
  
  def downheap(self, i): # 힙 내려가며 힙속성 회복
    while 2*i <= self.N: # i의 왼쪽자식이 힙에 있으면
      k = 2*i  # k는 왼쪽자식의 인덱스
      if k < self.N and self.a[k][0] > self.a[k+1][0]: #왼쪽과 오른쪽자식의 승자를 결정하여 k가 승자의 인덱스가됨
        k += 1  
      if self.a[i][0] < self.a[k][0]:
        break  # 현재 노드가 자식 승자보다 작으면, 루프를 중단하고
      self.a[i], self.a[k] = self.a[k], self.a[i] # 현재 노드가 자식 승자보다 크면 현재 노드와 자식 승자와 교환
      i = k   # 자식 승자가 현재 노드가 되어 다시 반복하기 위해
    
  def upheap(self, j): # 힙 올라가며 힙속성 회복
    while j > 1 and self.a[j//2][0] > self.a[j][0]: # 현재노드가 루트가 아니고 동시에 부모가 크면
      self.a[j], self.a[j//2] = self.a[j//2], self.a[j]  # 부모와 현재 노드 교환
      j = j//2  # 부모가 현재 노드가 되어 다시 반복하기 위해
       
  def print_heap(self): # 힙 출력
    for i in range(1, self.N+1): 
      print('[%2d' % self.a[i][0], self.a[i][1], ']', end='')
    print('\n힙 크기 = ', self.N)

이진힙 적용

In [4]:
#from binary_heap import BHeap
if __name__ == '__main__':
  a = [None] * 1
  a.append([90, 'watermelon'])
  a.append([80, 'pear'])
  a.append([70, 'melon'])
  a.append([50, 'lime'])
  a.append([60, 'mango'])
  a.append([20, 'cherry'])
  a.append([30, 'grape'])
  a.append([35, 'orange'])
  a.append([10, 'apricot'])
  a.append([15, 'banana'])
  a.append([45, 'lemon'])
  a.append([40, 'kiwi'])
  b = BHeap(a)
  print('힙 만들기 전:')
  b.print_heap()
  b.create_heap() # 힙 만들기
  print('최소힙:')
  b.print_heap()
  print('최솟값 삭제 후')
  print(b.delete_min())
  b.print_heap()
  b.insert([5,'apple'])
  print('5 삽입 후')
  b.print_heap()
힙 만들기 전:
[90 watermelon ][80 pear ][70 melon ][50 lime ][60 mango ][20 cherry ][30 grape ][35 orange ][10 apricot ][15 banana ][45 lemon ][40 kiwi ]
힙 크기 =  12
최소힙:
[10 apricot ][15 banana ][20 cherry ][35 orange ][45 lemon ][40 kiwi ][30 grape ][80 pear ][50 lime ][60 mango ][90 watermelon ][70 melon ]
힙 크기 =  12
최솟값 삭제 후
[10, 'apricot']
[15 banana ][35 orange ][20 cherry ][50 lime ][45 lemon ][40 kiwi ][30 grape ][80 pear ][70 melon ][60 mango ][90 watermelon ]
힙 크기 =  11
5 삽입 후
[ 5 apple ][35 orange ][15 banana ][50 lime ][45 lemon ][20 cherry ][30 grape ][80 pear ][70 melon ][60 mango ][90 watermelon ][40 kiwi ]
힙 크기 =  12