[Algorithm]기초적인 정렬
📚정렬
📄기본 개념
정렬은 주어진 데이터를 값의 크기 순서에 따라 재배치하는 것이다.
정렬 수행 시점에 데이터가 어디에 저장되어 있는지에 따라 정렬을 내부 정렬과 외부 정렬로 구분할 수 있다.
📄내부 정렬
전체 데이터를 주기억장치에 저장한 후 정렬을 수행
- 비교 기반 알고리듬
두 데이터의 값 전체를 직접적으로 비교하여 정렬 수행. 값의 비교 횟수를 알고리듬의 수행시간으로 간주.
(선택정렬, 버블 정렬, 삽입 정렬, 셀 정렬) -> O(n2)
(큌 정렬, 합병 정렬, 힙 정렬) -> O(n log n)
- 데이터 분포 기반 알고리듬
데이터의 분포에 기반한 정보를 바탕으로 정렬 수행. 데이터의 이동 횟수를수행시간으로 간주.
(계수 정렬, 기수 정렬, 버킷 정렬) -> O(n)
📄외부 정렬
모든 데이터를 주기억장치에 저장할 수 없을 때 데이터를 보조기억장치에 저장해 두고 그 중 일부 데이터만을 반복적으로 주기억장치로 옮겨와서 정렬을 수행
📄안정적 정렬
같은 값을 갖는 데이터가 여러 개 있을 때 정렬 전의 상대적 위치가 정렬 후에도 그대로 유지되는 것이 보장되는 정렬.
📄제자리 정렬
입력 배열 이외에 별도로 필요한 저장 공간이 상수 개를 넘지 않는 정렬. 입력 크기가 증가해도 추가적인 저장 공간이 증가하지 않는 정렬이다.
📚기초적인 정렬
모든 정렬 알고리듬은 배열 A[0…n-1]에 저장된 n개의 양의 정수에 대해 오름차순으로 정렬한다고 가정한다.
📄선택 정렬
입력 배열에서 가장 작은 값부터 순서대로 선택해서 나열하는 방식의 정렬.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include<stdio.h>
void SelectionSort(int array[], int size)
{
for (int i = 0; i < size - 1; i++) // n - 1번 반복
{
int min = i; // 미정렬 부분의 첫 번째 원소를 최소값으로 지정
// A[i ... n-1]에서 최소값 찾기
for (int j = i + 1; j < size; j++)
{
if (array[min] > array[j])
{
min = j;
}
}
// 미정렬 부분의 첫 번째 원소와 최소값 위치 교환
int temp = array[i];
array[i] = array[min];
array[min] = temp;
}
}
void Print(int array[], int size)
{
for (int i = 0; i < size; i++)
{
printf("%d ", array[i]);
}
}
int main()
{
int array[] = { 60, 20, 70, 10, 80, 30, 50, 40 };
int arraySize = sizeof(array) / sizeof(int);
SelectionSort(array, arraySize);
Print(array, arraySize);
return 0;
}
- 성능과 특징
- 시간 복잡도: O(n2)
- 입력 데이터의 순서에 민감하지 않다.
- 제자리 정렬 알고리듬이다.
- 안정적이지 않은 정렬 알고리듬이다.
📄버블 정렬
모든 인접한 두 데이터를 차례대로 비교해서 왼쪽이 더 큰 경우에 오른쪽과 자리를 바꾸는 과정을 반복하는 정렬.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void BubbleSort(int array[], int size)
{
for (int i = 0; i < size - 1; i++) // n - 1번 반복
{
for (int j = 0; j < size - 1; ++j) // 왼쪽에서 오른쪽으로 진행
{
if (array[j] > array[j + 1]) // 왼쪽이 오른쪽 보다 큰 경우
{
// 왼쪽과 오른쪽 자리 바꿈
int temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
}
}
}
- 성능과 특징
- 시간 복잡도: O(n2)
- 안정적인 정렬 알고리듬이다.
- 제자리 정렬 알고리듬이다.
- 선택 정렬에 비해 데이터 교환이 많이 발생 -> 선택 정렬보다 비효율적
- 개선의 여지가 있다.
📄개선된 버블 정렬
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include<stdbool.h>
void AdvancedBubbleSort(int array[], int size)
{
for (int i = 0; i < size - 1; i++)
{
bool bIsSorted = true; // 이미 정렬된 상태라고 가정
for (int j = 0; j < size - 1 - i; j++) // 왼쪽에서 오른쪽으로 진행
{
if (array[j] > array[j + 1])
{
int temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
// 자리바꿈이 발생하면 정렬되지 않은 상태
// 계속 다음 단계 처리 필요
bIsSorted = false;
}
}
// 이미 정렬된 상태라서 이후 단계 불필요
if (bIsSorted)
{
break;
}
}
}
- 성능과 특징
- 시간 복잡도: O(n2)
- 입력 데이터의 상태에 따라 성능 달라짐. 최악의 경우: O(n2), 최선의 경우: O(n)
📄삽입 정렬
주어진 데이터를 하나씩 뽑고 이미 나열된 데이터가 항상 정렬된 상태를 유지하도록 뽑은 데이터를 바른 위치에 삽입하는 방식의 정렬.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void InsertionSort(int array[], int size)
{
int i;
int j;
int val;
for (i = 1; i < size; i++) // 1, ..., n - 1 까지 반복
{
val = array[i]; // 미정렬 부분 array[i, ..., n - 1]의 첫 번째 데이터 선택
for (j = i; j > 0 && array[j - 1] > val; j--) // 정렬 부분에서 삽입할 위치 찾기
{
array[j] = array[j - 1]; // 정렬 부분의 array[j - 1]이 크면 뒤로 한 칸 이동
}
array[j] = val; // 찾은 위치에 선택된 데이터 삽입
}
}
- 성능과 특징
- 시간 복잡도: O(n2)
- 안정적인 정렬 알고리듬이다.
- 제자리 정렬 알고리듬이다.
- 입력 데이터의 순서에 민감하다.
📄셸 정렬
도널드 셸이 삽입 정령의 단점을 보완하기 위해 고안한 정렬 알고리듬이다.
삽입 정렬은 현재 삽입하고자 하는 데이터가 삽입될 제 위치에서 많이 벗어나 있어도 한 번에 한 자리씩만 이동해서 찾아가야 하는 단점이 있다.
셸 정렬은 멀리 떨어진 데이터와 비교, 교환으로 한 번에 이동할 수 있는 거리를 늘려서 처리 속도를 향상하기 위한 정렬이다.
처음에는 멀리 떨어진 두 데이터를 비교해서 필요시 위치를 교환하고 점차 가까운 위치의 데이터를 비교, 교환한뒤, 맨 마지막에 인접한 데이터를 비교, 교환하는 방식이다.
입력 배열을 부분배열로 나눠서 삽입 정렬을 수행하는 과정을 부분배열의 크기와 개수를 변화시켜 가면서 반복하는 방식으로도 말할 수 있다.
-
부분배열의 개수를 정하는 방법
- 양수로 이루어진 임의의 순열 h1, …, hk-1, hk을 사용
- 1 < i < k인 i에 대해 hi-1 < hi < hi+1를 항상 만족
- 임의의 i, j에 대해 i < j이면 hj는 hi의 배수가 되지 않음
- 항상 h1 = 1이 되어야 한다.
- 순열의 역순으로 적용(hk -> hk-1 -> … -> h1)
- 양수로 이루어진 임의의 순열 h1, …, hk-1, hk을 사용
첫 번째 단계 -> 전체 배열을 hk개의 부분배열로 나눠 처리(삽입 정렬 수행)
두 번째 단계 -> 부분 정렬된 전체 배열을 hk-1개의 부분배열로 나눠 처리
…
마지막 단계 -> 부분 정렬된 전체 배열을 h1 = 1개의 부분배열, 즉 전체에 대해 처리
- 순열값 hk의 의미
- 부분배열의 개수
- 전체 배열을 hk개의 부분배열로 나누어 각 부분배열에 대해 삽입 정렬을 적용
- 각 부분배열 내에서 이웃한 데이터 간의 거리
- i번째 부분배열을 구성하는 데이터 -> 배열 인덱스를 hk로 나눴을 때 나머지가 i - 1인 데이터
- 각 부분배열은 전체 배열에서 hk씩 떨어진 데이터로 구성
- 부분배열의 개수
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void ShellSort(int array[], int size)
{
int i;
int j;
int gap;
int val;
for (gap = size / 2; gap >= 1; gap /= 2)
{
for (i = gap; i < size; i++)
{
val = array[i];
for (j = i; j >= gap && array[j - gap] > val; j -= gap)
{
array[j] = array[j - gap];
}
array[j] = val;
}
}
}
// 하나의 입력 배열을 물리적(메모리 할당)으로 여러 개의 부분배열로 분할하지 않는다.
// 각 부분배열을 번갈아 가면서 미정렬 부분의 첫 번째 데이터를 뽑은 후
// gap만큼씩 떨어진 정렬 부분에서 제자리를 찾아서 삽입하는 방식이다.
- 성능과 특징
- 사용하는 순열(부분배열의 개수)에 따라 성능이 달라짐
- 가장 좋은 간격(순열)을 찾는 것은 미해결 과제
- 시간복잡도: 최선 O(n log n) ~ 최악 O(n2)
- 제자리 정렬 알고리듬이다.
- 안정적이지 않은 정렬 알고리듬이다.
Leave a comment