소팅 알고리즘 중 시간복잡도가 O(nlogn)인 알고리즘들을 정리하려고 한다!
정확도는 leetcode - Sort an Array 제출을 통해 확인했다. (코드들은 모두 오름차순 정렬 코드이다)
합병정렬. Divide & Conquer 방식으로 설계된 알고리즘으로, 입력으로 하나의 배열을 받고 연산 중 두 개의 배열으로 계속 쪼개어 나간 뒤, 합치면서 정렬해 최후에는 하나의 정렬된 배열을 출력
void mergeSort(int[] nums, int s, int e) {
if (s < e) {
int mid = (s + e) / 2;
mergeSort(nums, s, mid);
mergeSort(nums, mid + 1, e);
merge(nums, s, e, mid);
}
}
void merge(int[] nums, int s, int e, int mid) {
int i = s, j = mid + 1;
int[] sortedNums = new int[e - s + 1];
int index = 0;
while ((i <= mid) && (j <= e)) {
if (nums[i] > nums[j]) sortedNums[index++] = nums[j++];
else sortedNums[index++] = nums[i++];
}
while (i <= mid) sortedNums[index++] = nums[i++];
while (j <= e) sortedNums[index++] = nums[j++];
for (i = s; i <= e; i++) {
nums[i] = sortedNums[i - s];
}
printNums(nums);
}
[8,5,6,2,4] 정렬 시
1. [5,8,6,2,4]
2. [5,6,8,2,4]
3. [5,6,8,2,4]
4. [2,4,5,6,8]
퀵정렬. Divide & Conquer 방식으로 설계된 알고리즘으로, 리스트 중 원소하나(피벗)를 골라 피벗을 기준으로 왼쪽 요소들은 피벗보다 작고, 오른쪽 요소들은 큰 수로 배치해 피벗을 기준으로 리스트를 둘로 나눔. 분할된 두 개의 작은 리스트에 대해 재귀적으로 이 과정을 반복하는 정렬 알고리즘
void quickSort(int[] nums, int left, int right) {
if (left < right) {
int pivotIndex = partition(nums, left, right);
quickSort(nums, left, pivotIndex - 1);
quickSort(nums, pivotIndex + 1, right);
}
}
int partition(int[] nums, int left, int right){
int mid = (left + right) / 2;
int pivot = nums[left];
int i = left, j = right;
while(i<j){
while(pivot < nums[j]) j--;
while(i < j && pivot >= nums[i]) i++;
swap(nums, i, j);
}
nums[left] = nums[i];
nums[i] = pivot;
return i;
}
[8,5,6,2,4] 정렬 시
1. [2,5,4,6,8]
2. [4,2,5,6,8]
3. [2,4,5,6,8]
최악의 경우에 O(n²)의 시간복잡도를 가짐 (평균적으로는 Θ(nlogn))
👉 근데 왜 "quick" 정렬일까?
1. 인접한 요소가 아닌 먼 거리에 있는 요소를 교환
2. 캐시 효율(한 번 선택된 피벗은 제외됨)
힙정렬. 최대힙 트리나 최소힙 트리를 구성해 정렬하는 방법
위 과정을 모두 꺼낼 때 까지 반복, 매번 남은 수에 대해 힙을 재구성함
👉 가장 큰 몇 개의 수만 구할 때 더욱 유용