
자료구조/알고리즘 풀기(3)

자료를 보관할 수 있는 선형 구조
데이터를 넣을 때 한 쪽 끝에서 밀어넣고(enqueue)
꺼낼 때는 반대 쪽에서 깨낸다.(dequeue)
-> 먼저 들어 온 것이 먼저 나온다.(First In First Out)
size() : 큐에 들어있는 원소의 수
isEmpty() : 큐가 비어있는지 확인
enqueue(x) : x를 큐에 추가
dequeue() : 큐 가장 앞에 저장된 원소를 제거 및 반환
peek() : 큐 가장 앞에 저장된 원소를 반환(제거 x)
dequeue() 연산을 제외한 나머지 연산은 O(1)의 시간복잡도를 가진다.
(가장 앞의 원소를 꺼내고 뒤의 원소들이 한칸씩 당겨지기 때문에 dequeue는 O(n)의 시간복잡도를 가진다.)
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.prev = None
self.next = None
class DoublyLinkedList:
def __init__(self):
self.nodeCount = 0
self.head = Node(None)
self.tail = Node(None)
self.head.prev = None
self.head.next = self.tail
self.tail.prev = self.head
self.tail.next = None
def __repr__(self):
if self.nodeCount == 0:
return 'LinkedList: empty'
s = ''
curr = self.head
while curr.next.next:
curr = curr.next
s += repr(curr.data)
if curr.next.next is not None:
s += ' -> '
return s
def getLength(self):
return self.nodeCount
def traverse(self):
result = []
curr = self.head
while curr.next.next:
curr = curr.next
result.append(curr.data)
return result
def reverse(self):
result = []
curr = self.tail
while curr.prev.prev:
curr = curr.prev
result.append(curr.data)
return result
def getAt(self, pos):
if pos < 0 or pos > self.nodeCount:
return None
if pos > self.nodeCount // 2:
i = 0
curr = self.tail
while i < self.nodeCount - pos + 1:
curr = curr.prev
i += 1
else:
i = 0
curr = self.head
while i < pos:
curr = curr.next
i += 1
return curr
def insertAfter(self, prev, newNode):
next = prev.next
newNode.prev = prev
newNode.next = next
prev.next = newNode
next.prev = newNode
self.nodeCount += 1
return True
def insertAt(self, pos, newNode):
if pos < 1 or pos > self.nodeCount + 1:
return False
prev = self.getAt(pos - 1)
return self.insertAfter(prev, newNode)
def popAfter(self, prev):
curr = prev.next
next = curr.next
prev.next = next
next.prev = prev
self.nodeCount -= 1
return curr.data
def popAt(self, pos):
if pos < 1 or pos > self.nodeCount:
raise IndexError('Index out of range')
prev = self.getAt(pos - 1)
return self.popAfter(prev)
def concat(self, L):
self.tail.prev.next = L.head.next
L.head.next.prev = self.tail.prev
self.tail = L.tail
self.nodeCount += L.nodeCount
class LinkedListQueue:
def __init__(self):
self.data = DoublyLinkedList()
def size(self):
return self.data.nodeCount
def isEmpty(self):
return self.data.nodeCount == 0
def enqueue(self, item):
node = Node(item)
self.data.insertAt(self.size() + 1, node)
def dequeue(self):
return self.data.popAt(1)
def peek(self):
return self.data.head.next.data

정해진 개수의 저장공간을 돌려가며 이용하는 큐의 형태
-> 큐가 가득차면 더 이상 원소를 넣을 수 없음
rear : 데이터를 집어넣는 포인터
front : 데이터를 꺼내는 포인터
size() : 큐에 들어있는 원소의 수
isEmpty() : 큐가 비어있는지 확인
isFull() : 큐가 가득 찼는지 확인
enqueue(x) : x를 큐에 추가
dequeue() : 큐 가장 앞에 저장된 원소를 제거 및 반환
peek() : 큐 가장 앞에 저장된 원소를 반환(제거 x)
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]
큐가 FIFO의 방식을 따르지 않고 우선순위에 따라 원소를 꺼냄
우선순위 큐를 구현하는 방식에는 두 가지가 있다.
class Node:
def __init__(self, item):
self.data = item
self.prev = None
self.next = None
class DoublyLinkedList:
def __init__(self):
self.nodeCount = 0
self.head = Node(None)
self.tail = Node(None)
self.head.prev = None
self.head.next = self.tail
self.tail.prev = self.head
self.tail.next = None
def __repr__(self):
if self.nodeCount == 0:
return 'LinkedList: empty'
s = ''
curr = self.head
while curr.next.next:
curr = curr.next
s += repr(curr.data)
if curr.next.next is not None:
s += ' -> '
return s
def getLength(self):
return self.nodeCount
def traverse(self):
result = []
curr = self.head
while curr.next.next:
curr = curr.next
result.append(curr.data)
return result
def reverse(self):
result = []
curr = self.tail
while curr.prev.prev:
curr = curr.prev
result.append(curr.data)
return result
def getAt(self, pos):
if pos < 0 or pos > self.nodeCount:
return None
if pos > self.nodeCount // 2:
i = 0
curr = self.tail
while i < self.nodeCount - pos + 1:
curr = curr.prev
i += 1
else:
i = 0
curr = self.head
while i < pos:
curr = curr.next
i += 1
return curr
def insertAfter(self, prev, newNode):
next = prev.next
newNode.prev = prev
newNode.next = next
prev.next = newNode
next.prev = newNode
self.nodeCount += 1
return True
def insertAt(self, pos, newNode):
if pos < 1 or pos > self.nodeCount + 1:
return False
prev = self.getAt(pos - 1)
return self.insertAfter(prev, newNode)
def popAfter(self, prev):
curr = prev.next
next = curr.next
prev.next = next
next.prev = prev
self.nodeCount -= 1
return curr.data
def popAt(self, pos):
if pos < 1 or pos > self.nodeCount:
return None
prev = self.getAt(pos - 1)
return self.popAfter(prev)
def concat(self, L):
self.tail.prev.next = L.head.next
L.head.next.prev = self.tail.prev
self.tail = L.tail
self.nodeCount += L.nodeCount
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.next != None 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

