지금까지 살펴본 정렬들(버블 정렬, 선택 정렬, 삽입 정렬, 합병 정렬, 퀵 정렬)은 모두 비교 정렬 알고리즘
에 속한다.
2개
의 항목을 비교한다.O(n^2)
~ O(n log n)
이다.있다. 하지만 비교 정렬이 아닌 다른 방식으로 가능하다.
수학적으로 비교 정렬의 평균 시간 복잡도에는 하한선이 있기 때문이다.
비교 정렬 알고리즘이 아닌, 데이터의 특별한 속성을 이용한 정렬 방식이 있다.
=> 그것이 바로 기수 정렬
(Radix sort)이다.
비교를 수행하지 않는 특별한 정렬 알고리즘.
자릿수
로 인코딩 한다.일의 자리수를 기준으로 오름차순으로 정렬
십의 자리수를 기준으로 오름차순으로 정렬
백의 자리수를 기준으로 오름차순으로 정렬
...
가장 큰 수의 자릿수가 끝날 때까지 반복하다 보면 정렬이 되어 있다.
1. getDigit(num,place)
: 수와 위치를 받아서 그 위치의 숫자를 반환하는 함수
ex. getDigit(12345, 0) -> 일의 자리수 숫자 반환 : 5
getDigit(12345, 1) -> 십의 자리수 숫자 반환 : 4
자릿수를 알아내는 방법은 10의 제곱수
를 이용하는 것이다.
10의 0제곱 = 1 (일의 자리수)
10의 1제곱 = 10 (십의 자리수)
10의 제곱 = 100 (백의 자리수)
구현
function getDigit(num, i){
return Math.floor(Math.abs(num) / Math.pow(10, i)) % 10;
}
// getDigit(12345, 1)
// 12345 / 10 = 1234.5
// 1234.5를 내림하여 1234
// 1234 % 10 = 4 (일의 자리수)
2. digitCount(num)
: 자릿수 계산해주는 함수
ex. digitCount(2) -> 1
digitCount(25) -> 2
분류하는 시행을 몇 번 반복할지 알아내려면 가장 큰 수의 길이를 알아내야 한다.
ex. [4,7,29,408] -> 가장 큰 수인 408은 일의 자리, 십의 자리, 백의 자리를 모두 보아야 하므로 3번만큼 반복해야 한다.
Math.log10(num)
: 10을 몇 번 제곱해야 num이 나오는지 알려줌.구현
function digitCount(num){
if(num === 0) return 1;
return Math.floor(Math.log10(Math.abs(num))) + 1;
}
// Math.log10(423) = 2.62634..
// 내림해서 2
// 2 + 1 = 3 (= 423의 길이)
3. mostDigits(nums)
: 숫자들이 담긴 배열을 받아서 가장 큰 자릿수를 알려주는 함수.
(digitCount
활용.)
ex. mostDigits([1234, 56, 7]) -> 4
Math.max()
를 활용해서 기존 값보다 클 때만 갱신되도록.function mostDigits(nums){
let maxDigits = 0;
for(let i=0;i<nums.length;i++){
maxDigits = Math.max(maxDigits, digitCount(nums[i]));
}
return maxDigits;
}
// helper function
function getDigit(num, i) {
return Math.floor(Math.abs(num) / Math.pow(10, i)) % 10;
}
function digitCount(num) {
if (num === 0) return 1;
return Math.floor(Math.log10(Math.abs(num))) + 1;
}
function 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);
for(let k = 0; k < maxDigitCount; k++){
let digitBuckets = Array.from({length: 10}, () => []); // 배열 안에 10개의 하위 배열을 만들어줌.
for(let i = 0; i < nums.length; i++){
let digit = getDigit(nums[i],k);
digitBuckets[digit].push(nums[i]);
}
nums = [].concat(...digitBuckets);
}
return nums;
}
radixSort([23,345,5467,12,2345,9852])
Spread Operator와 concat
spread operator로 이중 배열을 풀고 concat으로 합쳐줌.
[ ].concat(...[[[1],[2],[3]])
-> [1,2,3]
n
= 배열의 길이 (= 정렬할 항목의 갯수), k
= 항목의 자릿수
시간 복잡도 : O(nk)
공간 복잡도 : O(n + k)
이론적으로 기수 정렬은 모든 비교 정렬보다 빠르다.
하지만, 항목의 길이 k
가 매우 길어질 경우 무시할 수 없으므로 논란의 여지가 있다.
무작위로 분산된 데이터를 다룰 때는 k가 log n
이 되어 시간 복잡도가 O(n log n)
이 되고, 그러면 비교 정렬의 속도와 비슷해질 수도 있다.