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