이 내용은 윤성우의 열혈 자료구조를 학습한 내용입니다.
인접한 두 개의 데이터를 비교해가면서 정렬을 진행하는 방식

#include <stdio.h>
void BubbleSort(int arr[], int n)
{
int i, j;
int temp;
for(i=0; i<n-1; i++)
{
for(j=0; j<(n-i)-1; j++)
{
if(arr[j] > arr[j+1]) // 오름차순 정렬
{
/* 데이터의 교환 */
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
int main(void)
{
int arr[4] = {3, 2, 4, 1};
int i;
BubbleSort(arr, sizeof(arr)/sizeof(int));
for(i=0; i<4; i++)
printf("%d ", arr[i]); // 1 2 3 4
printf("\n");
return 0;
}→ 실제로 빅-오를 결정하는 기준은 ‘비교의 횟수’이다. (배열의 끝까지 확인해야 하므로)
n개 기준
| 연산 | 최악 | 최선 |
|---|---|---|
| 비교연산 | ||
| 대입연산 |
정렬순서에 맞게 하나씩 선택해서 옮기는, 옮기면서 정렬이 되게 하는 알고리즘

#include <stdio.h>
void SelSort(int arr[], int n)
{
int i, j;
int maxIdx;
int temp;
for(i=0; i<n-1; i++)
{
maxIdx = i; // 현재 탐색 구간 중 최댓값을 가지는 인덱스
for(j=i+1; j<n; j++) // 최솟값 탐색
{
if(arr[j] < arr[maxIdx])
maxIdx = j;
}
/* 데이터 교환 */
temp = arr[i];
arr[i] = arr[maxIdx];
arr[maxIdx] = temp;
}
}n개 기준
| 연산 | 최악 | 최선 |
|---|---|---|
| 비교연산 | ||
| 대입연산 |
정렬 대상을 두 부분으로 나눠서, 정렬 안 된 부분에 있는 데이터를 정렬된 부분의 특정 위치에 ‘삽입’해 가면서 정렬을 진행하는 알고리즘

#include <stdio.h>
void InserSort(int arr[], int n)
{
int i, j;
int insData;
for(i=1; i<n; i++)
{
insData = arr[i]; // 정렬대상을 insData에 저장
for(j=i-1; j>=0 ; j--)
{
if(arr[j] > insData)
arr[j+1] = arr[j]; // 비교대상 한 칸 뒤로 밀기
else
break; // 삽입 위치 결정 및 탈출
}
arr[j+1] = insData; // 데이터 삽입
}
}삽입 정렬은 정렬대상의 대부분이 이미 정렬된 경우 매우 빠르게 동작한다.
시간복잡도: 정렬대상 n개 기준
🔻 구현
#include <stdio.h>
#include "UsefulHeap.h"
int PriComp(int n1, int n2)
{
return n2-n1; // 오름차순 정렬
// return n1-n2; // 내림차순 정렬
}
void HeapSort(int arr[], int n, PriorityComp pc)
{
Heap heap;
int i;
HeapInit(&heap, pc);
// 데이터를 모두 힙에 넣는다.
for(i=0; i<n; i++)
HInsert(&heap, arr[i]);
// 힙에서 다시 데이터를 꺼낸다.
for(i=0; i<n; i++)
arr[i] = HDelete(&heap);
}🔻 성능평가
n개 기준분할 정복(divide and conquer)
병합 정렬(Merge Sort)
n개의 데이터를 동시에 정렬하는 것보다, 이를 둘로 나눠서 n/2 개의 데이터를 정렬하는 것이 더 쉽다.실제 정렬은 나눈 것을 ‘병합’하는 과정에서 이뤄진다.

#include <stdio.h>
#include <stdlib.h>
void MergeTwoArea(int arr[], int left, int mid, int right)
{
int fIdx = left; // 앞쪽 구역 인덱스
int rIdx = mid+1; // 뒤쪽 구역 인덱스
int i;
// 병합한 결과를 담을 배열 sortArr 동적할당
int * sortArr = (int*)malloc(sizeof(int)*(right+1));
int sIdx = left; // sortArr의 인덱스
while(fIdx<=mid && rIdx<=right)
{
// 병합할 두 영역의 데이터들을 비교하여
// 정렬순서대로 sortArr에 하나씩 옮긴다.
if(arr[fIdx] <= arr[rIdx])
sortArr[sIdx] = arr[fIdx++];
else
sortArr[sIdx] = arr[rIdx++];
sIdx++;
}
if(fIdx > mid) // 배열의 앞부분이 모두 sortArr에 옮겨졌다면
{
// 배열의 뒷부분에 남은 데이터들을 sortArr에 그대로 옮긴다.
for(i=rIdx; i<=right; i++, sIdx++)
sortArr[sIdx] = arr[i];
}
else // 배열의 뒷부분이 모두 sortArr에 옮겨졌다면
{
// 배열의 앞부분에 남은 데이터들을 sortArr에 그대로 옮긴다.
for(i=fIdx; i<=mid; i++, sIdx++)
sortArr[sIdx] = arr[i];
}
for(i=left; i<=right; i++)
arr[i] = sortArr[i];
free(sortArr);
}
void MergeSort(int arr[], int left, int right)
{
int mid;
if(left < right) // 더 나눌 수 있다.
{
// 중간 지점을 계산
mid = (left+right) / 2;
// 둘로 나눠서 각각을 정렬
MergeSort(arr, left, mid); // left-mid에 위치한 데이터 정렬
MergeSort(arr, mid+1, right); // mid+1-right에 위치한 데이터 정렬
// 정렬된 두 배열 병합
MergeTwoArea(arr, left, mid, right);
}
}void MergeSort(int arr[], int left, int right);void MergeTwoArea(int arr[], int left, int mid, int right);병합결과를 담을 배열을 선언하고 분할된 두 영역의 값들을 순서대로 비교해서 담는다.
병합결과를 실제 배열에 적용한다.

n개 기준✔️ 기본 원리
퀵 정렬의 초기화

left: 정렬대상의 가장 왼쪽 지점right: 정렬대상의 가장 오른쪽 지점pivot: 기준low: 피벗을 제외한 가장 왼쪽에 위치한 지점high: 피벗을 제외한 가장 오른쪽에 위치한 지점low와 high의 이동): 오름차순 기준low: 피벗보다 정렬의 우선순위가 낮은(큰 값) 데이터를 만날 때까지high: 피벗보다 정렬의 우선순위가 높은(작은 값) 데이터를 만날 때까지low와 high의 교환)low와 high의 이동이 멈췄을 때 두 위치의 데이터를 교환한다.low > high 일 때까지, 이동을 진행한다.pivot의 이동)pivot과 high가 가리키는 데이터를 서로 교환한다.left > right 일 때까지, 두 개의 영역으로 나누어 1 ~ 3 과정을 반복한다.
#include <stdio.h>
void Swap(int arr[], int idx1, int idx2)
{
int temp = arr[idx1];
arr[idx1] = arr[idx2];
arr[idx2] = temp;
}
int Partition(int arr[], int left, int right)
{
int pivot = arr[left]; // 피벗의 위치는 가장 왼쪽
int low = left+1;
int high = right;
while(low <= high) // 교차되지 않을 때까지 반복
{
// low와 high 이동
while(pivot >= arr[low] && low <= right)
low++;
while(pivot <= arr[high] && high >= (left+1))
high--;
if(low <= high) // 교차되지 않은 상태라면 swap 실행
Swap(arr, low, high);
}
Swap(arr, left, high); // 피벗과 high가 가리키는 대상 교환
return high; // 옮겨진 피벗의 위치정보 반환
}
void QuickSort(int arr[], int left, int right)
{
if(left <= right)
{
int pivot = Partition(arr, left, right); // 둘로 나눠서
QuickSort(arr, left, pivot-1); // 왼쪽 영역 정렬
QuickSort(arr, pivot+1, right); // 오른쪽 영역 정렬
}
}void QuickSort(int arr[], int left, int right);void Swap(int arr[], int idx1, int idx2); : 값 교환int Partition(int arr[], int left, int right); : 정렬 진행🚩 피봇의 선택: 피봇이 중간에 해당하는 값일 경우, 정렬대상은 균등하게 나뉜다.
- Partition 함수의 호출횟수(정렬 과정에서 선택되는 피벗의 수)가 감소한다.
n개 기준n 개이다. (low와 high의 값을 pivot과 비교)✔️ 기본 원리

정렬대상을 값에 해당하는 버킷으로 이동시키고, 이동이 끝났다면 순서대로 값을 꺼내서 차례로 나열한다.
0 ~ 9 까지의 숫자첫 번째 자릿수(덜 중요한 자릿수)를 기준으로 하여 버킷에 값을 넣는다.
버킷 0에서부터 시작해서 데이터를 꺼낸다.
자릿수를 바꾸어 1 ~ 2 과정을 반복한다.

✅ 기수 정렬: LSD vs MSD
| 정렬 | 특징 |
|---|---|
| LSD | 마지막에 가서 정렬순서를 판단(모든 데이터에 일괄적인 과정을 거친다.) |
| MSD | 점진적으로 정렬을 완성(중간에 정렬이 완료될 수 있다.) |
#include <stdio.h>
#include "ListBaseQueue.h"
#define BUCKET_NUM 10
void RadixSort(int arr[], int num, int maxLen)
{
// 매개변수 maxLen에는 정렬대상 중 가장 긴 데이터의 길이 정보가 전달
Queue buckets[BUCKET_NUM];
int bi;
int pos;
int di;
int divfac = 1;
int radix;
// 총 10개의 버킷 초기화
for(bi=0; bi<BUCKET_NUM; bi++)
QueueInit(&buckets[bi]);
// 가장 긴 데이터의 길이만큼 반복
for(pos=0; pos<maxLen; pos++)
{
// 정렬대상의 수만큼 반복
for(di=0; di<num; di++)
{
// N번째 자리의 숫자 추출
radix = (arr[di] / divfac) % 10;
// 추출한 숫자를 근거로 버킷에 데이터 저장
Enqueue(&buckets[radix], arr[di]);
}
// 버킷 수만큼 반복
for(bi=0, di=0; bi<BUCKET_NUM; bi++)
{
// 버킷에 저장된 것 순서대로 꺼내서 다시 arr에 저장
while(!QIsEmpty(&buckets[bi]))
arr[di++] = Dequeue(&buckets[bi]);
}
// N번째 자리의 숫자 추출을 위한 피제수의 증가
divfac *= 10;
}
}
l: 모든 정렬대상의 길이, n: 정렬대상의 수참고: 윤성우의 열혈 자료구조