저번 글에 이어서 트리 구조로 DFS, BFS를 구현해보도록 하겠습니다.
class Node:
def __init__(self, data):
self.data = data
# self.child = [] 2진 트리가 아닌 경우 child를 사용할 수 있습니다.
self.left = None
self.right = None
class Tree:
def __init__(self, data):
init = Node(data)
self.root = init
self.count = 0
def __len__(self):
return self.count
def insert(self, data):
new_node = Node(data)
current_node = self.root
while current_node:
if data == current_node.data:
return
elif data < current_node.data:
if not current_node.left:
current_node.left = new_node
self.count += 1
return
current_node = current_node.left
elif data > current_node.data:
if not current_node.right:
current_node.right = new_node
self.count += 1
return
current_node = current_node.right

그림에서 알 수 있듯이, 한 방향으로 가면서 막힐 때까지 검사한다. 막히면 포기하고 마지막에 따라온 간선으로 되돌아 간다. 왼쪽부터 시작할 수도 있고, 오른쪽부터 시작할 수도 있다.
모든 노드를 방문하고자 하는 경우에 선택하는 알고리즘이다.
def DFS(self):
# 깊이우선탐색, DFS(Depth First Search)
# Stack 이용!
result = []
stack = [self.root]
while stack:
current = stack.pop()
if current.left:
stack.append(current.left)
if current.right:
stack.append(current.right)
result.append(current.data)
return result

가까운 정점을 먼저 방문하고, 먼 정점은 나중에 방문하는 알고리즘이다. 마찬가지로 오른쪽부터 시작하거나 왼쪽부터 시작할 수 있다.
# 깊스너큐(깊이 우선 탐색은 스택, 너비 우선 탐색은 큐)
def BFS(self):
# 너비우선탐색, BFS(Breadth First Search)
# Queue 이용
result = []
queue = [self.root]
while queue:
current = queue.pop(0)
if current.right:
queue.append(current.right)
if current.left:
queue.append(current.left)
result.append(current.data)
return result
BFS는 현재 노드를 맨 앞에서 꺼내고, DFS는 맨 뒤에서 꺼내는 것이 차이점이다.
tree = Tree(5)
tree.insert(3)
tree.insert(8)
tree.insert(1)
tree.insert(4)
tree.insert(6)
tree.insert(9)
tree.BFS() # [5, 8, 3, 9, 6, 4, 1]
tree.DFS() # [5, 8, 9, 6, 3, 4, 1]
스택에는 나중에 방문할 노드를 먼저 넣기에 왼쪽 먼저 넣었으니 오른쪽부터 탐색하는 그림이 그려진다.
큐는 오른쪽부터 넣으면 그대로 오른쪽부터 탐색한다.

