레코드(record)
라고 불린다.필드(field)
라고 하는 단위로 나누어진다.키(key)
라고 한다.정렬 알고리즘은 크게 2가지로 나누어진다.
자료의 개수가 적다면 단순한 정렬 방법을 사용하는 것도 괜찮지만 자료의 개수가 일정 개수를 넘어가면 반드시 효율적인 알고리즘을 사용
선택 정렬은 오른쪽 리스트가 공백 상태가 될 때까지 이 과정을 되풀이하는 방법
메모리를 절약하기 위해 배열을 사용하지 않고 제자리 정렬
사용
public class Selection_Sort {
private static void swap(int[] list, int least, int i) {
int temp = list[least];
list[least] = list[i];
list[i] = temp;
}
public static void selection_sort(int[] list) {
selection_sort(list, list.length);
}
private static void selection_sort(int[] list, int size) {
for(int i = 0; i < size - 1; i++) {
int least = i;
// 최솟값을 갖고있는 인덱스 찾기
for(int j = i + 1; j < size; j++) {
if(list[j] < list[least]) least = j;
}
// i번째 값과 찾은 최솟값을 서로 교환
swap(list, least, i);
}
}
}
외부 루프는 n-1번 실행, 내부 루프는 (n-1)-i번 반복
시간 복잡도 : O(n2)
안정성
문제if(i!=least)
swap(list, least, i);
삽입 정렬은 정렬되어 있는 리스트에 새로운 레코드를 적절한 위치에 삽입하는 과정을 반복
public class Insertion_Sort {
public static void insertion_sort(int[] list) {
selection_sort(list, list.length);
}
private static void insertion_sort(int[] list, int size){
int key, i, j;
for(i=1; i<size; i++){
key = list[i];
for(j=i-1; j>=0 && list[j] > key; j--)
list[j+1] = list[j];
list[j+1] = key;
}
}
}
이미 정렬되어 있는 경우 가장 빠름
외부 루프는 n-1번 실행, 각 단계에서 1번의 비교와 2번의 이동
총 비교 횟수는 n-1번, 총 이동 횟수는 2(n-1)번
시간 복잡도
- 최악의 경우(입력 자료가 역순) : O(n2)
- 평균의 경우 : O(n)
버블 정렬은 인접한 2개의 레코드를 비교하여 크기가 순서대로 되어 있지 않으면 서로 비교하는 비교-교환
과정을 리스트의 왼쪽 끝에서 오른쪽 끝까지 진행
public class Bubble_Sort {
private static void swap(int[] list, int least, int i) {
int temp = list[least];
list[least] = list[i];
list[i] = temp;
}
public static void bubble_sort(int[] list) {
selection_sort(list, list.length);
}
private static void bubble_sort(int[] list, int size) {
for(int i=size-1; i>0; size--){
for(int j=0; j<i; j++)
if(list[j] > list[j+1]
swap(list, j, j+1);
}
}
}
버블 정렬의 비교 횟수는 최선, 평균, 최악의 어떠한 경우에도 항상 일정하고 이동 횟수는 자료가 역순일 경우 최악, 정렬되어 있는 경우 최선이다.
시간 복잡도 : O(n2)
public class Shell_Sort {
private static void insertion_sort(int[] list, int first, int last, int gap){
int key;
for(int i=first+gap; i<=last; i+=gap){
key = list[i];
for(int j=i-gap; j>=first && list[j] > key; j-=gap)
list[j+gap] = list[j];
list[j+gap] = key;
}
}
private static void shell_sort(int[] list, int size){
for(int gap = size/2; gap>0; gap/=2){
if(gap%2 == 0) gap++;
for(int i==0; i<gap; i++)
insertion_sort(list, i, size-1, gap)
}
}
}
삽입 정렬에 비한 장점
시간 복잡도
- 최악의 경우 : O(n2)
- 평균의 경우 : O(n1.5)
합병 정렬은 다음의 단계들로 이루어진다.
분할 정복
기법을 적용public class Merge_Sort {
private static void merge(int[] list, int left, int mid, int right){
// i는 정렬된 왼쪽 리스트에 대한 인덱스
// j는 정렬된 오른쪽 리스트에 대한 인덱스
// k는 정렬된 리스트에 대한 인덱스
int i = left;
int j = right;
int k = left;
int[] sorted = new int[list.length];
// 분할 정렬된 list의 합병
while(i<=mid && j<=right){
if(list[i] <= list[j])
sorted[k++] = list[i++];
else
sorted[k++] = list[j++];
}
if(i>mid) // 남아 있는 레코드의 일괄 복사
for(int l=j; l<=right; l++)
sorted[k++] = list[l];
else // 남아 있는 레코드의 일괄 복사
for(int l=i; l<=mid; l++)
sorted[k++] = list[l];
// 배열 sorted[]의 리스트를 배열 list[]로 재복사
for(int l=left; l<=right; l++)
list[l] = sorted[l];
}
private static void merge_sort(int[] list, int left, int right){
int mid;
if(left<right){
mid = (left+right)/2; // 리스트의 균등 분할
merge_sort(list, left, mid); // 부분 리스트 정렬
merge_sort(list, mid+1, right); // 부분 리스트 정렬
merge(list, left, mid, right); // 합병
}
}
}
합병 정렬에서 실제로 정렬이 이루어지는 시점은 2개의 리스트를 합병
하는 단계이다.
합병 자체는 어렵지 않으나 추가적인 리스트를 필요로 한다.
시간 복잡도
- 최악, 평균, 최선 : O(nlog(n))
분할-정복법
에 근거비균등
하게 분할public class Quick_Sort {
private static void swap(int[] list, int least, int i) {
int temp = list[least];
list[least] = list[i];
list[i] = temp;
}
private static int partition(int[] list, int left, int right){
int pivot, temp;
int low, high;
low = left;
hight = right+1;
pivot = list[left];
do {
do
low++;
while(list[low]<pivot);
do
high--;
while(list[high]>pivot);
if(low<high) swap(list, low, high);
} while(low<high);
swap(list, left, high);
return high;
}
private static void quick_sort(int[] list, int left, int right){
if(left<right){
int q = partition(list, left, right);
quick_sort(list, left, q-1);
quick_sort(list, q+1, right);
}
}
}
시간 복잡도
- 최악의 경우 : O(n2)
- 평균의 경우 : O(nlog(n))
힙을 이용하여 데이터를 차례대로 최대 힙에 추가 후 한 번에 하나씩 힙에서 꺼내 배열의 뒤쪽부터 저장
public class Heap_Sort {
private static void heap_sort(int[] list){
PriorityQueue<Integer> heap = new PriorityQueue<Integer>();
// 힙에 배열의 원소 추가
for(int i = 0; i < list.length; i++) {
heap.add(list[i]);
}
// 힙에서 추출하여 정렬
for(int i = 0; i < list.length; i++) {
list[i] = heap.poll();
}
}
}
힙트리의 전체 높이가 거의 log(n)이므로 하나의 요소를 힙에서 삽입하거나 삭제할 때 힙을 재정비하는 시간이 log(n)만큼 소요, 요소의 개수는 n개
시간 복잡도
- 평균의 경우 : O(nlog(n))
public class Radix_Sort {
private static int BUCKETS = 10;
private static int DIGITS = 4;
private static void radix_sort(int[] list, int size){
int factor = 1;
// bucket 초기화
Queue<Integer>[] bucket = new LinkedList[BUCKETS];
for (int i = 0; i < BUCKETS; i++) {
bucket[i] = new LinkedList<>();
}
// 데이터들을 자리수에 따라 버킷에 삽입
for (int d = 0; d < DIGITS; d++) {
for (int i = 0; i < n; i++) {
bucket[(list[i] / factor) % 10].add(list[i]);
}
for (int b = i = 0; b < BUCKETS; b++) {
while (!bucket[b].isEmpty()) {
list[i++] = bucket[b].poll();
}
}
factor *= 10;
}
}
}
시간 복잡도
- 평균의 경우 : O(d*n)
알고리즘 | 최선 | 평균 | 최악 |
---|---|---|---|
선택 정렬 | O(n2) | O(n2) | O(n2) |
삽입 정렬 | O(n) | O(n2) | O(n2) |
버블 정렬 | O(n2) | O(n2) | O(n2) |
쉘 정렬 | O(n) | O(n1.5) | O(n1.5) |
합병 정렬 | O(nlog(n)) | O(nlog(n)) | O(nlog(n)) |
퀵 정렬 | O(nlog(n)) | O(nlog(n)) | O(n2) |
힙 정렬 | O(nlog(n)) | O(nlog(n)) | O(nlog(n)) |
기수 정렬 | O(d*n) | O(d*n) | O(d*n) |