가장 작은 데이터를 선택해 맨 앞에 있는 데이터와 바꾸고, 그 다음 작은 데이터를 선택해 앞에서 두 번째 데이터와 바꾸는 과정을 반복하는 방법
주어진 리스트 중에 최솟값을 찾는다.
그 값을 맨 앞에 위치한 값과 교체한다(패스(pass)).
맨 처음 위치를 뺀 나머지 리스트를 같은 방법으로 교체한다.
void selection_sort(int list[], int n){
int i, j, least,tmp;
for(i=0;i<n-1;i++){
least = i;
for(j=i+1;j<n;j++){
if(list[j]<list[least]){
least = j;
}
SWAP(list[i],list[j],tmp);
}
}
}
array = [7,5,9,0,3,1,6,2,4,8]
for i in range(0,len(array)-1):
min = i;
for j in range(i+1,len(array)):
if array[j] < array[min]:
min = j;
array[i], array[min] = array[min], array[i];
print(array)
비교하는 것이 상수 시간에 이루어진다는 가정 아래, n개의 주어진 리스트를 이와 같은 방법으로 정렬하는 데에는 O(n^2) 만큼의 시간이 걸린다. 이는 정렬해야하는 데이터 개수가 100배 늘어나면 이론적으로 수행시간은 10,000배 늘어나는 것이다.
삽입정렬(insertion sort)
자료 배열의 모든 요소를 앞에서부터 차례대로 이미 정렬된 배열 부분과 비교하여, 자신의 위치를 찾아 삽입함으로써 정렬을 완성하는 알고리즘이다.
삽입 정렬은 필요할 때만 위치를 바꾸므로 데이터가 거의 정렬되어 있을 때 훨씬 효율적이다.
void insert_sort(int arr[],int n){
int i, key, j;
for (i = 1; i < n; i++)
{
key = arr[i];
j = i-1;
while (j >= 0 && arr[j] > key)
{
arr[j+1] = arr[j];
j = j-1;
}
arr[j+1] = key;
}
}
def insert_sort(array):
# print(array)
for i in range(1,len(array)):
for j in range(i,0,-1):
if array[j] < array[j-1]:
array[j], array[j-1] = array[j-1], array[j]
else:
break
# print(array)
return array
복잡도
최선의 경우(이미 정렬) : O(n)
최악의 경우(역순으로 정렬된 경우) : O(n^2)
보통 삽입 정렬이 퀵 정렬보다 비효율 적이나, 이미 정렬되어있는 경우에는 퀵정렬보다 더 강력하다. 따라서 거의 정렬되어 있는 상태라면, 삽입 정렬을 이용하는 것이 더 좋다.
버블정렬(bubble sort)
인점한 2개의 레코드를 비교하여 순서대로 되어 있지 않으면 서로 교환한다.
void bubble_sort(int list[], int n){
int i,j,tmp;
for(i=n-1;i>0;i--){
for(j=0;j<i;j++){
if(list[i]<list[j])SWAP(list[i], list[j], tmp);
}
}
}
복잡도(최상, 평균, 최악) : O(n^2)
합병 정렬(merge_sort)
리스트를 두 개의 균등한 크기로 분할하고 분할된 부분리시트를 정렬한다.
정렬된 두개의 부분 리스트를 합하여 전체 리스트를 정렬한다.
void merge(int array[], int left, int mid, int right)
{
int i, j, k, m;
i = left;
j = mid + 1;
k = left; //결과 배열의 인덱스
int tempArray[MAX];
//left부터 mid 까지의 블록과 mid+1부터 right까지의 블록을 서로 비교하는 부분
while (i <= mid && j <= right) {
if (array[i] < array[j]){ //left index 값이 right index 값보다 작으면 left index 값을 결과 result에 복사
tempArray[k] = array[i];
i++;
}else{ //아니라면 right index 값을 결과 result에 복사
tempArray[k] = array[j];
j++;
}
k++;
}
// left 블록의 값은 다 처리되었는데 right 블록의 index가 아직 남아있을 경우
// right index를 순차적으로 결과 result에 복사
if (i > mid){
for (m = j; m <= right; m++){
tempArray[k] = array[m];
k++;
}
} else { // left 블록의 index가 아직 남아있을 경우 left index를 순차적으로 결과 result에 복사
for (m = i; m <= mid; m++){
tempArray[k] = array[m];
k++;
}
}
for(m = left; m <= right; m++){
array[m] = tempArray[m];
}
}
void merge_sort(int array[], int left, int right)
{
int mid;
// 분할이 다 되지 않았을 경우 if 문 실행
if(left < right){
mid = (left + right)/2;
merge_sort(array, left, mid); //왼쪽 블록 분할
merge_sort(array, mid+1, right); //오른쪽 블록 분할
merge(array, left, mid, right); //분할된 블록 병합
}
}
복잡도 : O(n*log(n))
기수정렬(Radix Sort)
기수 정렬은 정수의 자리수를 기준으로 낮은 자리수부터 비교해 정렬하는 알고리즘입니다.
예를 들어 3자리 수라면 1의자리, 10의자리 , 100의 자리 숫자를 순서대로 비교해서 정렬하는 방법입니다.
void radix_sort(int a[])
{
int i, b[MAX], m=0, exp=1;
// m에 최대값을 저장
for( i=0 ; i<MAX ; i++ )
{
if( a[i] > m )
m = a[i];
}
// m의 자리수보다 exp가 커지면 종료
while( m/exp > 0 )
{
int bucket[10] = {0}; //수별로 비교해서 임시로 저장해둘 공간
for( i=0 ; i<MAX ; i++ )
bucket[a[i]/exp%10]++;
for( i=1 ; i<10 ; i++ )
bucket[i] += bucket[i-1];
for( i=MAX-1 ; i>=0 ; i-- )
b[--bucket[a[i]/exp%10]] = a[i];
for( i=0 ; i<MAX ; i++ )
a[i] = b[i];
exp *= 10; //자리수 비교가 끝나면 다음 자리수!
}
}
def counting_sort(arr, digit):
n = len(arr)
# 배열의 크기에 맞는 output 배열을 생성하고 10개의 0을 가진 count란 배열을 생성한다.
output = [0] * n
count = [0] * 10
# 각 자리수에 맞게 count를 증가시켜준다.
for i in range(0, n):
index = int(arr[i] / digit)
count[(index) % 10] += 1
# count 배열에 앞의 수를 차례대로 더해 각 자릿수를 정해준다.
# ex) 123,134,151,121
# before : [ 0, 2, 0, 1, 1 ]
# after : [ 0, 2, 2, 3, 4 ]
for i in range(1, 10):
count[i] += count[i - 1]
# 각 자릿수 별로 output 배열에 차례대로 정렬한다.
# ex) 1의 자리 예제
# [ 0, 121, 0, 0]
# [ 151, 121, 0, 0]
# [ 151, 121, 0, 134]
# [ 151, 121, 123, 134]
i = n - 1
while i >= 0:
index = int(arr[i] / digit)
output[count[(index) % 10] - 1] = arr[i]
count[(index) % 10] -= 1
i -= 1
# arr에 output을 할당한다.
return output
# Method to do Radix Sort
def radix_sort(arr):
# arr 배열중에서 최대값을 가져와 자릿수를 파악한다.
# ex) 최대값이 9833 이면 1000까지
maximum = max(arr)
# 자릿수마다 countingSorting을 시작한다.
digit = 1
while int(maximum / digit) > 0:
arr = counting_sort(arr, digit)
digit *= 10
return arr
복잡도 : O(dn) d는 자릿수
퀵정렬(Quick sort)
pivot(기준값) 정하기
pivot보다 작은 원소들은 왼쪽, 큰 원소는 오른쪽
pivot을 기준으로 왼쪽 배열과 오른쪽 배열을 새로운 배열로 정하고 각 배열구간에서 1번과정 재귀적 반복
일반적으로 처음 또는 마지막 원소를 pivot으로 잡는다.
int partition (int arr[], int low, int high)
{
int pivot = arr[high];
int i = (low - 1);
for (int j = low; j <= high- 1; j++)
{
if (arr[j] <= pivot)
{
i++;
swap(&arr[i], &arr[j]);
}
}
swap(&arr[i + 1], &arr[high]);
return (i + 1);
}
void quickSort(int arr[], int low, int high)
{
if (low < high)
{
//pi = partition index
int pi = partition(arr, low, high);
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
#include <algorithm>
bool compare(int a, int b){
// 오름차순
// return a<b;
// 내림차순
return a>b;
}
bool comparep(POINT a, POINT b){
if(a.x == b.x ) return a.y < b.y;
else return a.x < b.x;
}
//처음부터 n-1번째까지 원소를 compare함수의 정의대로 정렬
//퀵정렬 기반으로 정렬한다.
//std::sort(정렬할 자료의 시작 주소, 정렬할 자료의 마지막 주소,[비교함수 주소])
std::sort(S,S+n,compare);
def quick_sort(array):
if len(array) <= 1:
return
pivot = array[0]
temp = array[1:]
left = [i for i in temp if i < pivot]
right = [i for i in temp if i > pivot]
return quick_sort(left) + [pivot] + quick_sort(right)
시간 복잡도
평균적 : O(nlog(n))
최악의 경우 : O(N^2)
데이터 수
N^2(선택정렬, 삽입 정렬)
NlogN(퀵 정렬)
1,000
약 1,000,000
약 10,000
1,000,000
약 1,000,000,000,000
약 20,000,000
다만 퀵정렬은 평균적으로 시간복잡도가 O(NlogN)이지만 최악의 경우(이미 정렬되어 있는 경우) O(N^2)인 것을 주의해야한다.
void heap_sort(int arr[], int n){
int i;
Heap heap;
init(&heap);
for(i=0;i<n;i++){
insert_max(&heap, arr[i]);
}
for(i=n-1;i>=0;i--){
arr[i]=delete_max(&heap);
}
}
복잡도
힙 삭제 시간 O(logn)*n = O(nlogn)
계수 정렬(Count Sort)
계수 정렬은 특정한 조건이 부합할 때만 사용할 수 있지만 매우 빠른 정렬 알고리즘이다.
모든 데이터가 양의 정수인 상황을 가정해볼 것이다. 데이터의 개수가 N, 데이터 중 최대값이 K일 때, 계수 정렬은 최악의 경우에도 수행시간 O(N+K)를 보장한다.
계수 정렬은 데이터의 크기 범위가 제한되어 정수 형태로 표현할 수 있을 때만 사용할 수 있다. 만약 데이터의 값이 무한한 범위를 가질 수 있는 실수형 데이터가 주어지는 경우에는 계수 정렬은 사용하기 어렵다. 일반적으로 가장 큰 데이터와 가장 작은 데이터의 차이가 1,000,000을 넘지 않을 때 효과적으로 사용할 수 있다.
계수 정렬은 위와 모든 범위를 담을 수 있는 크기의 리스트를 선언해야하기 때문에, 크기에 제한이 있다.
def count_sort(array):
count = [0] * (max(array)+1)
for i in array:
count[i]+=1
for j in range(len(count)):
for x in range(count[j]):
print(j, end=' ')
시간 복잡도 : O(N+K), 최대값 크기 K, 데이터수 N
파이썬 3.7 정렬 알고리즘 비교
데이터 수(N)
선택 정렬
퀵 정렬
기본 정렬 라이브러리
100
0.0123
0.00156
0.00000753
1000
0.354
0.00343
0.0000365
10000
15.475
0.0312
0.000248
선택 정렬은 기본 정렬 라이브러리를 포함해 다른 알고리즘과 비교했을 때 매우 비효율 적이지만, 특정한 리스트에서 가장 작은 데이터를 찾을때는 유용하게 사용된다.
sorted()
파이썬 기본 정렬 라이브러리에서 제공하는 sorted()는 퀵정렬 과 동작 방식이 비슷한 병합 정렬을 기반(병합 정렬 + 삽입정렬)으로 만들어졌다. 병합 정렬은 퀵정렬보다 일반적으로 느리지만, 최악의 경우에도 O(NlogN) 시간 복잡도를 보장한다.