노드(Node)와 엣지(Edge)를 이용해 데이터의 배치 형태를 추상화한 자료구조
모든 노드의 차수가 2 이하인 트리구조
포화 이진 트리(Full Binary Tree)
모든 수준에서 노드들이 모두 채워져있는 이진 트리의 구조(깊이 n일 떄 노드의 개수가 2ⁿ- 1)
완전 이진 트리(Complete Binary Tree)
깊이 k인 완전 이진 트리에 대해 k-2수준까지는 포화 이진 트리, k-1수준에서는 왼쪽에서부터 순차적으로 채워진 구조
size() : 노드의 갯수
전체 이진 트리의 size = 왼쪽 서브트리의 size + 오른쪽 서브트리의 size + 1
depth() : 트리의 깊이
전체 이진 트리의 depth = max(왼쪽 서브트리의 depth, 오른쪽 서브트리의 depth) + 1
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
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
- 중위 순회(In-order Traverse)
순회 순서 : 왼쪽 서브트리 -> 자기자신 -> 오른쪽 서브트리
- 전위 순회(Pre-order Traverse)
순회 순서 : 자기자신 -> 왼쪽 서브트리 -> 오른쪽 서브트리
- 후위 순회(Post order Traverse)
순회 순서 : 왼쪽 서브트리 -> 오른쪽 서브트리 -> 자기자신
수준이 높은 노드를 우선으로 탐색
한 노드를 탐색했을 때 나중에 방문할 노드들의 순서도 기록해야 한다.
-> 큐 사용
class Node:
def __init__(self, item):
self.data = item
self.left = None
self.right = None
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 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 []
모든 노드에 대해 왼쪽 서브 트리는 현재 노드보다 작고 오른쪽 서브 트리는 현재 노드보다 큰 구조를 가지고 있다.
이진탐색법에 비해 데이터 원소의 추가, 삭제가 용이하나 공간 소모가 큼
insert(key, data) : 트리에 주어진 데이터 원소 추가
remove(key) : 트리에서 특정 원소 삭제
lookup(key) : 특정 원소 검색
inorder() : 키의 순서에 떄라 데이터 원소 나열
min(),max() : 각각 최소, 최대키를 가지는 원소 탐색
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:
self.left.insert(key, data)
else:
self.left = Node(key, data)
elif key > self.key:
if self.right:
self.right.insert(key, data)
else:
self.right = Node(key, data)
else:
raise KeyError('ValueError')
return True
def inorder(self):
traversal = []
if self.left:
traversal += self.left.inorder()
traversal.append(self)
if self.right:
traversal += self.right.inorder()
return traversal
def countChildren(self):
count = 0
if self.left:
count += 1
if self.right:
count += 1
return count
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 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 node == parent.left:
parent.left = None
if node == parent.right:
parent.right = None
# 만약 parent 가 없으면 (node 는 root 인 경우)
# self.root 를 None 으로 하여 빈 트리로 만듭니다.
else:
self.root = None
# When the node has only one child
elif nChildren == 1:
# 하나 있는 자식이 왼쪽인지 오른쪽인지를 판단하여
# 그 자식을 어떤 변수가 가리키도록 합니다.
if node.left:
direction = node.left
else:
direction = node.right
# 만약 parent 가 있으면
# node 가 왼쪽 자식인지 오른쪽 자식인지 판단하여
# 위에서 가리킨 자식을 대신 node 의 자리에 넣습니다.
if parent:
if node == parent.left:
parent.left = direction
else:
parent.right = direction
# 만약 parent 가 없으면 (node 는 root 인 경우)
# self.root 에 위에서 가리킨 자식을 대신 넣습니다.
else:
self.root = direction
# When the node has both left and right children
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 []
다음 조건을 만족시키는 이진트리
- 언제나 부모가 자식보다 크거나 같다.(루트가 언제나 최대(MaxHeap) 혹은 최소(MinHeap))
- 완전 이진트리