Merge Sort
2020-1νκΈ°, λνμμ βμκ³ λ¦¬μ¦β μμ μ λ£κ³ 곡λΆν λ°λ₯Ό μ 리ν κΈμ λλ€. μ§μ μ μΈμ λ νμμ λλ€ :) μ 체 ν¬μ€νΈλ Algorithm ν¬μ€νΈμμ νμΈνμ€ μ μμ΅λλ€.
Merge Sort
βμ λ ¬βμ μκ³ λ¦¬μ¦μ κ°μ₯ κ·Όμμ΄ λλ μμ μ λλ€. κ·Έμ€μμ βλ³ν© μ λ ¬(Merge Sort)βκ³Ό βν΅ μ λ ¬(Quick Sort)βμ μΈλ₯ μμ¬μμ μ΅κ³ μ μκ³ λ¦¬μ¦μ΄λΌκ³ μκ° ν©λλ€! π
Merge Sortλ <λΆν μ 볡>μ μ λ§ μΆ©μ€ν μννλ μκ³ λ¦¬μ¦ μ λλ€.
βsplit the list into two halves, recursively sort each half, and them merge them.β

Merge Sortλ κΈ°λ₯μ μΌλ‘ λ΄€μ λ, βMerge Sortβμ βMergeβ λ κ°μ§ μμ λ§ μννλ©΄ λλ€.
function: mergesort($a[1β¦n]$)
if $n > 1$ then
ββ return merge(mergesort($a[1 β¦ n/2]$), mergesort($a[n/2+1 β¦ n]$))
else
ββ return $a$
end if
function: merge($x[1β¦k]$, $y[1β¦l]$)
if $k=0$: β return $y[1β¦l]$
if $l=0$: β return $x[1β¦k]$
if $x[1] \le y[1]$ then
ββ return $x[1]$ $\circ$ merge($x[2β¦k]$, $y[1β¦l]$)
else
ββ return $y[1]$ $\circ$ merge($x[1β¦k]$, $y[2β¦l]$)
end if
μ΄λ²μλ Complexityλ₯Ό κ³μ°ν΄λ³΄μ. μ νμμ μμ±νλ©΄ μλμ κ°λ€.
\[T(n) = 2 \cdot T(n/2) + O(n)\]κ³ μ λΉμ© $O(n)$λ merge μμ μμ λ°μνλ λΉμ©μ΄λ€!
$a=2$, $b=2$, $c=1$μ΄λ―λ‘, $a/b^c = 2/2 = 1$μ΄λ€. λ°λΌμ <Master Theorem>μ λ°λΌ
\[O(n) \cdot \log n = O(n \log n)\]μ Complexityλ₯Ό κ°μ§λ€!
Merge Sortλ κ°λ¨νμ§λ§ κ°λ ₯νλ€! λ³΄ν΅ Merge Sortλ₯Ό μ§μ ꡬννλ κ²½μ°λ λλ¬Όμ§λ§, μ΄λ€ κ²½μ°μμ Merge Sortμ μμ΄λμ΄λ₯Ό μ±μ©ν΄μ νμ΄μΌ νλ€!
Worst-case Analysis
μ λ ¬ μκ³ λ¦¬μ¦μ μ μ°¨λ <νΈλ¦¬ Tree>μ ννλ‘ ννν μ μλ€. μλ₯Ό λ€μ΄ μλμ νΈλ¦¬λ $a_1$, $a_2$, $a_3$ μΈ κ°μ μμμ λν μ λ ¬μ λμνν κ·Έλ¦Όμ΄λ€.

