사용자가 정의한 순서대로 데이터를 나열하는 것
정렬되지 않은 데이터를 이미 정렬된 부분 배열의 적절한 위치에 차례로 삽입하여 전체 데이터를 정렬하는 알고리즘
의사 코드
for j = 2 to A.length
key = A[j]
// A[j]를 정렬된 부분 배열 A[1..j-1]에 삽입한다.
i = j - 1
while i > 0 and A[i] > key
A[i + 1] = A[i]
i = i - 1
A[i + 1] = key
구현 코드
using namespace std;
int main(){
int arr[] = {5, 4, 3, 2, 1};
for(int i = 1; i<sizeof(arr)/sizeof(int); i++){
int key = arr[i];
int j = i;
while(j>0){
if(arr[j-1] > arr[j]){
arr[j] = arr[j-1];
arr[j-1] = key;
j--;
}
}
}
return 0;
}
최초 데이터정렬반복최악의 경우 매 원소 마다 0 번째 ~ 직전 원소들과 비교하므로 O(N²)
정렬되지 않은 데이터를 계속 반으로 나눈 뒤 작은 단위부터 정렬하며 병합해 나가는 방식의 정렬 알고리즘
의사 코드
MERGE_SORT(A, p, r)
if p < r
q = (p + r) / 2
MERGE_SORT(A, p, q)
MERGE_SORT(A, q + 1, r)
MERGE(A, p, q, r) // 이미 정렬된 A[p...q]와 A[q+1....r]을 병합해서 하나의 배열로 만듬
구현 코드
#include <iostream>
using namespace std;
void merge(int arr[], int left, int mid, int right) {
int size = right - left + 1; // 임시 배열의 크기
int* temp = new int[size]; // C-style 동적 배열 할당
int i = left; // 첫 번째 부분 배열(arr[left...mid])의 시작 인덱스
int j = mid + 1; // 두 번째 부분 배열(arr[mid+1...right])의 시작 인덱스
int k = 0; // 임시 배열(temp)의 시작 인덱스
// 두 부분 배열을 비교하며 임시 배열에 정렬하여 저장
while (i <= mid && j <= right) {
if (arr[i] <= arr[j]) {
temp[k++] = arr[i++];
} else {
temp[k++] = arr[j++];
}
}
// 첫 번째 부분 배열에 남은 요소가 있다면 임시 배열에 복사
while (i <= mid) {
temp[k++] = arr[i++];
}
// 두 번째 부분 배열에 남은 요소가 있다면 임시 배열에 복사
while (j <= right) {
temp[k++] = arr[j++];
}
// 임시 배열(temp)에 정렬된 요소들을 원래 배열(arr)의 해당 위치에 복사
for (int idx = 0; idx < size; ++idx) {
arr[left + idx] = temp[idx];
}
delete[] temp;
}
// mergeSort 함수는 변경할 필요 없음
void mergeSort(int arr[], int left, int right) {
if (left < right) {
// (left + right) / 2 는 큰 값에서 오버플로우를 일으킬 수 있음
int mid = left + (right - left) / 2;
// 왼쪽 부분 배열 정렬
mergeSort(arr, left, mid);
// 오른쪽 부분 배열 정렬
mergeSort(arr, mid + 1, right);
// 정렬된 두 부분 배열 병합
merge(arr, left, mid, right);
}
}
int main() {
int arr[] = {9, 3, 1, 5, 13, 12};
int n = sizeof(arr) / sizeof(arr[0]);
cout << "원본 배열: ";
for (int i = 0; i < n; i++)
cout << arr[i] << " ";
cout << endl;
mergeSort(arr, 0, n - 1);
cout << "정렬된 배열: ";
for (int i = 0; i < n; i++)
cout << arr[i] << " ";
cout << endl;
// 출력 결과
// 원본 배열: 9 3 1 5 13 12
// 정렬된 배열: 1 3 5 9 12 13
return 0;
}
분할정복결합(two pointer)데이터가 1개가 될 때까지 반으로 나누는 과정을 통해 생성되는 분할 트리의 높이 ->log N
각 분할 단계 마다 모든 데이터를 병합하여 병합 연산은 단계마다 총 N번 발생
-> 전체 시간 복잡도 = O(N) * O(log N) = O(N log N)
데이터 값 자체를 인덱스로 사용하는 데이터에 의존적인 정렬 방식이다. 각 값의 빈도 수를 세어 그 정보를 기반으로 정렬을 수행한다.
의도 코드
//A는 입력 배열이고, k는 입력 배열의 최대값
COUNTING-SORT(A, k)
count ← 크기가 k + 1인 0으로 채워진 배열
output ← 입력 배열과 같은 크기의 배열
for i = 0 to A.length - 1
count[A[i]] ← count[A[i]] + 1
idx ← 0
for i = 0 to k-1
while (count[i]--) {
arr[idx++] = i;
}
구현 코드
#include <iostream>
using namespace std;
void countingSort(int A[], int k, int size) {
// 0 ~ 최댓값 개수 만큼 count배열 동적할당, 0으로 초기화
int* count = new int[k + 1]();
// count에 빈도수 입력
for (int i = 0; i < size; i++) {
count[A[i]]++;
}
// A배열에 빈도수만큼 복사 입력
int idx = 0;
for (int i = 0; i < k + 1; i++) {
while (count[i]>0) {
A[idx++] = i;
count[i]--;
}
}
// 메모리 해제
delete[] count;
}
int main() {
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
int A[] = { 1, 5, 5, 2, 3, 7, 2, 4, 8, 1 };
int k = A[0];
for (auto& i : A) {
if (i > k) k = i;
}
countingSort(A, k, sizeof(A) / sizeof(int));
for (auto& i : A) {
cout << i << " ";
}
return 0;
}
카운트 배열 생성k일때 크기가 k+1인 count배열을 생성하고 0으로 초기화한다.빈도수 계산count 배열 초기화 -> O(K)
빈도수 확인 -> O(N)
정렬된 배열 생성 -> O(N)
-> 최종 시간 복잡도 = O(N+K)
조건을 만족하는 이진 트리이다. 보통 최대 힙과 최소 힙으로 구분된다.
최대 힙: 모든 부모 노드의 값이 자식 노드의 값보다 크거나 같다.
최소 힙: 부모 노드의 값이 자식 노드보다 작거나 같다.
특정 원소를 기준으로 최대 힙의 성질을 만족하도록 재구성하는 함수
의사 코드
// 전역 변수: A (배열), heap_size (힙 크기)
// 인덱스 i를 루트로 하는 트리를 최대 힙 속성을 만족하도록 수정 (Heapify)
max_heapify(i)
l = LEFT(i) // 왼쪽 자식 인덱스
r = RIGHT(i) // 오른쪽 자식 인덱스
largest = i // 가장 큰 값의 인덱스 (일단 부모 자신으로 초기화)
// 왼쪽 자식과 비교
if l < heap_size and A[l] > A[largest] then
largest = l
// 오른쪽 자식과 비교
if r < heap_size and A[r] > A[largest] then
largest = r
// 만약 자식 노드가 부모 노드보다 크다면
if largest != i then
swap A[i] and A[largest] // 두 노드의 값을 교환
max_heapify(largest) // 교환된 위치(자식 노드)에서 재귀적으로 Heapify 수행
주어진 데이터 전체에 대해서 최대 힙 조건을 만족하도록 max_heapify()를 수행하는 과정
의사 코드
// A : 힙으로 만들 배열
build_max_heap()
heap_size = A.length // 배열의 크기를 설정합니다
// 마지막 non-leaf 노드부터 루트(1번 인덱스)까지 역순으로 반복합니다
for idx = floor(A.length / 2) down to 1
max_heapify(idx) // 각 내부 노드에 대해 MAX_HEAPIFY를 호출합니다
A.length / 2 부터 1번 인덱스까지 반복한다.2*i2*i + 1A.length / 2 이상인 인덱스의 노드는 모두 리프 노드가 되고 max_heapify()를 수행할 필요가 없다.힙 정렬은 배열을 오름차순이나 내림차순으로 정렬하는 것을 의미한다. 위에서 설명한 최대 힙을 구축하는 것은 최댓값이 루트 노드(첫 번째 노드)에 있다는 것을 보장하지만 그 아래 노드들의 정렬을 보장하지 않는다. 따라서 힙을 정렬하기 위해서는 build_heap() 이후 정렬 과정이 필요하다.
build_heap()정렬 과정
heap_size: 정렬 대상 노드의 개수
heap_size = n일때
heap_size > 1 동안 반복
A[0]를 힙의 마지막 요소A[n-1]와 교환heap_size--A[0])에 대해 max_heapify() 수행heap_size = 1이면 배열 A는 오름차순으로 정렬된다.
| 단계 | 시간 |
|---|---|
| 힙 만들기 | O(N) |
| 정렬 과정(heapify N번) | O(N log N) |
| 전체 | O(N log N) |
출처: 스파르타코딩 내일배움캠프