- 버블정렬, 선택정렬, 삽입정렬은 확장성이 좋지 않다. 예를 들어 큰 규모의 데이터 정렬에서는 시간이 매우 많이 걸린다.
- 큰 규모의 데이터를 빨리 정렬하기 위해서 합병정렬, 퀵정렬, 기수정렬 등의 알고리즘을 사용할 수 있다.
- 시간복잡도 O(n^2) -> O(n)
병합
과 정렬
을 조합한 알고리즘이다.function merge(arr1,arr2){ //정렬하는코드
var results = [];
let i = 0;
let j = 0;
while(i < arr1.length && j < arr2.length){
if(arr1[i] < arr2[j]){
results.push(arr1[i]);
i++;
}else{ //같은값이어도 arr2로서 실행됨
results.push(arr2[j]);
j++;
}
}
while(i<arr1.length){ //위에 루프가 끝났는데 배열 1이나 배열 2에 요소가 남았을때 전부 푸쉬하는거
results.push(arr1[i]);
i++;
}
while(j<arr2.length){
results.push(arr2[j]);
j++;
}
return results;
}
- 비어 있거나 하나의 요소만 있는 배열이 될 때까지 배열을 반으로 나눈다. (slice 사용, 재귀).
- 아까 작게 나눈 배열들을 원본 배열의 길이로 돌아올 때까지 서로 병합한다. (정렬된 배열을 합치는 헬퍼 함수 활용).
- 배열이 모두 병합되면 병합된 배열을 반환한다.
function mergeSort(arr){ //분할 후 정렬하기위해
if(arr.length <= 1) return arr; //arr 길이가 0,1이 될때까지 분할
let midpoint = Math.floor(arr.length / 2);
let left = mergeSort(arr.slice(0,midpoint));
let right = mergeSort(arr.slice(midpoint));
return merge(left,right);
}
시간 복잡도(Best case, worst case, average case 모두) : O(nlogn)
log n
: n개의 요소를 지닌 배열을 1개 이하의 요소만 지닌 배열들로 나누기 위해서는 log2 n 번의 과정이 필요하다. (ex 8개 요소를 지닌 배열을 1개 요소만 지닌 배열들로 나누려면, 8→4→2→1처럼 log2 8(=3) 번의 과정 필요) - 재귀
n
: 최소단위로 나눈 배열들을 다시 합칠 때는 n 번의 비교를 해야한다. (위에서 merge함수)
위 작업들이 함께 연달아 수행되므로, O(nlogn) 의 시간 복잡도를 가지게 된다.
병합 정렬은 어떤 데이터에서도 적용 가능하면서 가장 빠른 편인 정렬 알고리즘이다.
공간복잡도 : O(n)
- 퀵 정렬은 재귀를 사용해서 데이터를 쪼개고, 배열이 0개나 1개의 요소를 가지면 정렬된 배열이 된다는 점을 이용해서 정렬을 했던 병합정렬과 같은 가정에서 출발한다.
피봇 포인트
기준점을 선택하여 해당 숫자보다 작은 숫자는 모두 왼쪽으로, 큰 숫자들은 오른쪽으로 옮긴다. 이 때 왼쪽과 오른쪽 숫자의 순서는 상관없다.
피벗
을(를) 선정한다.(분할)
리스트의 크기가 0이나 1이 될 때까지
병합 정렬을 구현하려면 먼저 피벗의 양쪽에 있는 배열의 요소를 배열하는 기능을 담당하는 함수를 구현하는 것이 유용하다.
피벗보다 작은 모든 값을 피벗의 왼쪽으로 이동시키고, 피벗보다 큰 모든 값을 피벗의 오른쪽으로 이동하도록 배열의 요소를 재정렬한다.
헬퍼함수는 새 배열을 만들지 않고 기존 배열에 대해서 작업을 한다.
완료되면 헬퍼 함수는 피벗의 인덱스를 반환한다.
피벗 선택
function pivot(arr,start=0,end=arr.lenght-1){ //재귀적으로 사용할 pivot 헬퍼함수
var pivotpoint = arr[start]; //맨처음 요소로 피봇포인트 설정
var swapidx = start;
for(let i=start+1; i<=end; i++){
if(pivotpoint > arr[i]){
swapidx++;
[arr[swapidx],arr[i]] = [arr[i],arr[swapidx]]; //작은걸 추적해가며 스왑해줌
}
}
[arr[start],arr[swapidx]] = [arr[swapidx],arr[start]];
return swapidx;
}
function quickSort(arr, left = 0,right = arr.length-1){
if(left<right){
let pivotIdx = pivot(arr,left,right); //pivot 함수 실행으로 반환된 인덱스 값
//하나가 구해지고 그것의 왼쪽 정렬
quickSort(arr,left,pivotIdx-1);
//하나가 구해지고 그것의 오른쪽 정렬
quickSort(arr,pivotIdx+1,right);
}
return arr;
}
시간 복잡도: Best case , average case: O(nlogn)
퀵정렬도 병합정렬처럼 log2 n 만큼의 decompostion(분할) 작업을 해야 한다. 예를 들어 32개 요소가 있는 경우에는 절반씩 계속 나누어 1개의 요소씩으로 파편화하는데 5번(log2 32)번의 분할을 해야 한다.
이렇게 매 분할마다 n번의 비교를 해야 한다.
Worst case : O(n^2)
만약 이미 정렬이 되어 있는 배열을 퀵정렬하고, 선택된 pivot이 항상 가장 작은 값이라면 (가장 큰 값이거나), 모든 요소에 대해 n번의 분할을 하고 매번 모든 요소에 대해 n번의 비교를 해서, n^2의 시간이 걸린다.
Tip! pivot으로 지금까지 했던 것처럼 첫 번째 요소를 선택하는 대신에 무작위(또는 가운데)로 요소를 선택하는 방식으로 상기 문제를 피할 수 있다. 이렇게 하면 정렬된 배열이라고 할지라도 문제 발생을 피할 확률을 높일 수 있다.
공간 복잡도 : O(logn)
자릿수
에 이미 저장되 있다는 사실을 이용하여 각 자릿수의 숫자를 나눠 담고, 가장 큰 숫자의 자리 수 까지 이를 반복한다.getDigit(num,i)
> 숫자 num의 i번째 자릿수 반환function getDigit(num,i){ //숫자에서 몇번째 자리에 어떤 숫자가 있는지 알아내기
return Math.floor(Math.abs(num) / Math.pow(10,i)) % 10;
}
digitCount(num)
> 주어진 숫자의 총 자릿수 알아내기function digitCount(num){ //주어진 숫자의 총 자릿수 알아내기
if(num === 0) return 1;
return Math.abs(num).toString().length;
}
mostDigits(arr)
> 주어진 숫자 배열 중 가장 큰 숫자의 자릿수 반환function mostDigits(arr){
let max = 0;
for(let i=0; i<arr.length; i++){
if(max < digitCount(arr[i])){
max = digitCount(arr[i]);
}
}
return max;
}
function 기수정렬(nums){ //radixSort
var maxdigit = mostDigits(nums); //들어온 입력 중 최대 자릿수 알아내기
for(let i=0; i<maxdigit; i++){ //최대 자릿수 만큼 반복 (버킷에서의 정렬)
let digitBucket = Array.from({length : 10},() => []); //하나의 배열안에 10개의 하위배열생성
for(let j=0; j<nums.length; j++){ //nums배열에서 요소 선택 루프
let digit = getDigit(nums[j],i);
digitBucket[digit].push(nums[j]);
} //여기까지는 버킷에 넣어서 구성은 하지만 배열에 들어간 순서대로 새 배열을 반환해줘야함
nums = [].concat(...digitBucket);
}
return nums;
}
console.log(Array.from({ length: 10 }, () => []));
// [ [], [], [], [], [], [], [], [], [], [] ]
console.log(Array.from({ length: 10 }, () => ["Hi"]));
// [ [ 'Hi' ], [ 'Hi' ], [ 'Hi' ], [ 'Hi' ] ]
console.log([].concat(...[[1], [2], [3]])); // [1, 2, 3]
n : 정렬할 항목 수
, k : 자릿수