삽입 정렬
모든 요소를 앞에서부터 차례대로 정렬된 부분과 비교하여 위치를 찾아 삽입하는 방법.
void InsertionSort(int[] data){//삽입정렬
for (int i = 1; i < SIZE; i++) {
key = data[i]; //키 값.
for (j = i-1; j >= 0; j--) { //앞에 정렬되어 있는 부분에서 해당하는 위치를 찾아 삽입
if (data[j] > key) {
data[j + 1] = data[j]; //한칸 옆으로 이동
}
else {
break;
}
}
data[j + 1] = key;
}
}
시간복잡도
- BestCase : 이미 정렬된 경우 -> 이동 없이 각각 1번의 비교씩 $O(N)$
- WorstCase : 입력 자료가 역순인 경우 등 (마지막이 4 3 1 2 으로 된 경우) ->$\sum_{i=1}^{n-1}i = \frac{n(n-1)}{2}$ -> $O(N^2)$ + 이동횟수 (배열인 경우 모두 한 칸씩 밀어내야함)
- AverageCase : $O(N^2)$
Q : 삽입정렬에서 원소를 정렬된 배열에 삽입할 때, 이진 탐색을 사용하면 $lgN$만 시간이 소요되므로 시간 복잡도가 줄지 않을까?
A : 어차피 자료구조가 배열이라 삽입할때 옆으로 한 칸씩 밀어야함 (결국 모든 원소를 방문하긴 해야됨)
Q : 삽입을 편하게 할 수 있는 연결리스트를 사용하면?
A : 연결리스트는 임의의 원소에 접근하지 못하기 때문에 이진탐색을 할 수 없음.
버블 정렬
서로 인접한 두 원소를 검사하여 정렬하는 알고리즘
void BubbleSort(int data[], int n){
int temp;
for(int i = SIZE-1; i > 0; i--){
for(int j = 0; j < i; j++){
if(data[j] < data[j + 1]){
temp = data[j];
data[j] = data[j + 1];
data[j + 1] = temp;
}
}
}
}
시간복잡도
Best, Worst, Average 모두 일정하게 검사 -> $\sum_{i=1}^{n-1}i = \frac{n(n-1)}{2}$ -> $O(N^2)$ (여기서 역순인 경우 교환횟수 3번 포함.)
퀵 정렬
앤서니 리처드 호어가 개발한 정렬 알고리즘.
분할 정복(Divide and conquer) 방법으로 문제를 작은 문제로 분할 후 해결, 이후에 결과를 모아 원래의 문제를 해결하는 방식.
재귀 호출 or 큐를 사용해서 구현가능.
피봇을 고른 후, 피봇보다 작은 요소들은 왼쪽으로 피봇보다 큰 요소들은 오른쪽으로 옮김. (이 과정에서 무조건 pivot은 제자리를 찾음)
이 오른쪽과 왼쪽 리스트를 각각 또 퀵소트로 정렬. -> 리스트의 크기가 0이나 1이 될 때까지 반복.
void quickSort(int data[],int max) { //큐를 사용하여 구현
int left, right;
int lower_point, upper_point;
int key, temp;
left = 0;
right = max-1;
push(right);
push(left);
while (top >= 0) {
left = pop();
right = pop();
//랜덤으로 키값 설정
if (right - left > 0) {
temp = rand()%(right-left+1) + left;
key = data[temp];
data[temp] = data[right];
data[right] = key;
lower_point = left - 1;
upper_point = right;
while (1) { //왼 ---> / <----오
while (data[++lower_point] < key);
while (data[--upper_point] > key);
if (lower_point >= upper_point) break; //겹침
temp = data[lower_point];
data[lower_point] = data[upper_point];
data[upper_point] = temp;
}
//data[right] == key 즉 한번 분할 끝날때마다 key값은 자기 위치로 가는것.
temp = data[lower_point];
data[lower_point] = data[right];
data[right] = temp;
push(right); //오른쪽 ~ 피봇 +1
push(lower_point + 1);
push(lower_point - 1); //피봇-1 ~ 맨 왼쪽
push(left);
}
}
}
void push(int val) {
if (top >= SIZE - 1) {
return;
}
else {
stack[++top] = val;
}
}
int pop() {
if (top < 0) {
return;
}
else {
return stack[top--];
}
}
시간복잡도
- BestCase : 정확히 리스트가 반씩 나뉘는 경우 -> n번 비교를 $lgn$번 $O(NlgN)$
- WorstCase : Pivot이 계속 불균형하게 잡히는 경우 (이미 정렬된 리스트에서 피봇을 맨 앞or 뒤에서 잡을 때) $\sum_{i=2}^{n}(i-1) = \frac{n(n-1)}{2}$ -> $O(N^2)$
- AverageCase : $O(NlgN)$
이후에 나올 합병(머지) 정렬, 힙 정렬보다 같은 시간복잡도이지만 퀵 정렬이 실제로 더 빠르다. 매번 퀵 소트 실행마다 피벗 1개는 무조건 정렬되어 제외되는 특성과 불필요한 데이터의 이동이 적기 때문이다.
'알고리즘 > 스터디' 카테고리의 다른 글
8. 탐색 - 선형 탐색, 이진 탐색, 이진 탐색 트리, 해시 탐색 (0) | 2020.08.25 |
---|---|
7. 정렬 (2) - 합병 정렬, 힙 정렬, 쉘 정렬 (0) | 2020.08.18 |
5. 재귀 호출 - 완전 탐색, 동적 프로그래밍 (0) | 2020.07.31 |
4. 비트 조작 - 비트 연산자, 비트 함수 (0) | 2020.07.31 |
3. 자료구조 (3) - 힙, 구조체 (0) | 2020.07.25 |
Comment