JUINTINATION

정렬(Sort) 본문

자료구조 및 알고리즘

정렬(Sort)

DEOKJAE KWON 2022. 6. 26. 22:19
반응형

정렬이란?

  • 데이터를 기준에 따라 오름차순이나 내림차순으로 나열하는 것

정렬 알고리즘의 평가 기준

  • 비교 횟수의 많고 적음
  • 이동 횟수의 많고 적음
  • 안정성(동일한 키 값을 갖는 원소들의 상대적인 위치가 정렬 후에도 바뀌지 않으면 안정적인 정렬)

선택정렬(Selection Sort)

아래의 과정을 반복하는 정렬

  1. 주어진 리스트에서 현재 위치 ~ 마지막 위치에서의 최솟값을 찾는다.
  2. 1에서 찾은 최솟값을 현재 위치의 값과 교환한다.
  3. 현재 위치를 마지막 위치가 될 때까지 한 칸씩 뒤로 옮긴다.

비교 횟수

  • (n - 1) + (n - 2) + … + 1 = n(n - 1)/2 = O(𝑛^2)

이동 횟수

  • 3(n - 1)

전체 시간적 복잡도

  • O(n^2)

안정성

  • 안전성을 만족하지 않음
#define swap(x, y, t) (t = x, x = y, y = t)
void selection_sort(int arr[], int n) {
    int tmp;
    for (int i = 0; i < n - 1; i++) {
        int idx = i;
        for (int j = i + 1; j < n; j++) {
            if (arr[j] < arr[idx]) idx = j;
        }
        swap(arr[i], arr[idx], tmp);
    }
}
#define swap(x, y, t) (t = x, x = y, y = t)
int movement, comparison, tmp;
void selection_sort(int arr[], int n) {
    movement = 0, comparison = 0; // 이동횟수와 비교횟수를 구하기 위해 0으로 초기화
    for (int i = 0; i < n - 1; i++) {
        int idx = i;
        for (int j = i + 1; j < n; j++) {
            if (arr[j] < arr[idx]) idx = j;
            comparison++; // 비교횟수 1 증가
        }
        swap(arr[i], arr[idx], tmp);
        movement += 3; // 이동횟수 3 증가(t = x, x = y, y = t)
    }
}

삽입정렬(Insertion Sort)

아래의 과정을 반복하는 정렬

  1. 현재 위치의 값과 앞에 있는 값들을 비교한다.
  2. 앞에 있는 값보다 현재 위치의 값이 작으면 교환한다.
  3. 현재 위치를 마지막 위치가 될 때까지 한 칸씩 뒤로 옮긴다.

비교 횟수

  • 최선의 경우, O(n) : 이미 정렬되어 있는 경우
  • 최악의 경우, O(n^2) : 역순으로 정렬되어 있는 경우

이동 횟수

  • 최선의 경우, n - 1
  • 최악의 경우, O(𝑛^2)

전체 시간적 복잡도

  • 평균의 경우, O(n^2)

안정성

  • 안정성을 만족
void insertion_sort(int arr[], int n) {
    for (int i = 1; i < n; i++) {
        int key = arr[i], j = i - 1;
        while(j >= 0 && target < a[j]) {
            a[j + 1] = a[j];
            j--;
        }
        arr[j + 1] = key;
    }
}
int movement, comparison;
void insertion_sort(int arr[], int n) {
    movement = 0, comparison = 0; // 이동횟수와 비교횟수를 구하기 위해 0으로 초기화
    for (int i = 1; i < n; i++) {
        int key = arr[i], j = i - 1;
        movement++; // 이동횟수 1 증가(key = arr[i])
        do {
            if (arr[j] > key) {
                arr[j + 1] = arr[j];
                movement++; // 이동횟수 1 증가
            }
            comparison++; // 위에서 비교횟수 1 증가
        } while (j-- > 0 && arr[j] > key);
        arr[j + 1] = key;
        movement++; // 이동횟수 1 증가(arr[j + 1] = key)
    }
}

버블정렬(Bubble Sort)

아래의 과정을 반복하는 정렬

  1. 현재 위치의 값과 바로 뒤에 있는 값을 비교한다.
  2. 현재 원소가 다음 원소보다 크면 원소를 교환한다.
  3. 현재 위치를 마지막 위치가 될 때까지 한 칸씩 뒤로 옮긴다.

비교 횟수

  • n(n - 1)/2 = O(n^2)

이동 횟수

  • 최선의 경우, 0
  • 최악의 경우, 3 * 비교 횟수
  • 평균의 경우, O(𝑛^2)

전체 시간적 복잡도

  • 평균의 경우, O(𝑛^2)

안정성

  • 안정성을 만족
