2020-1ν•™κΈ°, λŒ€ν•™μ—μ„œ β€˜μ•Œκ³ λ¦¬μ¦˜β€™ μˆ˜μ—…μ„ λ“£κ³  κ³΅λΆ€ν•œ λ°”λ₯Ό μ •λ¦¬ν•œ κΈ€μž…λ‹ˆλ‹€. 지적은 μ–Έμ œλ‚˜ ν™˜μ˜μž…λ‹ˆλ‹€ :) 전체 ν¬μŠ€νŠΈλŠ” Algorithm ν¬μŠ€νŠΈμ—μ„œ ν™•μΈν•˜μ‹€ 수 μžˆμŠ΅λ‹ˆλ‹€.

9 minute read

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

Categories:

Updated: