Sorting
- Bubble Sort (거품 정렬)
- Selection Sort (선택 정렬)
- Insertion Sort (삽입 정렬)
- Quick Sort (퀵 정렬)
- Merge Sort (병합 정렬)
- Heap Sort (힙 정렬)
- Radix Sort (기수 정렬)
- Counting Sort (계수 정렬)
O(N²)
O(N²)
O(N²)
O(N²)
void bubbleSort(int[] arr) { // Ascending
int temp = 0;
for (int i = 0; i < arr.length; i++)
for (int j = 0; j < arr.length - i - 1; j++)
if(arr[j] > arr[j + 1])
swap(array, j, j + 1);
}
O(N²)
으로 비효율적O(N²)
O(N²)
O(N²)
O(N²)
void selectionSort(int[] arr) {
int indexMin, temp;
for (int i = 0; i < arr.length - 1; i++) {
indexMin = i;
for (int j = i + 1; j < arr.length; j++)
if (arr[j] < arr[indexMin])
indexMin = j;
swap(arr[indexMin], arr[i]);
}
}
Bubble Sort
에 비해 실제로 교환하는 횟수는 적기 때문에 많은 교환이 일어나야 하는 자료상태에서 비교적 효율적O(N²)
으로 비효율적O(N)
O(N²)
O(N²)
O(N)
의 시간복잡도를 갖게 됨void insertionSort(int[] arr)
{
for(int index = 1 ; index < arr.length ; index++){
int temp = arr[index];
int prev = index - 1;
while( (prev >= 0) && (arr[prev] > temp) ) {
arr[prev+1] = arr[prev];
prev--;
}
arr[prev + 1] = temp;
}
}
}
O(N²)
으로 비효율적O(Nlog₂N)
O(Nlog₂N)
O(N²)
O(Nlog₂N)
가 된다O(N²)
이 된다public void quickSort(int[] array, int left, int right) {
if(left >= right) return;
// 분할
int pivot = partition();
// 피벗은 제외한 2개의 부분 배열을 대상으로 순환 호출
quickSort(array, left, pivot-1); // 정복(Conquer)
quickSort(array, pivot+1, right); // 정복(Conquer)
}
public int partition(int[] array, int left, int right) {
/**
// 최악의 경우, 개선 방법
int mid = (left + right) / 2;
swap(array, left, mid);
*/
int pivot = array[left]; // 가장 왼쪽값을 피벗으로 설정
int i = left, j = right;
while(i < j) {
while(pivot < array[j]) {
j--;
}
while(i < j && pivot >= array[i]){
i++;
}
swap(array, i, j);
}
array[left] = array[i];
array[i] = pivot;
return i;
}
O(Nlog₂N)
를 가지는 다른 정렬 알고리즘과 비교했을 때도 가장 빠름JAVA
에서 Arrays.sort()
내부적으로도 Dual Pivot Quick Sort로 구현되어 있을 정도로 효율적인 알고리즘삽입 정렬(Insertion Sort)
와 Quick Sort
를 합친 것void DualPivotQuickSort(int arr[], int left, int right){
// lp : Left Pivot, rp : Right Pivot
// left > right인 경우는 배열상으로 성립이 되지 않는다.
if(left <= right){
int lp = arr[left]; // 분할의 가장 왼쪽 값
int rp = arr[right]; // 분할의 가장 오른쪽 값
// 양 끝의 값을 비교한다.
if(arr[lp] > arr[rp]){
swap(arr, lp, rp); // lp가 크면 lp와 rp의 위치를 바꾸어준다.
}
int l = left + 1;
int k = left + 1; // left+1의 원소부터 차례로 비교해준다.
int g = right - 1;
}
while(k <= g){ //서로 엇갈릴 때 까지
//arr[k]가 lp보다 작으면, lp보다 작은 (1)영역에 들어간다.
if(arr[k] < lp){
swap(arr, k, l);
l++;
}
// lp보다 큰 (2)영역, (3)영역
else{
//rp보다 큰 (3) 영역
if(arr[k] > rp){
while(arr[g] > rp && k < g){
g--;
}
swap(arr, k, g);
g--;
if(arr[k] < lp){
swap(arr, k, l);
l++;
}
}
}
k++; // k에 대한 비교가 끝
}
l--; g--;
swap(arr, left, l);
swap(arr, right, r);
DualPivotQuickSort(arr, left, l-1);
DualPivotQuickSort(arr, l+1, g-1);
DualPivotQuickSort(arr, g+1, right);
}
정렬(partition)
→ 영역을 쪼갬(quickSort)
쪼갬(mergeSort)
→ 정렬(merge)
two-way
방식이라 함O(Nlog₂N)
O(Nlog₂N)
O(Nlog₂N)
O(N²)
public void mergeSort(int[] array, int left, int right) {
if(left < right) {
int mid = (left + right) / 2;
mergeSort(array, left, mid);
mergeSort(array, mid+1, right);
merge(array, left, mid, right);
}
}
public static void merge(int[] array, int left, int mid, int right) {
int[] L = Arrays.copyOfRange(array, left, mid + 1);
int[] R = Arrays.copyOfRange(array, mid + 1, right + 1);
int i = 0, j = 0, k = left;
int ll = L.length, rl = R.length;
while(i < ll && j < rl) {
if(L[i] <= R[j]) {
array[k] = L[i++];
}
else {
array[k] = R[j++];
}
k++;
}
// remain
while(i < ll) {
array[k++] = L[i++];
}
while(j < rl) {
array[k++] = R[j++];
}
}
O(Nlog₂N)
O(Nlog₂N)
O(Nlog₂N)
log₂N
이고 모든 노드마다 실행되면 전체의 시간복잡도는 Nlog₂N
이 된다public static void heapify(int array[], int n, int i) {
int p = i;
int l = i * 2 + 1;
int r = i * 2 + 2;
if (l < n && array[p] < array[l]) {
p = l;
}
if (r < n && array[p] < array[r]) {
p = r;
}
if (i != p) {
swap(array, p, i);
heapify(array, n, p);
}
}
public static void heapSort(int[] array) {
int n = array.length;
// init, max heap
for (int i = n / 2 - 1; i >= 0; i--) {
heapify(array, n, i);
}
// for extract max element from heap
for (int i = n - 1; i > 0; i--) {
swap(array, 0, i);
heapify(array, i, 0);
}
}
O(N + K)
O(N + K)
O(N + K)
int arr[5]; // [5, 4, 3, 2, 1]
int sorted_arr[5];
// 과정 1 - counting 배열의 사이즈를 최대값 5가 담기도록 크게 잡기
int counting[6]; // 단점 : counting 배열의 사이즈의 범위를 가능한 값의 범위만큼 크게 잡아야 하므로, 비효율적이 됨.
// 과정 2 - counting 배열의 값을 증가해주기.
for (int i = 0; i < arr.length; i++) {
counting[arr[i]]++;
}
// 과정 3 - counting 배열을 누적합으로 만들어주기.
for (int i = 1; i < arr.length; i++) {
counting[i] += counting[i - 1];
}
// 과정 4 - 뒤에서부터 배열을 돌면서, 해당하는 값의 인덱스에 값을 넣어주기.
for (int i = arr.length - 1; i >= 0; i--) {
sorted_arr[counting[arr[i]]] = arr[i];
counting[arr[i]]--;
}
O(N + K)
O(N + K)
O(N + K)
void countSort(int arr[], int n, int exp) {
int buffer[n];
int i, count[10] = {0};
// exp의 자릿수에 해당하는 count 증가
for (i = 0; i < n; i++){
count[(arr[i] / exp) % 10]++;
}
// 누적합 구하기
for (i = 1; i < 10; i++) {
count[i] += count[i - 1];
}
// 일반적인 Counting sort 과정
for (i = n - 1; i >= 0; i--) {
buffer[count[(arr[i]/exp) % 10] - 1] = arr[i];
count[(arr[i] / exp) % 10]--;
}
for (i = 0; i < n; i++){
arr[i] = buffer[i];
}
}
void radixsort(int arr[], int n) {
// 최댓값 자리만큼 돌기
int m = getMax(arr, n);
// 최댓값을 나눴을 때, 0이 나오면 모든 숫자가 exp의 아래
for (int exp = 1; m / exp > 0; exp *= 10) {
countSort(arr, n, exp);
}
}
참조 : https://hanhyx.tistory.com/35, https://gyoogle.dev/blog/algorithm/Bubble%20Sort.html, https://cs-vegemeal.tistory.com/53, https://st-lab.tistory.com/233?category=892973