티스토리 뷰
데이터 정렬(Sort)과 탐색(Search)은 컴퓨터 시스템의 가장 기본적인 기능으로 데이터베이스, 웹 검색, 단순 입력창에 이르기까지 다양한 영역에서 사용하는 것이므로 꼭 알아두어야 할 개념입니다. 퍼스널 컴퓨터가 등장하기 이전 부터 데이터 정렬은 여러 형태로 발전되어 왔는데 예전에는 메모리가 크지 않은 환경이었으므로 이런 제약적인 환경에서도 활용할 수 있는 알고리즘부터 최대한 검색 시간을 줄이기 위하여 검색을 감안하여 정렬된 데이터로 보관하는 기법까지 다양한 알고리즘이 존재합니다. 이런 알고리즘의 복잡도는 O(빅오 Big-Oh)로 표시합니다.
■ 버블 정렬(Bubble sort)
void print_data(int dat[], int size) { int i; printf("\n"); for (i = 0; i < size; i++) { printf("%3d", dat[i]); } } main() { int loop, i, tmp; int data[] = { 10, 3, 34, 5, 26, 4, 1, 6, 9, 19 }; int datasize = sizeof(data) / sizeof(int); for (loop = datasize-1; loop>0; loop--) { print_data(data, datasize); for (i = 0; i < loop; i++) { if (data[i] > data[i+1]) { tmp = data[i]; data[i] = data[i+1]; data[i+1] = tmp; } } } }
위의 결과를 보면 일곱번째 부터는 자리 바꿈이 일어나지 않고 있으므로 이런 상황을 감안하여 플래그를 두어 불필요한 비교 과정을 생략하면 좀더 효과적인 로직이 될 수 있습니다.
■ 선택 정렬(Selection sort)
main() { int loop, i, tmp, min; int data[] = { 10, 3, 34, 5, 26, 4, 1, 6, 9, 19 }; int datasize = sizeof(data) / sizeof(int); for (loop = 0; loop < datasize - 1; loop++) { print_data(data, datasize); min = loop; for (i = loop + 1; i < datasize; i++) { if (data[i] < data[min]) { min = i; } } if (min != loop) { tmp = data[min]; data[min] = data[loop]; data[loop] = tmp; } } print_data(data, datasize); }
코드를 보면 최소값을 찾는 로직이 주이고 데이터 교환은 시작 위치보다 작은 값을 찾은 경우에만 발생하므로 최소의 데이터 이동으로 정렬할 수 있습니다. 아래의 그림은 선택 정렬을 수행한 결과입니다.
■ 삽입 정렬(Insertion sort)
main() { int loop, i, tmp; int data[] = { 10, 3, 34, 5, 26, 4, 1, 6, 9, 19 }; int datasize = sizeof(data) / sizeof(int); for (loop = 1; loop < datasize; loop++) { print_data(data, datasize); for (i = 0; i < loop; i++) { if (data[i] > data[loop]) break; } if (i < loop) { //found tmp = data[loop]; memmove(&data[i+1], &data[i], sizeof(int) * (loop - i)); data[i] = tmp; } } print_data(data, datasize); }
■ 쉘 정렬(Shell sort)
void shell_sort_pass(int data[], int datasize, int grpval) { int i, j, insert, blk, tmp; blk = (datasize / grpval) * grpval; for (i=0; i < datasize; i++) { insert = data[i]; for (j = i - grpval; j >= 0; j -= grpval) { if (data[j] <= insert) break; data[j + grpval] = data[j]; } if (i != (j+grpval)) data[j + grpval] = insert; } } main() { int grpsize[] = {701, 301, 132, 57, 23, 10, 4, 1}; double ciura_parameter = 2.3; int data[] = { 10, 3, 34, 5, 26, 4, 1, 6, 9, 19 }; int datasize = sizeof(data) / sizeof(int); int grpidx = 0; int grpval = grpsize[0]; //check first group size if (datasize > grpval) { while(datasize > grpval) { grpidx--; grpval = (int)(grpval * ciura_parameter); } } else { while(datasize < grpval) { grpidx++; grpval = grpsize[grpidx]; } } print_data(data, datasize); while (grpval > 1) { grpidx++; if (grpidx >= 0) { grpval = grpsize[grpidx]; } else { grpval = (int)(grpval/ciura_parameter); } shell_sort_pass(data, datasize, grpval); print_data(data, datasize); } }
코드와 결과를 보면서 설명드리면 입력 데이터의 크기가 10이므로 예제에서는 그룹크기 4와 1로 정렬을 수행합니다. 그룹을 나누는 값 4를 간격으로 (10, 26, 9), (3, 4, 19), (34, 1), (5, 6)를 그룹으로 해서 삽입 정렬을 수행하고 최종적으로는 삽입 정렬과 동일한 방식이 적용됩니다. 주목할 점은 최종 단계에 들어서기 전에 데이터가 상당부분 정렬되어 있다는 점이고 정렬 대상이 클수록 효율성의 차이를 보일 것입니다. main 함수를 보면 본격적인 정렬에 들어가기 앞서 최초 그룹의 크기를 정하는 작업을 수행하는데 701보다 큰 값의 경우에는 2.3이라는 파라미터를 곱해서 그룹 크기를 정하고 있음을 확인할 수 있습니다.
시간 복잡도는부터 까지 그룹을 나누는 값에 따라 다양하지만 공간 복잡도는 0으로 추가 메모리가 필요 없으면서도 빠른 속도를 가지고 있어 임베디드 시스템등에서 사용하는 편입니다. 같은 값인 경우 원래 순서를 유지하지 못하는 특성이 있습니다.(불안정성)
■ 퀵 정렬(Quick sort)
void print_data(int dat[], int sta, int end) { int i; printf("\n%d~%d : ", sta, end); for (i = 0; i < sta; i++) printf(" "); for (i = sta; i <= end; i++) printf("%3d", dat[i]); } void swap(int data[], int idx1, int idx2) { int tmp; tmp = data[idx1]; data[idx1] = data[idx2]; data[idx2] = tmp; } int partition( int data[], int sta, int end) { int pivot, i, j, tmp; pivot = data[sta]; i = sta; j = end + 1; while (1) { do ++i; while ( data[i] <= pivot && i <= end ); do --j; while ( data[j] > pivot ); if( i >= j ) break; swap(data, i, j); } swap(data, sta, j); return j; } void quicksort( int data[], int sta, int end) { int pivot; if( sta >= end ) return; pivot = partition( data, sta, end); print_data(data, sta, end); quicksort( data, sta, pivot - 1); quicksort( data, pivot + 1, end); } main() { int data[] = { 10, 3, 34, 5, 26, 4, 1, 6, 9, 19 }; int datasize = sizeof(data) / sizeof(int); print_data(data, 0, datasize - 1); quicksort(data, 0, datasize - 1); print_data(data, 0, datasize - 1); }
위의 예제 코드는 피벗으로 정렬 대상의 맨 좌측을 선택하는 방식입니다. 실행 결과를 보면 10, 1, 3, 9, 4, 5, 26를 피벗으로 사용했습니다.
■ 힙 정렬(Heap sort)
#define LEFT(N) (2*(N) + 1) #define RIGHT(N) (2*(N) + 2) #define PARENT(N) ((N-1) / 2) #define SWAP(i, j) tmp = data[i]; data[i] = data[j]; data[j] = tmp main() { int node, i, tmp; int data[] = { 10, 3, 34, 5, 26, 4, 1, 6, 9, 19 }; int datasize = sizeof(data) / sizeof(int), sortsize; print_data(data, datasize); //make max heap for (i = 0; i < datasize; i++) { node = i; while (data[PARENT(node)] < data[node]) { SWAP(PARENT(node), node); node = PARENT(node); } } print_data(data, datasize); sortsize = datasize; while (sortsize > 0) { SWAP(0, sortsize - 1); sortsize --; node = 0; // 0 = new entry, keep max heap while (1) { if (LEFT(node) < sortsize && data[node] < data[LEFT(node)] && (RIGHT(node) >= sortsize || data[LEFT(node)] >= data[RIGHT(node)])) { SWAP(node, LEFT(node)); node = LEFT(node); } else if (RIGHT(node) < sortsize && data[node] < data[RIGHT(node)]) { SWAP(node, RIGHT(node)); node = RIGHT(node); } else { break; } } print_data(data, datasize); } print_data(data, datasize); }
코드를 보면 우선 MAC HEAP을 제작합니다. 그리고 루트에 올라온 최대값을 정렬된 영역으로 옮기면서 새로운 값을 루트에 두고 새로운 값이 MAX HEAP에 맞도록 위치를 찾아가도록 하면 다시 MAX HEAP이 유지되어 루트에 있는 최대값을 옮기고 새로운 값에 대하여 위치를 찾아가는 작업을 반복하여 데이터를 정렬합니다.
■ 병합 정렬(Merge sort)
가장 오래된 정렬 알고리즘의 하나로 메모리의 크기 보다 상당히 큰 파일을 N개로 나누어 정렬하는 외부 병합 정렬은 메모리 허용 공간으로는 퀵소트와 같은 빠른 알고리즘을 사용하여 정렬하고 정렬된 블럭을 병합 정렬을 통해서 정렬합니다. 병합 정렬은 데이터를 원소가 1개이하의 그룹일때 까지 나누고 각 그룹을 재귀적으로 합치면서 정렬합니다.
void mergesort(int data[], int datasize, int temp[]) { int i, j, t; if (datasize <= 1) return; mergesort(data, datasize/2, temp); mergesort(data + datasize/2, datasize - datasize/2, temp); i = 0; j = datasize/2; t = 0; while (i < datasize/2 && j < datasize) { if (data[i] <= data[j]) temp[t++] = data[i++]; else temp[t++] = data[j++]; } //remainder while (i < datasize/2) temp[t++] = data[i++]; while (j < datasize) temp[t++] = data[j++]; memcpy(data, temp, datasize * sizeof(int)); }
블럭을 2개가 아니라 N개로 나누어 병합하기도 하고 블럭을 1개 이하가 아니라 일정 수 이하인 경우에는 삽입 소트등으로 정렬하기도 합니다. 의 시간 복잡도를 보이고 의 추가 공간을 필요로 합니다. 같은 값인 경우 원래 순서를 유지하는 안정성이 있습니다.
■ 기수 정렬(Radix sort)
데이터 항목간 비교가 아니라 N진법의 각 자리수를 기준으로 정렬하는 방식으로 10진수라면 1의 자리, 10의 자리, 100의 자리 수으로 정렬합니다.
void radixsort(int data[], int datasize) { int i, max = data[0], exp = 1, base = 10; int tmp[datasize], bucket[base]; for (i = 1; i < datasize; i++) { if (data[i] > max) max = data[i]; } while (max / exp > 0) { memset(bucket, 0, base * sizeof(int)); for (i = 0; i < datasize; i++) { bucket[(data[i] / exp) % base]++; } for (i = 1; i < base; i++) { bucket[i] += bucket[i - 1]; } for (i = datasize - 1; i >= 0; i--) { tmp[--bucket[(data[i] / exp) % base]] = data[i]; } memcpy(data, tmp, datasize * sizeof(int)); exp *= base; print_data(data, datasize); } } main() { int data[] = { 10, 3, 34, 5, 26, 4, 1, 6, 9, 19 }; int datasize = sizeof(data) / sizeof(int); print_data(data, datasize); radixsort(data, datasize); }
위의 코드를 보면 exp는 1, 10, 100등으로 변하면서 각 자리수를 지칭하고 각 자리수별로 동일한 값을 버킷으로 관리하여 간편한 방법으로 데이터를 정렬합니다. 정수값에만 사용할 수 있는 한계가 있지만 간단하고 빠른 성능을 보입니다. 의 추가 공간을 필요로 하고 의 시간 복잡도를 가집니다. 같은 값인 경우 원래 순서를 유지하는 안정성이 있습니다.
■ 연관 문제 풀이
1. 다음의 정렬 방법 중 최악의 경우(worst case) 가장 빠른 속도를 갖는 것은?
(1) 선택 정렬(selection sort)
(2) 삽입 정렬(insertion sort)
(3) 쉘 정렬(shell sort)
(4) 퀵 정렬(quick sort)
(5) 힙 정렬(heap sort)
※쉘 정렬과 퀵 정렬의 경우에도 최악의 경우에는 시간 복잡도가 선택, 삽입 정렬과 같다는 점에 주의합니다.
2. 다음은 삽입정렬(insert sort)하는 프로그램의 일부이다. 빈 칸에 알맞은 것은?
for(i=1; i<n; i++) {
j = i; temp = a[i];
while(j > 0 && a[j-1] > temp) {
____________________________
}
a[j] = temp;
}
(1) a[j] = a[j+1]; j++; (2) a[j] = a[j+1]; j--; (3) a[j+1] = a[j]; j--; (4) a[j] = a[j-1]; j++; (5) a[j] = a[j-1]; j--;
※두번째 항목부터 새로 추가될 항목으로 간주하여 해당 값 보다 큰 값을 뒤로 밀고 비교하는 작업을 동시에 수행하는 알고리즘입니다. 루프가 0보다 클동안 반복하는 것을 보면 오른쪽부터 거꾸로 내려옴을 확인할 수 있습니다.
3. 다음은 주어진 수를 오름차순으로 정렬하는 프로그램의 일부이다. 빈 칸에 알맞은 것은?
for ( i = 0; i < 10; i++ )
{
tmp = a[i];
for ( j = i - 1; j >= 0; j-- )
if ( a[j] > tmp ) ________________;
else break;
a[j + 1] = tmp;
}
① a[j] = a[j + 1] ② a[j + 1] = a[j] ③ a[j] = a[i] ④ a[j + 1] = a[i] ⑤ a[i + 1] = a[j]
※삽입위치를 찾아 그 뒤에 있는 항목들은 뒤로 미는 삽입정렬입니다.
4. a 배열에는 10이하인 자연수가 10개 담겨 있다. b 배열에 결과 값이 담긴다고 할 때, 다음 함수는 무엇을 하는 함수인가?
void f () { int i; for ( i = 1; i <= 10; i++) c[i] = 0; for ( i = 1; i <= 10; i++) c[a[i]]++; for ( i = 2; i <= 10; i++) c[i] += c[i-1]; for ( i = 1; i <= 10; i++) { b[c[a[i]]] = a[i]; c[a[i]]--; } }
① 각 숫자의 빈도수를 구하는 프로그램 ② 오름차순으로 정렬하는 프로그램 ③ 내림차순으로 정렬하는 프로그램 ④ 작은 순으로 등수를 구하는 프로그램 ⑤ 큰 순으로 등수를 구하는 프로그램
※기수 정렬을 참조해 보시면 c 배열은 버킷을 만드는 과정임을 확인할 수 있습니다. 값을 끄집어 내는 과정을 1->10 방향으로 수행했으므로 오름차순 정렬입니다.
pa = pb = pc = 0;while ( pa < N && pb < M ){if ( a[pa] < b[pb] ) {c[pc] = a[pa];pc++; pa++;} else {c[pc] = b[pb];pc++; pb++;}}if (________)for ( i = pa; i < N; i++ ) [pc++] = a[i];elsefor ( i = pb; i < M; i++ ) [pc++] = b[i];
2006
6. 다음과 같이 정렬되어 있는 배열에서 이진 탐색(binary search)으로 10의 위치를 찾고자 한다. 10을 찾기까지 찾은 10을 포함해서 몇 개의 수를 10과 비교하는가?
1, 3, 4, 7, 10, 12, 14, 17, 23, 31, 37, 42, 45, 57, 63
① 3개 ② 4개 ③ 5개 ④ 6개 ⑤ 7개
7. 다음은 오름차순으로 정렬되어 있는 두 배열 a와 b를 합쳐 오름차순으로 정렬된 하나의 배열 c를 만드는 프로그램의 일부이다. 배열 a에는 M개의 수가 a[1]부터 a[M]까지, 배열 b에는 N개의 수가 b[1]부터 b[N]까지 들어 있고, MAX는 이 모든 수보다 더 큰 수라고 할 때 빈 칸에 들어갈 내용으로 가장 알맞은 것은?
p = q = 1; r = 0;
a[M + 1] = b[N + 1] = MAX;
while (______ ) {
r++;
if ( a[p] < b[q] ) {
c[r] = a[p]; p++;
} else {
c[r] = b[q]; q++;
}
}
① p < M || q < N ② p < M && q < N ③ p <= M && q <= N
④ p <= M || q <= N ⑤ p + q <= M + N
※약간 다른 방식으로 구현한 병합 정렬입니다. 앞서 소개된 병합정렬은 나머지 처리 부분이 있는데 병합 대상 끝에 MAX 값을 두어서 별도의 나머지 처리 로직을 제거한 알고리즘입니다.
8. 다음 프로그램은 둘 다 a와 b의 값을 서로 바꾸어 주는 swap 함수를 구현한 것이다. 빈 칸에는 보기 중 하나의 문장이 들어간다고 할 때 다음 보기 중 어느 곳에도 들어가는 내용이 아닌 것은?
void swap( int &a, int &b )
{
a = a + b;
________;
________;
}
void swap( int &a, int &b )
{
a = a - b;
________;
________;
}
① a = a - b ② a = b - a ③ b = a + b ④ b = a - b ⑤ b = b - a
for (i = 1 ; i <= 9 ; i++) {c1 = 0; c2 = 0;for (j = 1 ; j <= 9 ; j++) {if (a[j] ㉠ a[i]) c1++;if (a[j] ㉡ a[i]) c2++;}if (c1 >= ㉢ && c2 >= ㉣) {mid = a[i];break;}}printf("%d\n", mid);
'IT 일반' 카테고리의 다른 글
최단 경로 계산하기 - 2015 정보올림피아드 문제풀이 (1) | 2015.04.15 |
---|---|
C언어 기초 - 2015 정보올림피아드 문제풀이 (0) | 2015.04.15 |
트리(Tree)의 운행과 알고리즘 - 정보올림피아드 문제풀이 (0) | 2015.03.25 |
트리(Tree)의 개념 - 정보올림피아드 문제풀이 (4) | 2015.03.19 |
진법 변환 - 정보올림피아드 문제풀이 (0) | 2015.03.18 |