재귀
적 방법으로 원소를 반 씩 쪼개나가면서 정렬을 수행하는 방법.배열을 쪼개는 경우: 첫번째 줄에서는 1번의 쪼갬, 두번째 줄에서는 2번의 쪼갬, 세번째 줄에서는 4번의 쪼갬... n
번째 줄에서는 2^n
번의 쪼갬이 필요하다... 1+2+4...2^n = 2n....이므로, 이는 시간복잡도 O(n)
의 연산이다.
배열을 합치는 경우: 크기가 1인 배열들을 합쳐서 크기가 N인 원소를 다시 만들어야한다. 이는 매 배열의 크기가 2씩 늘어나는 합쳐짐의 과정을 거친다. 배열의 크기가 2배로 늘어나면서 합치는 과정은 각 단계마다 N
의 연산이 필요하고, 또한 해당 연산을 총 log2N
번 반복해야 하기떄문에 O(NlogN)
의 연산이 필요하게 된다.
이 둘을 합치면 총 O(NlogN)
의 연산이 필요하게 된다.
#include <bits/stdc++.h>
using namespace std;
int n = 10;
int arr[1000001] = {15, 25, 22, 357, 16, 23, -53, 12, 46, 3};
int tmp[1000001]; // merge 함수에서 리스트 2개를 합친 결과를 임시로 저장하고 있을 변수
// mid = (st+en)/2라고 할 때 arr[st:mid], arr[mid:en]은 이미 정렬이 되어있는 상태일 때 arr[st:mid]와 arr[mid:en]을 합친다.
void merge(int st, int en){
int mid = (st+en)/2;
int lidx = st; // arr[st:mid]에서 값을 보기 위해 사용하는 index
int ridx = mid; // arr[mid:en]에서 값을 보기 위해 사용하는 index
for(int i = st; i < en; i++){
if(ridx == en) tmp[i] = arr[lidx++];
else if(lidx == mid) tmp[i] = arr[ridx++];
else if(arr[lidx] <= arr[ridx]) tmp[i] = arr[lidx++];
else tmp[i] = arr[ridx++];
}
for(int i = st; i < en; i++) arr[i] = tmp[i]; // tmp에 임시저장해둔 값을 a로 다시 옮김
}
// a[st:en]을 정렬하고 싶다.
void merge_sort(int st, int en){
if(en == st+1) return; // 리스트의 길이가 1인 경우
int mid = (st+en)/2;
merge_sort(st, mid); // arr[st:mid]을 정렬한다.
merge_sort(mid, en); // arr[mid:en]을 정렬한다.
merge(st, en); // arr[st:mid]와 arr[mid:en]을 합친다.
}
int main(void){
ios::sync_with_stdio(0);
cin.tie(0);
merge_sort(0, n);
for(int i = 0; i < n; i++) cout << arr[i] << ' '; // -53 3 12 15 16 22 23 25 46 357
}
else if(arr[lidx] <= arr[ridx]) tmp[i] = arr[lidx++];
else tmp[i] = arr[ridx++];
피벗
을 맨 앞의 원소로 두고, l과 r 포인터를 둔다.피벗
보다 큰 값이 나올때까지 오른쪽으로 이동, 포인터 r은 피벗
보다 작은 값이 나올때까지 왼쪽으로 이동한다.이 작업
을 r이 l보다 작아질떄까지 반복한다.재귀적
으로 r을 기준으로 배열을 반으로 쪼개 위의 작업들을 반복해간다.l과 r의 위치를 옮겨가면서 원소를 교환하는 과정은 배열의 크기에 비례하기 때문에 O(n)
의 시간복잡도를 갖는다. 또한, 반으로 쪼개는 과정의 횟수 약 logN
만큼 반복해야하기 때문에 최종적으로 시간복잡도는 O(NlogN)
의 시간복잡도를 갖는다.
이미 배열이 정렬되어 있는 경우에는, 반으로 쪼개는 과정이 logN
에 비례해 반복하는 것이 아닌 배열의 크기에 비례해 반복해야함으로, O(N^2)
의 시간복잡도를 갖는다.
#include <iostream>
using namespace std;
int n = 8;
int arr[1000001] = {6,-8,-1,12,8,3,7,1};
int tmp[1000001];
void quick_sort(int st, int en)
{
if(st+1>=en) return;
int apos = st+1;
int bpos = en-1;
int pivot = arr[st];
while(1)
{
while(apos <= bpos && arr[apos] <= pivot) apos++;
while(apos <= bpos && arr[bpos] >= pivot) bpos--;
if(apos > bpos) break;
swap(arr[apos],arr[bpos]);
}
swap(arr[st], arr[bpos]);
quick_sort(st, bpos);
quick_sort(bpos+1, en);
}
int main(void)
{
ios::sync_with_stdio(0);
cin.tie(0);
//merge_sort(0,n);
quick_sort(0,n);
for(int i = 0;i<n;i++) cout << arr[i] << ' ';
}
O(NlogN)
의 시간복잡도를 가진다. 따라서 코딩테스트에서 직접 구현해야 하는 경우에는 머지소트를 구현해서 사용하는 것이 좋다.O(n)
의 공간복잡도를 갖는다.O(N^2)
의 시간복잡도를 가지며, 일반적인 경우에는 머지 소트보다 빠른 O(NlogN)
의 복잡도를 가진다. (cache hit rate, 추가적으로 필요한 배열공간이 없기때문)