μμ κ°μ΄ μ£Όμ΄μ§ λ°°μ΄μ λν΄ μμλ₯Ό λΉκ΅νλ λͺ¨λ κ²½μ°λ₯Ό κ³ λ €νκ² λλ©΄, μ°λ¦¬λ λ°°μ΄μ $n$κ° μμμ λν κ°λ₯ν λͺ¨λ <μμ΄ Permutation>μ νμΈνλ κ²κ³Ό λμΌνλ€. μ¦, $n!$ κ°μ§μμ κ°λ₯ν μμλ₯Ό μκ°νλ€λ λ§μ΄λ€. μ΄λ, βμ λ ¬βμ΄λΌλ μμ μ <λΉκ΅ νΈλ¦¬; Comparison Tree>μμ κ°λ₯ν νλμ <κ²½λ‘ Path>λ₯Ό μ·¨νλ κ²μ΄λΌκ³ ν μ μλ€.
λΉκ΅ νΈλ¦¬μμ κ°μ₯ κΈ΄ κ²½λ‘(longest path)μ κΈΈμ΄λ₯Ό μκ°ν΄λ³΄μ. λΉκ΅ νΈλ¦¬λ μ΄ $n!$κ° λ§νΌμ leaf nodeλ₯Ό κ°μ§λ―λ‘ νΈλ¦¬μ κΉμ΄(depth)λ $\log {(n!)}$μ΄ λλ€. μ΄ νΈλ¦¬μ βκΉμ΄βλ κ³§ Worst-caseμμμ λΉκ΅ νμμ΄λ€!!
μ½κ°μ μ 리λ€μ νμ©νλ©΄, $\log {(n!)}$μ λν΄ μλκ° μ±λ¦½ν¨μ λ³΄μΌ μ μλ€.
\[\log {(n!)} \ge c \cdot n \log n \quad \textrm{for some} \; c > 0\]μ΄κ²μ ν¨μ $\log {(n!)}$μ λν΄ $\Omega \left( n \log {n} \right)$μμ μλ―Ένλ€. κ·Έλ¦¬κ³ μ΄λ€ μ λ ¬ μκ³ λ¦¬μ¦λ $n$κ° μμλ₯Ό μ λ ¬νλ λ°μ $\Omega (n \log {n})$ λ§νΌμ λΉκ΅λ λ°λμ μνν΄μΌ ν¨μ μλ―Ένλ€. μ¦, μ λ ¬ μκ³ λ¦¬μ¦μ΄ κ°μ§ μ μλ Complexityμ ννμ μ΄ $n \log {n}$μ΄λΌλ μλ―Έμ΄λ€.
κ·Έλ°λ° μ°λ¦¬λ μ λ¬Έλ¨μμ <Master Theorem>μΌλ‘ Merge Sortκ° $O(n \log {n})$μ Complexityλ₯Ό κ°μ§λ€λ κ±Έ μ λνλ€. μ¦, Merge Sortμ Complexityκ° $n \log {n}$ λ³΄λ€ μ λ λμμ§μ§ μλλ€λ κ±°λ€! μ΄ λ§μ κ³§ $O(n \log {n})$μ ν¨μ¨μ κ°λ Merge Sortκ° optimal μ λ ¬ λ°©μμ΄λΌλ κ²μ λ§νλ€!! $\blacksquare$
p.s. λ³ΈμΈλ μ²μμλ μ΄ν΄κ° μ λμ§ μμλ€. μλ§ $\Omega (n \log {n})$μ μλ―Έκ° μ μλΏμ§ μμλ κ² κ°μλ°, λ³ΈμΈμ $\Omega (n \log {n})$λ₯Ό Complextiyμ ννμ μ΄λΌκ³ ν΄μνλ€. μ¦, μ λ ¬ μκ³ λ¦¬μ¦μ΄ $\Omega (n \log {n})$μ Complexityλ₯Ό κ°λλ€λ 건 λͺ¨λ μ λ ¬ μκ³ λ¦¬μ¦μ $n \log {n}$ λ³΄λ€ ν¬κ±°λ κ°μ Complexityλ₯Ό κ°μ§ μ λ°μ μλ€κ³ ν΄μνλ€λ λ§μ΄λ€. κ·Έλ°λ° Merge Sortλ $O(n \log {n})$μ Complextiyλ₯Ό κ°μ§λ, μ λ $n \log {n}$ μ΄μμΌλ‘λ Complexityκ° μ»€μ§μ§ μλλ€. κ·Έλ¬λ $O(n \log {n)}$μ $\Omega(n \log {n)}$ μν©μμ optimal μΌ μ λ°μ μλ€!
Implementation
μλ μ½λλ λ³ΈμΈ μ€νμΌ λλ‘ κ΅¬νν Merge Sort μ½λλ€. μλλ λ°°μ΄μ μ¬μ©ν΄ κ³΅κ° μ¬μ©μ΄ $O(1)$μΈ βIn-placeβλ‘ κ΅¬νν΄λ³΄λ €κ³ νλλ°, λκ° μ½λκ° μμκ² λμ€μ§ μμ κ² κ°μμ vector
STLκ³Ό κ³΅κ° μ¬μ©μ΄ $O(N \log N)$μΈ λ°©μμΌλ‘ ꡬνν΄λ΄€λ€.
vector<int> merge(vector<int> left, vector<int> right) {
int left_idx = 0, right_idx = 0;
vector<int> sorted;
while((left_idx < left.size()) && (right_idx < right.size())) {
if (left[left_idx] <= right[right_idx]) {
sorted.push_back(left[left_idx]);
left_idx += 1;
} else {
sorted.push_back(right[right_idx]);
right_idx += 1;
}
}
for (int i = left_idx; i < left.size(); i++) {
sorted.push_back(left[i]);
}
for (int i = right_idx; i < right.size(); i++) {
sorted.push_back(right[i]);
}
return sorted;
}
vector<int> mergeSort(vector<int> arr) {
if (arr.size() <= 1) {
return arr;
}
int mid = arr.size() / 2;
vector<int> leftSide = mergeSort(vector<int>(arr.begin(), arr.begin() + mid));
vector<int> rightSide = mergeSort(vector<int>(arr.begin() + mid, arr.end()));
return merge(leftSide, rightSide);
}
κ°μΈμ μΈ κ²½νμΌλ‘ MergeSort
λ pythonμΌλ‘ ꡬννλκ² κ°μ₯ μ½κ³ νΈνλ€! μλλ Leetcodeμ Sort an Array λ¬Έμ λ₯Ό ν΄κ²°ν μ½λμ΄λ€.
class Solution:
@staticmethod
def merge(left, right):
sorted = []
l_idx = r_idx = 0
while (l_idx < len(left) and r_idx < len(right)):
if left[l_idx] <= right[r_idx]:
sorted.append(left[l_idx])
l_idx += 1
else:
sorted.append(right[r_idx])
r_idx += 1
for i in range(l_idx, len(left)):
sorted.append(left[i])
for j in range(r_idx, len(right)):
sorted.append(right[j])
return sorted
def sortArray(self, nums: List[int]) -> List[int]:
if len(nums) <= 1:
return nums
mid = len(nums) // 2
left = self.sortArray(nums[:mid])
right = self.sortArray(nums[mid:])
return Solution.merge(left, right)
In-place Implementation
κ³΅κ° μ¬μ©μ΄ $O(1)$μΈ κ΅¬νμ λλ€. Leetcodeμ Sort List λ¬Έμ λ₯Ό νκΈ° μν΄ κ΅¬ννμμ΅λλ€.
κ²°κ³Όλ₯Ό 보면, μκ° λ³΅μ‘λλ κ·Έμ κ·Έλ° μμμ΄μ§λ§, λ©λͺ¨λ¦¬ μ¬μ©λμ΄ μλμ μΌλ‘ μ€μμ΅λλ€ βοΈ μκ°μ μ‘°κΈ ν¬μνλ©΄μ 곡κ°μ λν μ΄λμ λ³Έ κ²μ΄μ£ !
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
@staticmethod
def get_list_size(node: ListNode) -> int:
i = 0
while True:
i += 1
if node.next:
node = node.next
else:
return i
@staticmethod
def get_next_nth_node(node: ListNode, cnt: int) -> ListNode:
for _ in range(cnt):
if node.next:
node = node.next
return node
def merge(left_node, right_node) -> ListNode:
new_head = None
if left_node.val <= right_node.val:
new_head = left_node
left_node = left_node.next
else:
new_head = right_node
right_node = right_node.next
curr = new_head
while left_node or right_node:
if not left_node:
curr.next = right_node
right_node = right_node.next
elif not right_node:
curr.next = left_node
left_node = left_node.next
elif left_node.val <= right_node.val:
curr.next = left_node
left_node = left_node.next
else:
curr.next = right_node
right_node = right_node.next
curr = curr.next
return new_head
def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]:
if not head:
return head
if not head.next:
return head
sz = Solution.get_list_size(head)
mid = sz // 2
# print('sz: ', sz, 'mid: ', mid)
l_list = head
l_list_tail = Solution.get_next_nth_node(head, mid - 1)
r_list = l_list_tail.next
l_list_tail.next = None
# print('l_head', l_list.val, 'r_head', r_list.val)
l_list = self.sortList(l_list)
r_list = self.sortList(r_list)
return Solution.merge(l_list, r_list) # new head