ALGORITHM/Theory

Radix Sort - 기수 정렬

Harimad 2022. 4. 29. 16:40

목차

기수 정렬 : 소개
기수 정렬 : helper 메소드
기수 정렬 : 의사코드
기수 정렬 : 구현
기수 정렬 : 빅오 복잡도

기수 정렬 : 소개

https://visualgo.net/en/sorting

 

1. 기수 정렬은 숫자 리스트에서 작용하는 특수 정렬 알고리즘이다.
2. 결코 요소들을 비교하지 않는다!
3. 숫자의 크기는 자릿수로 결정된다.
4. 자릿수가 많을수록 숫자가 커진다!

기수 정렬 : helper 메소드

기수 정렬을 구현하려면 먼저 몇 가지 헬퍼함수를 만드는 것이 좋다.

getDigit(num, place) 는 주어진 place인자의 값에 해당하는 num인자의 값을 반환한다. (자릿수에 해당하는 값 리턴)

getDigit(12345, 0); // 5
getDigit(12345, 1); // 4
getDigit(12345, 2); // 3
getDigit(12345, 3); // 2
getDigit(12345, 4); // 1
getDigit(12345, 5); // 0
function getDigit(num, i) {
  return Math.floor(Math.abs(num) / Math.pow(10, i)) % 10;
}

실행순서

Math.abs(12345) => 12345

Math.pow(10, 4) => 10000

Math.floor(12345 / 10000) => 1

1 % 10 => 1

 

digitCount(num)은 num 인자의 자릿수를 반환한다.

digitCount(1); // 1
digitCount(25); // 2
digitCount(314); // 3
function digitCount(num) {
  if (num === 0) return 1;
  return Math.floor(Math.log10(Math.abs(num))) + 1;
}

 

digitCount(1234) 실행순서
1. Math.abs(1234); // 1234
2. Math.log10(1234); // 3.091315159697223
3. Math.floor(3.091315159697223) // 3
4. 3 + 1 // 4

 

mostDigits(nums)는 숫자 배열을 인자로 받고, 배열안에서 가장 큰 숫자의 자릿수를 리턴한다.

mostDigits([1234, 56, 7]); // 4
mostDigits([1, 1, 11111, 1]); // 5
mostDigits([12, 34, 56, 78]); // 2
function mostDigits(nums) {
  let maxDigits = 0;
  for (let i = 0; i < nums.length; i++) {
    maxDigits = Math.max(maxDigits, digitCount(nums[i]));
  }
  return maxDigits;
}

mostDigits([1, 11, 111, 1111, 11111]); // 5  실행순서

i 가 0일 때: maxDigits === 1,
i 가 1일 때: maxDigits === 2,
i 가 2일 때: maxDigits === 3,
i 가 3일 때: maxDigits === 4,
i 가 4일 때: maxDigits === 5 (5 자릿수)

 

기수 정렬 : 의사코드

1. 숫자 목록을 받는 함수(radixSort)를 정의한다.
2. 가장 큰 숫자가 몇 자릿수 인지 알아낸다. (최대 자리수(mostDigits) 메소드 사용)
3. 2번값이 만약 4라면, 루프를 4 번(0부터 1, 2, 3 진행) 수행한다.
4. 반복문 진행  
  4-1. 각 자릿수에 버킷을 만든다. (버킷은 빈 배열이다. 하위 배열이 10(0~9)개 있는 배열이다.)
  4-2. 루프를 수행할 때마다 각각의 수를 올바른 버킷에 넣는다.
5. 기존 배열을 버킷의 값(0~9)으로 교체한다.
6. 마지막에는 목록을 반환

기수 정렬 : 구현

function getDigit(num, i) {		//🤔 getDigit(num, place) - 자릿수에 해당하는 값! 리턴
  return Math.floor(Math.abs(num) / Math.pow(10, i)) % 10;
}

function digitCount(num) {		//🤔 digitCount(num) - num인자의 자릿수! 리턴
  if (num === 0) return 1;
  return Math.floor(Math.log10(Math.abs(num))) + 1;
}

function mostDigits(nums) {		//🤔 mostDigits(nums) - 숫자 배열안에 가장 큰 숫자의 자릿수 리턴
  let maxDigits = 0;
  for (let i = 0; i < nums.length; i++) {
    maxDigits = Math.max(maxDigits, digitCount(nums[i]));
  }
  return maxDigits;
}

function radixSort(nums){
    let maxDigitCount = mostDigits(nums); 	// 최대 자릿값: 4
    for(let k = 0; k < maxDigitCount; k++){ 	// 0~3 까지 4번 반복 (1, 10, 100, 1000의 자리수)
        let digitBuckets = Array.from({length: 10}, () => []);	// 길이10인 빈 배열 생성
        for(let i = 0; i < nums.length; i++){ 	// 입력된 배열 길이만큼 반복
            let digit = getDigit(nums[i],k); 	// 배열의 요소값과 k(자리값)을 헬퍼함수에 넣어서 자릿수에 해당하는 값 구함
            digitBuckets[digit].push(nums[i]); 	// 버킷배열의 인덱스자리에 nums[i] 요소 push
        }
        nums = [].concat(...digitBuckets); 	// 새로운 배열([])에 버킷배열값을 얕은복사한다.
    }
    return nums; // 최종 배열 반환.
}

radixSort([23,345,5467,12,2345,9852])  //[12, 23, 345, 2345, 5467, 9852]

동작순서

INPUT [ 23, 345, 5467, 12, 2345, 9852 ]
       \ 0 1 2 3 4 5 6 7, 8 9
1 자리   12, 9852   23   345, 2345   5467  
10 자리   12 23   345, 2345 9852 5467    
100 자리 12, 23     345, 2345 5467       9852
1000 자리 12, 23, 345   2345     5467     9852
OUTPUT [ 12, 23, 345, 2345, 5467, 9852 ]

 

기수 정렬 : 빅오 복잡도

회고
1. 병합 정렬 및 퀵 정렬은 효율적인 정렬 알고리즘이다.
2. 퀵 정렬은 최악의 경우 느릴 수 있지만 평균적으로 병합 정렬과 비슷하다.
3. 병합 정렬은 새 배열을 만들기 때문에 더 많은 메모리를 사용한다. (내부 병합 정렬은 존재하지만 실제로는 복잡하다!).
4. 기수 정렬은 숫자에 대한 빠른 정렬 알고리즘이다.
5. 기수 정렬은 선형적인 시간안에 숫자를 정렬하기 위해 자릿수를 이용한다. (고정된 자릿수의 경우에)