2009/03/18 - [Dev.Programming/DS/Algorithm] - 기수 정렬 - in Ruby

예전에 LSD 기수 정렬 에 대해 포스팅했었는데, 이번에는 MSD 기수 정렬과 기수교환 정렬에 대해 포스팅해 본다.

LSD 기수 정렬은 위의 포스팅에서 알 수 있듯이 가장 덜 중요한 자리수인 오른쪽부터 정렬하여 왼쪽으로 정렬 기준을 바꾸면서 정렬하는 방법이다.
이 방법은 결정적으로 가변 길이 키에 적용하기 어렵다는 단점이 있다. 짧은 쪽의 키에 패딩을 가해 문자열의 길이를 같게 하면 해결이 되지만 번거로운 작업이다.

MSD 기수 정렬은 반대로 가장 중요한 자릿수인 왼쪽부터 정렬하여 오른쪽으로 정렬 기준을 바꾸면서 정렬하는 방법이다. 숫자를 정렬할 경우 여전히 패딩이 필요하지만, 문자열을 정렬할 경우 사전식(역순포함)으로 정렬한다면 패딩이 필요없게 된다. 또한, LSD 의 경우 맨 앞자리까지 정렬을 꼭 수행해야 정렬이 완료되지만 MSD 의 경우 앞부분이 유일해지면 그 원소는 비교 대상에서 제외시킬 수 있어 조금 더 효율을 기할 수 있다.

LSD / MSD 기수 정렬은 버킷 정렬이나 셈 정렬 등으로 각 자릿수를 정렬하는데, 별도의 메모리 공간이나 빈도수 계산 시간을 요하게 된다.

기수 정렬시에 별도의 메모리 공간을 사용하지 않고 자체적으로 해결하기 위한 것이 기수교환 정렬이다.

기수교환 정렬은 MSD 기수 정렬을 기반으로 하되, 정렬 과정에서 퀵 소트를 사용하는 방법이다.

퀵 소트도 그대로 사용하지 않고, 파티션 함수를 약간 수정한다.
일반적으로 파티션 함수를 거치면, 피벗을 제외하고 두 개의 그룹으로 나뉘게 되는데, 값이 전부 다 다를 경우에는 이 방법이 유용하다.
하지만 지금은 한자리만 기준으로 비교하는 것이므로 피벗과 같은 값이 많이 나타날 수 있으며, 이 그룹은 해당 자리에서는 더 이상의 정렬이 필요없다.
그러므로 파티션 함수에서 피벗을 하나 정하고 피벗보다 작은 그룹, 피벗과 같은 그룹, 피벗보다 큰 그룹으로 3개의 그룹으로 쪼개도록 한다. 그러면 피벗보다 작은 그룹, 피벗보다 큰 그룹만 재귀적으로 정렬대상에 포함된다.

한 자리의 정렬이 완료되면, 각 자리를 그룹화하여 다음 자리에 대해 각각 퀵소트를 적용한다.(MSD 기수정렬 방식)




def compare_items_element(item1, item2, elem_idx)  
  ch_item1 = item1[elem_idx]

  ch_item2 = item2[elem_idx]

  if !ch_item1 && !ch_item2

    nil
  elsif !ch_item1
    1
  elsif !ch_item2

    -1
  else
    ch_item1 <=> ch_item2
  end

end

def radix_exchange_recurse(array, start_idx, end_idx, curr_char_idx)

  # If start index is equal or higher from end_idx, it doesn't need to sort
  return if start_idx >= end_idx

  # If all elements' nth char. is nil, sort for current range(set) is complete

  all_nil = true
  (start_idx..end_idx).each do |idx|

    all_nil = nil if array[idx][curr_char_idx]
  end

  return if all_nil

  # sort by current character index
  quicksort(array, start_idx, end_idx) do |item1, item2|

    compare_items_element(item1, item2, curr_char_idx)
  end

  p "#{start_idx} ~ #{end_idx} step end... current array is \'#{array.join " "}\'"

  # grouping and re-sort
  last_char = array[start_idx][curr_char_idx]
  char_group_start_idx = start_idx

  ((start_idx + 1)..end_idx).each do |idx2|

    if array[idx2][curr_char_idx] != last_char
      radix_exchange_recurse(array, char_group_start_idx, idx2 - 1,

        curr_char_idx+1)

      last_char = array[idx2][curr_char_idx]

      char_group_start_idx = idx2
    end

  end

  # last group

  radix_exchange_recurse(array, char_group_start_idx, end_idx,
        curr_char_idx+1)

end

def radix_exchange_sort(array)
  radix_exchange_recurse(array, 0, array.length - 1, 0)

end

def quicksort(array, begin_idx, end_idx, &compare_block)

  return if begin_idx >= end_idx

  #p "Quicksorting #{begin_idx} to #{end_idx}..."

  pivot_start_idx, pivot_end_idx =
    partition(array, begin_idx, end_idx, &compare_block)

  #p "Pivot is #{pivot_idx}"

  quicksort(array, begin_idx, pivot_start_idx - 1, &compare_block)

  quicksort(array, pivot_end_idx + 1, end_idx, &compare_block)

end

# 3-group partition
# less than pivot, equal to pivot, higher than pivot
# http://blogs.microsoft.co.il/blogs/eyal/archive/2009/02/15/interview-question-6-three-way-partition-generic-solution.aspx
def partition(array, begin_idx, end_idx)

  pivot_idx = begin_idx
  pivot = array[pivot_idx]

  first_p = second_p = pivot_idx + 1
  third_p = end_idx

  while second_p <= third_p
    comp_val = yield(array[second_p], array[pivot_idx])

    if comp_val > 0
      # move to the third partition
      array[second_p], array[third_p] = array[third_p], array[second_p]

      third_p -= 1
    elsif comp_val < 0
      # move to the first partition

      array[first_p], array[second_p] = array[second_p], array[first_p]

      first_p += 1
      second_p += 1
    else

      # leave in the second partition
      second_p += 1
    end

  end

  # move pivot to second partiton's first position
  array[pivot_idx], array[first_p - 1] =

    array[first_p - 1], array[pivot_idx]

  return first_p - 1, third_p
end

def make_example_array

  %w(add cab fee bad dad bee bed ace)
end

array = make_example_array
p array
radix_exchange_sort(array)

p array

1
2
3
4
5

6
["add", "cab", "fee", "bad", "dad", "bee", "bed", "ace"]

"0 ~ 7 step end... current array is 'add ace bad bed bee cab dad fee'"
"0 ~ 1 step end... current array is 'ace add bad bed bee cab dad fee'"
"2 ~ 4 step end... current array is 'ace add bad bee bed cab dad fee'"
"3 ~ 4 step end... current array is 'ace add bad bed bee cab dad fee'"
["ace", "add", "bad", "bed", "bee", "cab", "dad", "fee"]

저작자 표시 비영리 변경 금지
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by Heart