이야기박스
c++) 정렬 알고리즘 종합 정리 본문
안녕하세요 !!
오늘은 정렬 알고리즘들을 많이 모아 정리해놨습니다 ㅎㅎ
목적은 각 알고리즘들이 수행시간 실험을 하기 위해서였습니다.
구성은 다음과 같습니다.
○ 일반
- 버블 정렬 (Bubble Sort)
○ 우선순위 큐 (Priority Queue)
- 선택 정렬 (Selection Sort)
- 삽입 정렬 (Insertion Sort)
- 힙 정렬 (Heap Sort)
○ 분할 통치 기법
- 합병 정렬 (Merge Sort)
- 퀵 정렬 (Quick Sort)
○ 기타 메소드
- checkTime() : 시간 측정 해줍니다
- swap() : 다들 아시죠?
- print() : 리스트를 출력합니다
시험들이 끝나고 심심해서 막 달렸네요 ㅋㅋ
작년 이맘때 열심히 공부했었는데, 오랜만에 작성하려고 하니 책도 뒤지고 나름 고생했습니다 ㅋㅋ
반나절이나 걸렸어요..
그러면 코드를 확인하겠습니다.
우선 정렬 알고리즘들이 있는 mySort.h입니다.
§ mySort.h
namespace 선언은 그냥 한번 해보고 싶었어요.. ㅋㅋ
#pragma once
#include <iostream>
#include <vector>
#include <cstdlib>
#include <ctime>
using namespace std;
namespace mySort {
// 출력
void print(vector<int> v) {
for (int a : v) {
cout << a << " ";
}
cout << endl << endl;
}
// swap
void swap(int &a, int &b) {
int tmp = a;
a = b;
b = tmp;
}
// time check
void checkTime(void (*func)(vector<int>), vector<int> v) {
clock_t start, end;
double times;
start = clock();
func(v);
end = clock();
times = (double)(end - start) / (CLOCKS_PER_SEC);
cout << "실행 시간 : " << times << " second" << endl << endl;
}
/*
* 기본 버블
*/
inline void bubble(vector<int> v) {
for (int i = 0; i < v.size(); ++i) {
for (int j = 0; j < v.size() - i - 1; ++j) {
if (v[j] > v[j + 1]) swap(v[j], v[j + 1]);
}
}
// print(v);
}
/*
* 우선순위 큐 정렬
* 삽입 , 선택 , 힙 정렬
*/
// 삽입 정렬
inline void insertion(vector<int> v) {
vector<int>::iterator itr;
int save; // 이번 턴의 체크할 값
for (itr = v.begin()+1; itr != v.end(); ++itr) {
save = *itr;
vector<int>::iterator itr2 = itr;
// 기존의 체크한 값들을 비교
// save 보다 크면 하나씩 뒤로 민다
// => save가 들어갈 위치를 찾는 과정
while (itr2 != v.begin() && *(itr2-1) > save) {
*itr2 = *(itr2 - 1);
--itr2;
}
*itr2 = save;
}
// print(v);
}
// 선택 정렬
inline void selection(vector<int> v) {
vector<int>::iterator minLoc; // 가장 큰 값의 위치
vector<int>::iterator itr, itr2;
int tmp;
// 하나 남은 경우는 더 실행할 필요가 없다.
for (itr = v.begin(); itr != v.end(); ++itr) {
minLoc = itr;
for (itr2 = itr + 1; itr2 != v.end(); ++itr2) {
if (*itr2 < *minLoc) minLoc = itr2;
}
// swap
swap(*itr, *minLoc);
}
// print(v);
}
// 힙 정렬
void downHeap(vector<int> &v, int i, int n) {
int pos = 2 * i + 1;
vector<int>::iterator left, right, down;
// 범위 넘어가면 종료
if (pos >= n) return;
// 왼쪽, 오른쪽 노드 값 분배
left = v.begin() + pos;
right = v.begin() + pos + 1;
// 내려갈 곳 찾음
if (pos+1 < n) {
if (*left < *right) down = right;
else down = left;
}
else
down = left;
// 내가 내려갈 곳보다 작으면 안내려감
if (v[i] >= *down) return;
// 내려감
swap(v[i], *down);
// 재귀
downHeap(v, (int)(down - v.begin()), n);
}
void buildHeap(vector<int> &v) {
int n = v.size();
for (int i = (n - 2) / 2; i >= 0; i--) {
downHeap(v, i, n);
}
}
inline void heap(vector<int> v) {
int n = v.size();
// phase 1 힙 생성
buildHeap(v);
// phase 2 힙 정렬
while( n > 1 ) {
swap(v[0], v[n-1]);
--n;
downHeap(v, 0, n-1);
}
// print(v);
}
/*
* 분할 통치법
* 합병 정렬, 퀵 정렬
*/
// 합병 정렬
void merging(vector<int> &v, int left, int size, int right) {
vector<int> total;
int i, j;
i = left; // L1 시작점
j = size + 1; // L2 시작점
while (i <= size && j <= right) {
if (v[i] > v[j]) {
total.push_back(v[j++]);
}
else {
total.push_back(v[i++]);
}
}
// L2가 남은 경우
if (i > size) {
while (j <= right) {
total.push_back(v[j++]);
}
}
// L1이 남은 경우
else {
while (i <= size) {
total.push_back(v[i++]);
}
}
for (int x = left; x <= right; x++) {
v[x] = total[x - left];
}
}
void divide(vector<int> &v, int left, int right) {
// 재귀 베이스
if (left >= right) return;
// 분할 포인트
int size = (left + right) / 2;
// 재귀
divide(v, left, size);
divide(v, size + 1, right);
// 합병
merging(v, left, size, right);
}
inline void merge(vector<int> v) {
divide(v, 0, v.size() - 1);
// print(v);
}
// 퀵 정렬
int partition(vector<int> &v, int left, int right, int pivot) {
int i = left;
int j = right - 1;
// i가 j 넘기 전까지
while (i <= j) {
// 왼쪽은 피벗보다 작을때 i 이동
// 오른쪽은 피벗보다 클 때 j 이동
// 둘 다 성립하기 전까지 이동 후, 교환
while (i <= j && v[i] <= pivot) ++i;
while (j >= i && v[j] >= pivot) --j;
if (i < j) swap(v[i], v[j]);
}
// 피벗과 작동이 멈춘 i 위치 교환
// --> 피벗이 위치를 찾아 가는 것
swap(v[i], v[right]);
return i;
}
void quickSort(vector<int> &v, int left, int right) {
int Pi;
// 재귀 베이스
if (left >= right) return;
// 피벗 포인터 받기
Pi = partition(v, left, right, v[right]);
quickSort(v, left, Pi - 1);
quickSort(v, Pi + 1, right);
}
inline void quick(vector<int> v) {
quickSort(v, 0, v.size()-1);
// print(v);
}
} // namespace mySort
다음은 정렬을 실행시킬 main.cpp 입니다.
§ main.cpp
#include "mySort.h"
#define NUM 20000
using namespace mySort;
int main() {
srand((unsigned int)time(nullptr));
vector<int> sample;
for (int i = 0; i < NUM; ++i) {
int t = rand() % 100;
sample.push_back(t);
}
std::cout << "<< Experiment start --- size : " << NUM << " >>" << endl;
std::cout << "** bubble" << endl;
checkTime(bubble, sample);
std::cout << "** insertion" << endl;
checkTime(insertion, sample);
std::cout << "** selection" << endl;
checkTime(selection, sample);
std::cout << "** heap" << endl;
checkTime(heap, sample);
std::cout << "** merge" << endl;
checkTime(merge, sample);
std::cout << "** quick" << endl;
checkTime(quick, sample);
return 0;
}
실험은 NUM 값만 변경해주면서 하시면 됩니다.
그럼 실험 결과를 보겠습니다.
First ) NUM = 1000
Second ) NUM = 10000
Third ) NUM = 20000
리스트의 크기가 커질수록 차이가 현격하게 증가하는 것을 볼 수 있습니다!
하지만 퀵 정렬은 짱짱..
§ 분석
우리가 알고 있는 알고리즘 수행 시간
알고리즘 |
수행 시간 |
버블 (Bubble) |
O(n^2) |
선택 (Selection) |
O(n^2) |
삽입 (Insertion) |
O(n^2) |
힙 (Heap) |
O(n log n) |
병합 (Merge) |
O(n log n) |
퀵 (Quick) | O(n log n) |
O(n log n)의 성능을 가지는 알고리즘이 빠른건 다행이네요.
하지만 왜이렇게 급격하게 차이가 나는 걸까요?
n^2과 n log n의 크기의 차이가 그렇게 큰걸까요?
아래 그래프를 보면 그럴 수 있겠다는 생각이 듭니다 ㅋㅋ
< 그래프 정보 >
< 그래프 >
저 그래프들은 각 끝이 대략 100일 때 모습입니다.
이것들이 앞으로 얼마나 큰 차이가 날지는 뻔하죠?
이상으로 정렬 포스팅을 마치겠습니다.
다른 정렬들도 더 있지만, 가장 유명하고 기본적인 정렬들이에요!
다음에는 보다 좋은 자료로 찾아 오겠습니다!
요즘에 진로 준비에 바빠서.. ㅎㅎ ㅜㅜ
코드 자료 --> Git hub
( 그래프 참조 사이트 : https://www.desmos.com/calculator )
( 관련 정보 : https://ko.wikipedia.org/wiki/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98_%EB%B6%84%EC%84%9D )
'Programming Language > c, c++' 카테고리의 다른 글
| c++) AVL 트리 ( 삽입, 삭제, 트리 출력 ) (0) | 2017.11.11 |
|---|---|
| c++) 이진 탐색 트리 (0) | 2017.11.04 |
| C) 하노이의 탑 (0) | 2017.03.27 |
| c) Heap 정렬을 해보자 (0) | 2017.03.17 |
| c) 가중치 / 다익스트라 응용, 최단거리 찾기 + 개수 찾기 (2) | 2017.03.17 |