#define swap(x, y, t) (t = x, x = y, y = t)
void bubble_sort(int arr[], int n) {
    int tmp;
    for (int i = n - 1; i > 0; i--) {
        for (int j = 0; j < i; j++) {
            if (arr[j] > arr[j + 1]) {
                swap(arr[j], arr[j + 1]);
            }
        }
    }
}
#define swap(x, y, t) (t = x, x = y, y = t)
int movement, comparison, tmp;
void bubble_sort(int arr[], int n) {
    movement = 0, comparison = 0; // 이동횟수와 비교횟수를 구하기 위해 0으로 초기화
    for (int i = n - 1; i > 0; i--) {
        for (int j = 0; j < i; j++) {
            comparison++; // 비교횟수 1 증가
            if (arr[j] > arr[j + 1]) {
                swap(arr[j], arr[j + 1], tmp);
                movement += 3; // 이동횟수 3 증가(t = x, x = y, y = t)
            }
        }
    }
}

합병(병합)정렬(Merge Sort)

리스트를 균등한 크기로 분할하고 분할된 부분 리스트를 정렬, 정렬된 부분 리스트들을 합하여 전체 리스트를 정렬

  1. 분할(Divide): 배열을 같은 크기의 2개의 부분 배열로 분할.
  2. 정복(Conquer): 부분배열을 정렬한다.
    부분배열의 크기가 충분히 작지 않으면 재귀 호출을 이용하여 다시 분할정복기법 적용
  3. 결합(Combine): 정렬된 부분 배열을 하나의 배열에 통합.

비교 횟수

  • 크기 n인 리스트를 정확히 균등 분배하므로 log(n) 개의 패스
  • 각 패스에서 리스트의 모든 레코드 n개를 비교하므로 n번의 비교 연산
  • 총 비교 횟수: n*log(n)

이동 횟수

  • 레코드의 이동이 각 패스에서 2n번 발생하므로 전체 레코드의 이동은 2n*log(n)번 발생
  • 레코드의 크기가 큰 경우에는 매우 큰 시간적 낭비 초래
  • 레코드를 연결 리스트로 구성하여 합병 정렬할 경우, 매우 효율적

전체 시간적 복잡도

  • 최적, 평균, 최악의 경우 큰 차이 없이 O(n*log(n))

안정성

  • 안정성을 만족
int* sorted;
void merge(int arr[], int left, int mid, int right) {
    int i = left, j = mid + 1, k = left;
    while (i <= mid && j <= right) {
        if (arr[i] <= arr[j]) sorted[k++] = arr[i++];
        else sorted[k++] = arr[j++];
    }
    if (i > mid) {
        for (int l = j; l <= right; l++) {
            sorted[k++] = arr[l];
        }
    }
    else {
        for (int l = i; l <= mid; l++) {
            sorted[k++] = arr[l];
        }
    }
    for (int l = left; l <= right; l++) {
        arr[l] = sorted[l];
    }
}
void merge_sort(int arr[], int left, int right) {
    if (left < right) {
        int mid = (left + right) / 2;
        merge_sort(arr, left, mid);
        merge_sort(arr, mid + 1, right);
        merge(arr, left, mid, right);
    }
}
#include <stdio.h>
#define MAX_SIZE 7
int sorted[MAX_SIZE];
void print_array(int arr[], int n) {
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}
void merge(int arr[], int left, int mid, int right) {
    int i = left, j = mid + 1, k = left;
    while (i <= mid && j <= right) {
        if (arr[i] <= arr[j]) sorted[k++] = arr[i++];
        else sorted[k++] = arr[j++];
    }
    if (i > mid) {
        for (int l = j; l <= right; l++) {
            sorted[k++] = arr[l];
        }
    }
    else {
        for (int l = i; l <= mid; l++) {
            sorted[k++] = arr[l];
        }
    }
    for (int l = left; l <= right; l++) {
        arr[l] = sorted[l];
    }
}
int cnt = 1;
void merge_sort(int arr[], int left, int right) {
    if (left < right) {
        int mid = (left + right) / 2;
        merge_sort(arr, left, mid);
        merge_sort(arr, mid + 1, right);
        merge(arr, left, mid, right);
        printf("Pass %d: ", cnt++);
        print_array(arr, MAX_SIZE);
    }
}
main() {
    int arr[MAX_SIZE] = { 4,8,6,3,9,7,2 };
    print_array(arr, MAX_SIZE);
    merge_sort(arr, 0, MAX_SIZE - 1);
    print_array(arr, MAX_SIZE);
}
/*
4 8 6 3 9 7 2 
Pass 1: 4 8 6 3 9 7 2
Pass 2: 4 8 3 6 9 7 2
Pass 3: 3 4 6 8 9 7 2
Pass 4: 3 4 6 8 7 9 2
Pass 5: 3 4 6 8 2 7 9
Pass 6: 2 3 4 6 7 8 9
2 3 4 6 7 8 9
*/

