수식의 후위 표기법
(A + B) * (C + D)
AB + CD + *
[중위] A * (B + C)
[후위] ABC + *
# 연산자의 우선순위
prec = {
'*': 3, '/': 3,
'+': 2, '-': 2,
'(': 1
}
def solution(S):
opStack = ArrayStack()
answer = ''
for x in S:
# 피연산자일 경우
if x.isalpha():
answer += x
else:
# 여는 괄호일 경우
if x == '(':
opStack.push(x)
# 닫는 괄호일 경우
elif x == ')':
while not opStack.isEmpty():
curr = opStack.pop()
if curr == '(':
break
answer += curr
# 연산자일 경우
else:
if opStack.isEmpty():
opStack.push(x)
continue
while not opStack.isEmpty():
prev = opStack.peek()
# 스택에 있는 연산자의 우선순위가 높을 경우 pop
if prec[prev] >= prec[x]:
answer += opStack.pop()
else:
break
opStack.push(x)
# print(opStack.size())
while not opStack.isEmpty():
answer += opStack.pop()
return answer
# 후위 표기법 순서로 연산자 및 피연산자가 담긴 tokenList를 인자로 갖는 함수
def postfixEval(tokenList):
opStack = ArrayStack()
for token in tokenList:
if type(token) is int:
opStack.push(token)
else:
num2 = opStack.pop()
num1 = opStack.pop()
if token == '*':
opStack.push(num1 * num2)
elif token == '/':
opStack.push(num1 / num2)
elif token == '-':
opStack.push(num1 - num2)
elif token == '+':
opStack.push(num1 + num2)
return opStack.pop()
큐(Queues)
자료를 넣을 때는 한쪽 끝에서 밀어 넣어야 하고 꺼낼 때에는 반대쪽에서 뽑아 꺼내야 하는 제약이 있는 선형 자료 구조. 선입선출(FIFO: first-in-first-out)
큐의 연산
- .enqueue(x)
: 원소 x를 큐에 넣는 동작
- .dequeue()
: 큐의 맨 앞에 저장된 원소를 제거(또한, 반환)
큐의 추상적 자료 구조 구현
- 배열 이용
dequeue() 연산의 시간 복잡도 O(N)가 배열의 길이에 비례 하게 된다.
- 이중 연결 리스트 이용
class LinkedListQueue:
def __init__(self):
self.data = DoublyLinkedList()
def size(self):
return self.data.getLength()
def isEmpty(self):
return self.data.getLength()==0
def enqueue(self,item):
node = Node(item)
self.data.insertAt(self.data.nodeCount+1, node)
def dequeue(self):
return self.data.popAt(1)
def peek(self):
return self.data.getAt(1).data
환형 큐(Circular Queue)
정해진 개수의 저장 공간을 빙 돌려가며 이용한다.
isFull()
연산을 구현해 큐에 데이터 원소가 꽉 차 있는지를 판단하자.class CircularQueue:
def __init__(self, n):
self.maxCount = n
self.data = [None]*n
self.count = 0
self.front = -1
self.rear = -1
def size(self):
return self.count
def isEmpty(self):
return self.count == 0
def isFull(self):
return self.count == self.maxCount
def enqueue(self, x):
if self.isFull():
raise IndexError('Queue full')
self.rear = (self.rear + 1) % self.maxCount
self.data[self.rear] = x
self.count += 1
def dequeue(self):
if self.isEmpty()
raise IndexError('Queue empty')
self.front = (self.front + 1) % self.maxCount
x = self.data[self.front]
self.count -= 1
return x
def peek(self):
if self.isEmpty():
raise IndexError('Queue empty')
return self.data[(self.front + 1) % self.maxCount]
def solution(x):
return 0
우선순위 큐(Priority Queue)
큐가 FIFO 방식을 따르지 않고 원소들의 우선순위에 따라 큐에서 빠져나오는 방식을 갖는 자료구조.
class PriorityQueue:
def __init__(self):
self.queue = DoublyLinkedList()
def size(self):
return self.queue.getLength()
def isEmpty(self):
return self.size() == 0
def enqueue(self, x):
newNode = Node(x)
curr = self.queue.head
while curr.next != self.queue.tail and x < curr.next.data:
curr = curr.next
self.queue.insertAfter(curr, newNode)
def dequeue(self):
return self.queue.popAt(self.queue.getLength())
def peek(self):
return self.queue.getAt(self.queue.getLength()).data
트리(Trees)
node와 edge를 이용하여 데이터의 배치 형태를 추상화한 자료구조. 나무를 거꾸로 한 모습의 2차원 구조이다.
주요 용어
노드 (nodes)
간선 (edges)
루트 노드 (root node)
리프 노드 (leaf nodes): 차수가 0인 노드
내부 노드 (internal nodes)
부모 (parent) 노드와 자식 (child) 노드
노드의 수준 (level): root node는 level 0
노드의 차수 (degree): 특정 노드의 입장에서 자식(서브트리)의 수 = 자식으로 연결되는 간선의 개수
트리의 높이 (height) 또는, 깊이 (depth): 최대 수준(level) + 1
부분 트리 (서브트리; subtrees)
포화 이진 트리 (full binary trees): 모든 레벨에서 노드들이 모두 채워져 있는 이진 트리
- 높이가 k일 때 노드의 개수는 2^k - 1
완전 이진 트리 (complete binary trees)
- 높이 k일 때 레벨 k - 2까지는 모든 노드가 2개 자식을 가진 포화 이진 트리
- 레벨 k - 1에서는 왼쪽부터 노드가 순차적으로 채워져 있는 이진 트리
이진 트리(Binary Tree)
트리에 포함되는 모든 노드의 차수가 2 이하인 트리
재귀적 정의
- 루트 노드 + 왼쪽 서브트리 + 오른쪽 서브트리 (단, 이 때 왼쪽과 오른쪽 서브트리 또한 이진 트리)
- 빈 트리도 이진트리로 정의한다. (재귀 호출의 종료조건)
구현 - size(): 트리의 노드 개수
- 전체 이진 트리의 size = left subtree의 size + right subtree의 size + 1(자기 자신)
구현 - depth()
- 전체 이진 트리의 depth = left subtree의 depth와 right subtree의 depth 중 더 큰 것 + 1
깊이 우선 순회(deapth first traversal)
class Node:
def __init__(self, item):
self.data = item
self.left = None
self.right = None
def size(self):
l = self.left.size() if self.left else 0
r = self.right.size() if self.right else 0
return l + r + 1
def depth(self):
l = self.left.depth() if self.left else 0
r = self.right.depth() if self.right else 0
return max(l, r) + 1
# 중위 순회
def inorder(self):
traversal = []
if self.left:
traversal += self.left.inorder()
traversal.append(self.data)
if self.right:
traversal += self.right.inorder()
return traversal
# 전위 순회
def preorder(self):
traversal = []
traversal.append(self.data)
if self.left:
traversal += self.left.preorder()
if self.right:
traversal += self.right.preorder()
return traversal
# 후위 순회
def postorder(self):
traversal = []
if self.left:
traversal += self.left.postorder()
if self.right:
traversal += self.right.postorder()
traversal.append(self.data)
return traversal
class BinaryTree:
def __init__(self, r):
self.root = r
def size(self):
if self.root:
return self.root.size()
else:
return 0
def depth(self):
if self.root:
return self.root.depth()
else:
return 0
def inorder(self):
if self.root:
return self.root.inorder()
else:
return []
def preorder(self):
if self.root:
return self.root.preorder()
else:
return []
def postorder(self):
if self.root:
return self.root.postorder()
else:
return []
class ArrayQueue:
def __init__(self):
self.data = []
def size(self):
return len(self.data)
def isEmpty(self):
return self.size() == 0
def enqueue(self, item):
self.data.append(item)
def dequeue(self):
return self.data.pop(0)
def peek(self):
return self.data[0]
class Node:
def __init__(self, item):
self.data = item
self.left = None
self.right = None
class BinaryTree:
def __init__(self, r):
self.root = r
def bft(self):
traversal = []
q = ArrayQueue()
if self.root:
q.enqueue(self.root)
while not q.isEmpty():
node = q.dequeue()
traversal.append(node.data)
if node.left:
q.enqueue(node.left)
if node.right:
q.enqueue(node.right)
return traversal
def solution(x):
return 0
이진 탐색 트리(Binary Search Tree)
모든 노드에 대해서 왼쪽 서브트리에 있는 데이터는 모두 현재 노드의 값보다 작고 오른쪽 서브트리에 있는 데이터는 모두 현재 노드의 값보다 큰 성질을 만족하는 이진 트리
class Node:
def __init__(self, key, data):
self.key = key
self.data = data
self.left = None
self.right = None
def insert(self, key, data):
# 왼쪽에 들어가야 하는 경우
if key < self.key:
if self.left:
return self.left.insert(key, data)
else:
self.left = Node(key, data)
# 오른쪽에 들어가야 하는 경우
elif key > self.key:
if self.right:
return self.right.insert(key, data)
else:
self.right = Node(key, data)
else:
raise KeyError('중복된 노드 존재')
def lookup(self, key, parent=None):
# 지금 방문된 노드(self.key)보다 키가 작으면 왼쪽으로
if key < self.key:
if self.left:
return self.left.lookup(key, self)
else:
return None, None
# 지금 방문된 노드보다 키가 크면 오른쪽으로
elif key > self.key:
if self.right:
return self.right.lookup(key, self)
else:
return None, None
else:
return self, parent
def inorder(self):
traversal = []
if self.left:
traversal += self.left.inorder()
traversal.append(self)
if self.right:
traversal += self.right.inorder()
return traversal
def min(self):
if self.left:
return self.left.min()
else:
return self
def max(self):
if self.right:
return self.right.max()
else:
return self
class BinSearchTree:
def __init__(self):
self.root = None
def insert(self, key, data):
if self.root:
self.root.insert(key, data)
else:
self.root = Node(key, data)
def lookup(self, key):
if self.root:
return self.root.lookup(key)
else:
return None, None
def inorder(self):
if self.root:
return self.root.inorder()
else:
return []
def min(self):
if self.root:
return self.root.min()
else:
return None
def max(self):
if self.root:
return self.root.max()
else:
return None
def solution(x):
return 0
class BinSearchTree:
def __init__(self):
self.root = None
def insert(self, key, data):
if self.root:
self.root.insert(key, data)
else:
self.root = Node(key, data)
def lookup(self, key):
if self.root:
return self.root.lookup(key)
else:
return None, None
def remove(self, key):
node, parent = self.lookup(key)
if node:
nChildren = node.countChildren()
# 자식이 없는 노드를 삭제하려고 할 때
if nChildren == 0:
# 만약 parent 가 있으면
# node 가 왼쪽 자식인지 오른쪽 자식인지 판단하여
# parent.left 또는 parent.right 를 None 으로 하여
# leaf node 였던 자식을 트리에서 끊어내어 없앱니다.
if parent:
if parent.left == node:
parent.left = None
if parent.right == node:
parent.right = None
# 만약 parent 가 없으면 (node 는 root 인 경우)
# self.root 를 None 으로 하여 빈 트리로 만듭니다.
else:
self.root = None
# 자식이 하나 있는 노드를 삭제하려고 할 때
elif nChildren == 1:
# 하나 있는 자식이 왼쪽인지 오른쪽인지를 판단하여
# 그 자식을 어떤 변수가 가리키도록 합니다.
if node.left:
newnode = node.left
else:
newnode = node.right
# 만약 parent 가 있으면
# node 가 왼쪽 자식인지 오른쪽 자식인지 판단하여
# 위에서 가리킨 자식을 대신 node 의 자리에 넣습니다.
if parent:
if parent.left == node:
parent.left = newnode
else:
parent.right = newnode
# 만약 parent 가 없으면 (node 는 root 인 경우)
# self.root 에 위에서 가리킨 자식을 대신 넣습니다.
else:
self.root = newnode
# 자식이 둘 있는 노드를 삭제하려고 할 때
else:
parent = node
successor = node.right
# parent 는 node 를 가리키고 있고,
# successor 는 node 의 오른쪽 자식을 가리키고 있으므로
# successor 로부터 왼쪽 자식의 링크를 반복하여 따라감으로써
# 순환문이 종료할 때 successor 는 바로 다음 키를 가진 노드를,
# 그리고 parent 는 그 노드의 부모 노드를 가리키도록 찾아냅니다.
while successor.left:
parent = successor
successor = successor.left
# 삭제하려는 노드인 node 에 successor 의 key 와 data 를 대입합니다.
node.key = successor.key
node.data = successor.data
# 이제, successor 가 parent 의 왼쪽 자식인지 오른쪽 자식인지를 판단하여
# 그에 따라 parent.left 또는 parent.right 를
# successor 가 가지고 있던 (없을 수도 있지만) 자식을 가리키도록 합니다.
if successor.key is parent.left.key:
parent.left = successor.right
else:
parent.right = successor.right
return True
else:
return False
def inorder(self):
if self.root:
return self.root.inorder()
else:
return []
힙(Heaps)
루트 노드가 언제나 최댓값 또는 최솟값을 가지며 완전 이진 트리이다. 힙 내의 임의의 노드를 루트로 하는 서브트리 또한 힙이다.
이진 탐색 트리 | 힙 | |
---|---|---|
원소들이 완전히 크기 순으로 정렬되어 있는가? | O | X |
특정 키 값을 가지는 원소를 빠르게 검색할 수 있는가? | O | X |
부가의 제약 조건은 어떤 것인가? | 완전 이진 트리 |
최대 힙의 장점
- 완전 이진 트리여야 한다는 제약 때문에, n개 노드로 이루어진 최대 힙의 높이는 logN + 1
- 데이터 원소의 삽입/삭제 연산의 실행 시간은 언제나 logN에 비례
- 최대 힙으로부터 반복적으로 루트 노드를 꺼내면 logN 시간에 비례하여 데이터를 내림차순으로 정렬할 수 있음
최대 힙의 추상적 자료구조
- 연산의 정의
배열을 이용한 이진 트리의 표현
class MaxHeap:
def __init__(self):
self.data = [None]
def insert(self, item):
self.data.append(item)
m = len(self.data) - 1
while m > 1:
# 부모 노드가 더 작을 경우
if self.data[m // 2] < self.data[m]:
self.data[m // 2], self.data[m] = self.data[m], self.data[m // 2]
m = m // 2
else:
break
class MaxHeap:
def __init__(self):
self.data = [None]
def remove(self):
if len(self.data) > 1:
self.data[1], self.data[-1] = self.data[-1], self.data[1]
data = self.data.pop(-1)
self.maxHeapify(1)
else:
data = None
return data
def maxHeapify(self, i):
# 왼쪽 자식 (left child) 의 인덱스를 계산합니다.
left = 2 * i
# 오른쪽 자식 (right child) 의 인덱스를 계산합니다.
right = 2 * i + 1
smallest = i
# 왼쪽 자식이 존재하는지, 그리고 왼쪽 자식의 (키) 값이 (무엇보다?) 더 큰지를 판단합니다.
if left < len(self.data) and self.data[left] > self.data[smallest]:
# 조건이 만족하는 경우, smallest 는 왼쪽 자식의 인덱스를 가집니다.
smallest = left
# 오른쪽 자식이 존재하는지, 그리고 오른쪽 자식의 (키) 값이 (무엇보다?) 더 큰지를 판단합니다.
if right < len(self.data) and self.data[right] > self.data[smallest]:
# 조건이 만족하는 경우, smallest 는 오른쪽 자식의 인덱스를 가집니다.
smallest = right
if smallest != i:
# 현재 노드 (인덱스 i) 와 최댓값 노드 (왼쪽 아니면 오른쪽 자식) 를 교체합니다.
self.data[i], self.data[smallest] = self.data[smallest], self.data[i]
# 재귀적 호출을 이용하여 최대 힙의 성질을 만족할 때까지 트리를 정리합니다.
self.maxHeapify(smallest)