여러 정렬이 있지만 현재는 Timsort로 굳어지고 있다.
예전보다 중요도는 떨어졌지만 알아두면 쓰인다고 한다.
def 최솟값_인덱스_리턴_함수 (리스트):
최솟값인덱스 = 0
for 증가값 in range(0, len(리스트)):
if 리스트[증가값] < 리스트[최솟값인덱스]:
최솟값인덱스 = 증가값
return 최솟값인덱스
def 선택정렬(리스트):
최종결과값 = []
while 리스트:
최솟값_인덱스_저장 = 최솟값_인덱스_리턴_함수(리스트)
최솟값 = 리스트.pop(최솟값_인덱스_저장)
최종결과값.append(최솟값)
return 최종결과값
주어진리스트 = [199, 22, 33, 12, 32, 64, 72, 222, 233]
print(선택정렬(주어진리스트)) # [12, 22, 32, 33, 64, 72, 199, 222, 233]
전 = [199, 22, 33, 12, 32, 64, 72, 222, 233]
후 = []
전 = [22, 33, 12, 32, 64, 72, 222, 233]
후 = [199]
전 = [33, 12, 32, 64, 72, 222, 233]
후 = [22, 199]
전 = [12, 32, 64, 72, 222, 233]
후 = [22, 33, 199]
전 = [32, 64, 72, 222, 233]
후 = [12, 22, 33, 199]
전 = [64, 72, 222, 233]
후 = [12, 22, 32, 33, 199]
전 = [72, 222, 233]
후 = [12, 22, 32, 33, 64, 199]
def 삽입값이들어갈_인덱스 (결과값, 삽입값):
for 증가값 in range(0, len(결과값)):
if 삽입값 < 결과값[증가값]:
return 증가값
return len(결과값)
def 삽입정렬(입력_리스트_하나):
결과값 = []
while 입력_리스트_하나:
삽입값 = 입력_리스트_하나.pop(0)
삽입값_인덱스 = 삽입값이들어갈_인덱스(결과값, 삽입값)
결과값.insert(삽입값_인덱스, 삽입값)
return 결과값
주어진리스트 = [199, 22, 33, 12, 32, 64, 72, 222, 233]
print(삽입정렬(주어진리스트))
입력값 = [5, 10, 66, 77, 54, 32, 11, 15]
정렬된배열 = []
# 분할(이해를 돕기 위해 8개로 조정)
[5, 10, 66, 77, 54, 32, 11, 15]
[5, 10, 66, 77], [54, 32, 11, 15]
[5, 10], [66, 77], [54, 32], [11, 15]
[5], [10], [66], [77], [54], [32], [11], [15]
# 정복(0번째끼리 비교!)
[5, 10], [66, 77], [32, 54], [11, 15]
[5, 10], [66, 77], [32, 54], [11, 15]
[5, 10, 66, 77], [11, 15, 32, 54]
[5, 10, 11, 15, 32, 54, 66, 77]
def 병합정렬(입력리스트):
# print(입력리스트)
입력리스트길이 = len(입력리스트)
if 입력리스트길이 <= 1:
return 입력리스트
중간값 = 입력리스트길이 // 2
그룹_하나 = 병합정렬(입력리스트[:중간값])
그룹_둘 = 병합정렬(입력리스트[중간값:])
결과값 = []
while 그룹_하나 and 그룹_둘:
if 그룹_하나[0] < 그룹_둘[0]:
결과값.append(그룹_하나.pop(0))
else:
결과값.append(그룹_둘.pop(0))
if 그룹_하나:
결과값.extend(그룹_하나)
그룹_하나.clear()
if 그룹_둘:
결과값.extend(그룹_둘)
그룹_둘.clear()
return 결과값
주어진리스트 = [199, 22, 33, 12, 32, 64, 72, 222, 233]
print(병합정렬(주어진리스트))
오래된 원소가 가장 빨리 나간다.
# 순서 : 0, 4, 6, 5, 4, 7, 8
[]
[0]
[0, 4]
[0, 4, 6]
[4, 6, 5]
[4, 6, 5] # 4가 hit
[6, 5, 7]
[5, 7, 8]
오랫동안 사용되지 않은 원소가 삭제된다.
# 순서 : 0, 4, 6, 5, 4, 7, 8
[]
[0]
[0, 4]
[0, 4, 6]
[4, 6, 5]
[6, 5, 4] # 4가 hit
[5, 4, 7]
[4, 7, 8]
실행 시간을 구하는 문제에 주로 사용된다.
["바나나", "체리", "한라봉", "자몽", "수박", "수박", "체리"]
[바나나] # 5
[바나나, 체리] # 5
[바나나, 체리, 한라봉] # 5
[체리, 한라봉, 자몽] # 5
[한라봉, 자몽, 수박] # 5
[한라봉, 자몽, 수박] # 1 - hit
[자몽, 수박, 체리] # 5
큐의 길이가 제한되어 있으며 최대 길이를 넘으면 일반적으로 오래된 원소가 삭제된다.
만약, 현재 큐에 있는 원소가 추가된다고 하면 해당 원소는 꺼내진 후 맨 뒤에 append 된다. 이 과정을 hit 이라고 한다. 위 예제에서는 1초이다.
연속된 2개의 합이 10인 모든 수를 찾는 것과 같은 문제의 알고리즘이다. 구간의 넓이는 고정되어 있다.
def find_pairs_sum(nums, target_sum):
result = []
window_sum = nums[0] + nums[1]
left = 0
right = 1
while right < len(nums):
if window_sum == target_sum:
result.append((nums[left], nums[right]))
window_sum -= nums[left]
left += 1
window_sum += nums[right + 1]
right += 1
elif window_sum < target_sum:
right += 1
if right < len(nums):
window_sum += nums[right]
else:
window_sum -= nums[left]
left += 1
return result
nums = [1, 5, 4, 6, 4, 6, 7, 6, 4, 3, 1, 2]
target_sum = 10
print(find_pairs_sum(nums, target_sum))
연속된 배열의 합이 10인 배열의 인덱스를 모두 찾아라..와 같은 문제이다. 구간의 넓이가 조건에 따라 유동적으로 변하는 알고리즘이다.
def solution(data, require_value):
start_pointer = 0
end_pointer = 0
temp_sum = 0
data_len = len(data)
result = []
for start_pointer in range(data_len):
while temp_sum < require_value and end_pointer < data_len:
temp_sum += data[end_pointer]
end_pointer += 1
if temp_sum == require_value:
result.append([start_pointer, end_pointer-1])
temp_sum -= data[start_pointer]
return result
solution([1, 5, 4, 6, 4, 6, 7, 6, 4, 3, 1, 2], 10)
# [[0, 2], [2, 3], [3, 4], [4, 5], [7, 8], [8, 11]]
위니브 이호준 강사님의 ppt자료를 받아 작성하였습니다.