퀵정렬(Quick Sort)

리스트를 2개의 부분 리스트로 비균등 분할하고, 각각의 부분 리스트를 다시 퀵정렬함(재귀 호출)

  1. 리스트에서 하나의 원소인 피벗을 고른다.
  2. 피벗의 왼쪽에서부터는 피벗보다 큰 값을 찾고, 오른쪽에서부터는 피벗보다 작은 값을 찾는다.
  3. 2에서 찾은 두 원소의 위치를 바꾼다.
  4. 왼쪽과 오른쪽의 탐색 위치가 엇갈리지 않을 때까지 2번부터의 과정을 반복한다.
  5. 엇갈린 위치를 기준으로 분할된 두 개의 부분 리스트에 대해 크기가 0이나 1이 될 때까지 재귀적으로 위의 과정을 반복한 뒤 인접한 부분 리스트끼리 합친다.

비교 횟수

  • 최선의 경우(거의 균등한 리스트로 분할되는 경우), nlog(n)
  • 최악의 경우(극도로 불균등한 리스트로 분할되는 경우), n^2

이동 횟수

  • 비교 횟수에 비해 적으므로 무시 가능

전체 시간적 복잡도

  • 최악의 경우, O(n^2)
  • 최적, 평균의 경우, O(n*log(n))

안정성

  • 안전성을 만족하지 않음
#define swap(x, y, t) (t = x, x = y, y = t)
int partition(int arr[], int left, int right) {
    int tmp, pivot = arr[left];
    int low = left, high = right + 1;
    do {
        do {
            low++;
        } while (low <= right && arr[low] < pivot);
        do {
            high--;
        } while (high >= left && arr[high] > pivot);
        if (low < high) swap(arr[low], arr[high], tmp);
    } while (low < high);
    swap(arr[left], arr[high], tmp);
    return high;
}
void quick_sort(int arr[], int left, int right) {
    if (left < right) {
        int q = partition(arr, left, right);
        quick_sort(arr, left, q - 1);
        quick_sort(arr, q + 1, right);
    }
}
#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 7
#define swap(x, y, t) (t = x, x = y, y = t)
void print_array(int arr[], int n) {
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}
int partition(int arr[], int left, int right) {
    int tmp, pivot = arr[left];
    int low = left, high = right + 1;
    do {
        do {
            low++;
        } while (low <= right && arr[low] < pivot);
        do {
            high--;
        } while (high >= left && arr[high] > pivot);
        if (low < high) {
            swap(arr[low], arr[high], tmp);
        }
    } while (low < high);
    swap(arr[left], arr[high], tmp);
    return high;
}
int cnt = 1;
void quick_sort(int arr[], int left, int right) {
    if (left < right) {
        int q = partition(arr, left, right);
        printf("pass %d: ", cnt++);
        print_array(arr, MAX_SIZE);
        quick_sort(arr, left, q - 1);
        quick_sort(arr, q + 1, right);
    }
}
main() {
    int arr[MAX_SIZE] = { 4,8,6,3,9,7,2 };
    print_array(arr, MAX_SIZE);
    quick_sort(arr, 0, MAX_SIZE - 1);
    print_array(arr, MAX_SIZE);
}
/*
4 8 6 3 9 7 2 
pass 1: 3 2 4 6 9 7 8
pass 2: 2 3 4 6 9 7 8
pass 3: 2 3 4 6 9 7 8
pass 4: 2 3 4 6 8 7 9
pass 5: 2 3 4 6 7 8 9
2 3 4 6 7 8 9
*/

중간값(medium)을 피벗으로 선택하면 불균등 분할 완화 가능

#define swap(x, y, t) (t = x, x = y, y = t)
void quickSort(int arr[], int left, int right) {
    int i = left, j = right, pivot = arr[(left + right) / 2], tmp;
    do {
        while (arr[i] < pivot) i++;
        while (arr[j] > pivot) j--;
        if (i <= j) {
            swap(arr[i], arr[j], tmp);
            i++;
            j--;
        }
    } while (i <= j);
    if (left < j) quickSort(arr, left, j);
    if (i < right) quickSort(arr, i, right);
}

정렬 알고리즘 비교

728x90

'자료구조 및 알고리즘' 카테고리의 다른 글

깊이우선탐색(DFS)과 너비우선탐색(BFS)  (2) 2023.01.15
동적 계획법(dynamic programming)  (0) 2022.07.16
덱(Deque)  (0) 2022.07.01
큐(Queue)  (0) 2022.07.01
스택(Stack)  (0) 2022.07.01
Comments