🖍🖍 알고리즘 백준 문제 마라톤에 문제 주제에 맞춰서 정리해본거라 중구난방일 수 있지만.. 최대한 내가 이해한대로! 알고리즘 튜터님(스파르타 코딩클럽 박현준 튜터님😆)이 집어주신 전체적인 자료구조의 숲을 보는 훈련을 하면서 정리하는게 목표입니다!
옆에 거랑 비교하면서 큰 수가 마지막으로 가게끔 자리를 계속 바꿔 줍니다!
시간 복잡도는 for문 안에 하나의 for문이 더 있으므로 O(N^2)
버블 정렬 code
1) 일단 비교를 몇 번 해야 할지
생각 해보고
2) 맨 마지막 까지 비교가 끝나면, 맨 마지막 숫자는 제대로 정렬 된 거니깐 그 다음 비교는 그걸 제외하고 하면 됩니다!
즉, 배열의 길이가 5라면 처음엔 array[4] 까지 비교하고 그 과정이 끝나면 array[4]는 정렬이 완료된 것이고 그 다음은 array[3]까지만 비교하면 되는 방식 입니다!
input = [4, 6, 2, 9, 1]
def bubble_sort(array):
n = len(array)
for i in range(n - 1):
for j in range(n - i - 1):
if array[j] > array[j + 1]:
array[j], array[j + 1] = array[j + 1],array[j]
return array
처음부터 마지막까지 비교해보고 제일 작은 수를 맨 앞으로 자리를 바꿔줍니다!
똑같이 2차 반복문이 나오고, array의 길이 만큼 반복하므로 O(N^2)
선택 정렬 code
1) 일단 이렇게 비교하는 행위를 총 몇번해야하는 지
생각 합니다. 예를들어 list에 수가 5개 있으면, 처음에는 4번
(4와6, 4와2, 2와9, 9와1), 이렇게 비교해서 제일 작은 수를 맨앞으로 보내면, 맨 앞은 이미 정렬이 완료
된 것입니다! 그러면 맨 처음을 제외한 나머지 숫자 4개는 3번
비교, 그 다음은 2번
, 마지막 1번
이렇게 총 4번을 합니다! => 그러므로 큰 반복문은 n - 1
2) 비교 할 때마다 맨 앞자리가 갱신
되므로 (맨 처음에는 array[0], 그다음은 array[1]...) => min_index = i
3) 1)에서 설명했듯이 실질적으로 비교하는 횟수는 맨 처음에 4번, 그다음 3번 이런식이므로 i가 1씩 커질 때마다 비교해야하는 범위가 줄어 듭니다
. => 그래서 작은 반복문은 n - i
4) 2)에서 설명했듯이 맨 앞자리가 갱신되고 거기서 부터 비교를 다시 시작
하므로 => array[i + j]
5) 그리고 맨 앞과 비교한 수 중 가장 작은 수 자리 변경!
input = [4, 6, 2, 9, 1]
def selection_sort(array):
n = len(array)
for i in range(n - 1):
min_index = i
for j in range(n - i):
if array[i + j] < array[min_index]:
min_index = i + j
array[i], array[min_index] = array[min_index], array[i]
return
선택 정렬과 가장 큰 차이는 비교해보고 필요 할때만 자리를 이동 시킵니다!
이것도 똑같이 O(N^2) 이 지만, 만약 정렬이 거의 되어있는 list라면? break 문에 의해서 금방 끝나게 되므로 최선의 경우에는 Ω(N)
삽입 정렬 code
1) 비교 자체는 똑같이 n - 1
번 입니다.
2) 이번에는 비교횟수가 점점 늘어납니다
. 예를 들어 맨 앞에 4가 있고 6이 들어온다고 했을 때, 4와 6만 비교하면 되서 1번
, 6다음으로 2가 들어온다고 하면 6과 2, 4와 2 비교하면 되서 2번
입니다. => 이렇게 비교하는 횟수가 1씩 늘어나므로 j의 범위는 i 로 정해줘서 하나씩 늘어 나게 하고, 맨 처음 부터 비교 한번을 하므로 i의 범위를 1부터 n으로 합니다. 그럼 n - 1과 똑같은 횟수가 됩니다!
3) 앞에 있는 수가 뒤에 있는 수보다 크면 둘의 자리를 바꿔 줍니다.
여기서 만약 바로 앞에 있는 수가 더 크지 않으면 더 앞에 있는 수와는 비교할 필요가 없습니다!
이미 앞은 정렬이 되어있으니까요! 예를 들어, [2, 4, 6, 9, 1]의 경우가 9가 새로 들어온다고 가정했을 때, 6과 9를 비교하면 이미 6이 9보다 작으므로 앞에 2와 4도 당연히 9보다 작다는 걸알 수 있습니다. 따라서 이런경우에는 더 비교하지 않고 break! 삽입 정렬의 가장 큰 특징 이라고 할 수 있어요!
input = [4, 6, 2, 9, 1]
def insertion_sort(array):
n = len(array)
for i in range(1, n):
for j in range(i):
if array[i - j - 1] > array[i - j]:
array[i - j - 1], array[i - j] = array[i - j], array[i - j - 1]
else:
break
return
배열을 앞부분과 뒷부분으로 쪼개서 정렬하고 병합하는 과정을 반복합니다!
시간 복잡도는 O(NlogN)
병합 code
1) 일단 merge 함수는 두개로 쪼개진 (정렬된)list가 각각 있을 때, 각각의 원소를 하나씩 비교해서 크기가 작은 거를 새로운 result list에 추가해 줍니다. 이렇게 더 작은 걸 가지고 있던 list는 그 다음 원소를 비교할 수 있도록 index를 +1 해줍니다 => array1 요소를 result list에 추가해주면 i += 1, array2면 j += 1
2) 한 list가 먼저 끝나면 나머지 list에 있는 원소들을 그대로 result list에 마저 추가해 줍니다
array_a = [1, 2, 3, 5]
array_b = [4, 6, 7, 8]
def merge(array1, array2):
result = []
i = 0
j = 0
while i < len(array1) and j < len(array2):
if i < len(array1) and array1[i] <= array2[j]:
result.append(array1[i])
i += 1
elif j < len(array2) and array1[i] > array2[j]:
result.append(array2[j])
j += 1
if i == len(array1):
while j < len(array2):
result.append(array2[j])
j += 1
if j == len(array2):
while i < len(array1):
result.append(array1[i])
i += 1
return result
print(merge(array_a, array_b))
merge 함수를 사용해서 합칩니다.
array = [5, 3, 2, 1, 6, 8, 7, 4]
def merge_sort(array):
if len(array) <= 1:
return array
mid = len(array) //2
left_list = array[:mid]
right_list = array[mid:]
return merge(merge_sort(left_list), merge_sort(right_list))
Last In First Out
제일 나중에 들어간게 제일 먼저 나옵니다!
넣은 순서가 필요한 경우 스택 사용! ex) 뒤로가기 버튼
data를 넣고 빼는 게 빈번? Linked List
비슷!
스택 직접 구현하는 code
class Node:
def __init__(self, data):
self.data = data
self.next = None
class Stack:
def __init__(self):
self.head = None
# 맨 앞에 새로운 Node추가
def push(self, value):
new_node = Node(value)
new_node.next = self.head
self.head = new_node
return
# 제일 나중에 들어간걸 제일 먼저 나오게 하므로
# 맨 앞에 있는 것 반환
def pop(self):
if self.is_empty():
return "Stack is empty!"
delete_node = self.head
self.head = self.head.next
return delete_node
def peek(self):
if self.is_empty():
return "Stack is empty!"
return self.head.data
# isEmpty 기능 구현
def is_empty(self):
return self.head is None
First In First Out
먼저 들어간게 먼저 나옵니다!
따라서, 순서대로 처리 되어야 하는 일에 필요! ex) 주문 들어온 순서 대로 처리, 먼저 해야 하는 일 저장
stack은 한쪽에서 넣고 빼고를 다하지만, queue는 한쪽에서 넣고 다른 한쪽에서 뺄 수 있습니다!
stack 처럼 넣고 빼고가 빈번하므로 Linked List
와 비슷하게 구현!
큐 직접 구현하는 code
class Node:
def __init__(self, data):
self.data = data
self.next = None
class Queue:
def __init__(self):
self.head = None
self.tail = None
# 맨 뒤에 새로운 node 추가하기
def enqueue(self, value):
new_node = Node(value)
if self.is_empty():
self.head = new_node
self.tail = new_node
return
self.tail.next = new_node
self.tail = new_node
return
# 큐는 맨 앞에 있는게 맨 먼저 들어간 것이므로
# head를 삭제 => 제일 먼저 들어간게 제일 먼저 나온다
def dequeue(self):
if self.is_empty():
return "Queue is empty"
delete_node = self.head
self.head = self.head.next
return delete_node.data
def peek(self):
if self.is_empty():
return "Queue is empty"
return self.head.data
def is_empty(self):
return self.head is None
🤔 실제 문제를 풀 때는 ?
stack과 queue 개념을 이용하기 위해 직접 구현해봤지만 문제를 풀때는 파이선의 list를 쓰면 됩니다.
stack.append(4) => push(4)
stack.pop(4) => pop(4)
queue.pop(0) => dequeue()
파이썬의 dictionary
와 거의 같습니다!
key
를 통해서 value
를 불러옵니다!
하지만 dictionary는 내부적으로 array(배열)을 사용하므로 어떤 index에 특정한 값을 넣어줄지를 정해야 합니다! 그 때 사용하는 것이 바로 해쉬함수
입니다!
👀 Hash function ?
임의의 길이를 갖는 메세지를 입력하면 고정된 길이의 해쉬값을 출력하는 함수 입니다! ex) hash("fast") = -146084012848775433
해쉬 함수를 사용해서 나온 값은 너무 크므로 그 값을 list의 길이로 나눈 나머지 값을 index로 해줍니다!
어떤 수든 나누는 수 보다 나머지가 작기 때문에 가능합니다!
해쉬 code
class Dict:
def __init__(self):
self.items = [None] * 8
def put(self, key, value):
index = hash(key) % len(self.items)
self.items[index] = value
return
def get(self, key):
index = hash(key) % len(self.items)
return self.items[index]
한 index에 data를 넣되, 예쁘게 정리해서 즉, linked list로 연결해서 넣어줍니다!
이렇게 겹치는 현상을 충돌 이라 하고 아래 그림 처럼 Linked List를 이용해서 충돌을 해결하는 걸 체이닝 이라고 합니다! class Dict:
def __init__(self):
self.items = [None] * 8
def put(self, key, value):
index = hash(key) % len(self.items)
# 이미 index에 다른 data가 있다면 그 뒤에 붙여주자!
return self.items[index].add(key, value)
def get(self, key):
index = hash(key) % len(self.items)
# 한 index에 여러 data가 있다면 key로 value를 가져오자!
return self.items[index].get(key)