CH 10 - 2 복잡하지만 효율적인 정렬 알고리즘

honeyricecake·2022년 2월 20일
0

자료구조

목록 보기
27/36

이 게시글은 윤성우 선생님의 자료구조 강의를 수강 후 나름대로의 내용 정리를 한 것임을 미리 밝힙니다.
스스로의 복습을 위해 작성한 글이므로 심층있는 학습을 위해서는 책의 구매 및 강의수강을 권장합니다.

1. 퀵 정렬

(1) 이론

(2) 이론 공부 후 구현해본 코드

퀵 정렬을 공부 후
https://www.acmicpc.net/problem/2750 를 해결한 코드이다.

수 정렬하기 2 ~ 5는 이미 정렬된 수열은 시간복잡도가 n^2이므로 해결하지 못하기에 퀵 정렬로 풀 수 없는 문제이다.

#include <stdio.h>

int array[1000];

void qsort(int left, int right)
{
	if (left >= right) return;
	int pivot = array[left];
	int low = left + 1; // low가 low인 이유 : 우선 순위가 낮기 때문, 다 오른편으로 던질 것들
	int high = right; // high가 high인 이유 : 우선 순위가 높기 때문, 다 왼편으로 던질 것들
	int temp;
	while (pivot >= array[low]&&low<=right) low++;
	while (pivot <= array[high]&&high>left) high--;
	if (low > right)// 5 4 3 2 1같이 우선순위가 모두 높은 경우, 4 3 2 1 5와 같이 바꿔주면 된다.
	{
		array[left] = array[right];
		array[right] = pivot;
		qsort(left, right - 1);
		return;
	}
	if (high <= left)// 1 2 3 4 5 같이 우선 순위가 모두 낲은 경우
	{
		qsort(left + 1, right);
		return;
	}
	while (low <= high)//안이 참인동안 동작, low > high인 순간 동작 중지
	{
		temp = array[low];
		array[low] = array[high];
		array[high] = temp;
		while (pivot >= array[low]) low++;//pivot의 우선순위가 낮은 동안 동작, 즉 멈췄을 때는 pivot보다 array[low]의 우선순위가 더 높을 때
		while (pivot <= array[high]) high--;	
	}
	array[left] = array[high];//위의 과정을 거친 후 나온 high는 low와 high가 교차한 후 최초의 high 즉, 우선순위가 피버보다 높은 성분 중 가장 오른쪽의 성분이다.
	array[high] = pivot;
	qsort(left, high - 1);
	qsort(high + 1, right);
	return;
}

int main()
{
	int i, N;
	scanf("%d", &N);
	for (i = 0; i < N; i++)
	{
		scanf("%d", &array[i]);
	}
	qsort(0, N - 1);
	for (i = 0; i < N; i++)
	{
		printf("%d\n", array[i]);
	}
	return 0;
}

느낀 점 - 함수를 호출하면 F10과 조사식을 이용한 디버깅이 좀 힘들어진다.
함수의 중간과정에서 일어나는 일이 궁금하면 printf를 적극활용하자
손으로 직접 써가면서 과정을 살펴보는 방법도 있으나, 이는 꽤 힘든 과정이다.
차라리 어디서 문제가 생기는지 printf를 통해 확인한 후 그 부분만 손으로 써가며 고민해보는게 좋을 것이다.

(3) 강의는 아니고 좀 더 세련된 코드// 강의의 코드에 의문이 생겨 강의는 수강치 않기로 결정.

void qsort(int left, int right)
{
   if (left >= right) return;
   int pivot = array[left];
   int low = left + 1;  // low가 low인 이유 : 우선 순위가 낮기 때문, 다 오른편으로 던질 것들
   int high = right;  // high가 high인 이유 : 우선 순위가 높기 때문, 다 왼편으로 던질 것들
   int temp;
   while (low <= high)//이러면 low<=high이면 바로 검사 종료!
   {
      if (pivot >= array[low]) low++;//array[low]의 우선순위가 높은동안 low움직이고 낮으면 여기 진입 X
      else if (pivot <= array[high]) high--;
      else {//low가 우선순위가 낮은 성분, high가 높은 성분을 가리킬 때 진입
         temp = array[low];
         array[low++] = array[high];
         array[high--] = temp;
      }
   }
   array[left] = array[high];
   array[high] = pivot;
   qsort(left, high - 1);
   qsort(high + 1, right);
}

(4) 피벗 선택에 대한 논의

정렬이 되어 있고 피벗이 정렬대상의 한 쪽 끝에 치우치는 경우 최악의 경우를 보이므로 피벗은 가급적 중간에 해당하는 값이 선택되어야 좋은 성능을 보인다.

-> 중간에 해당하는 값이면 나머지 값들이 모두 피벗 기준으로 좌우로 정렬되기 때문.
-> 최대 혹은 최소가 피봇으로 선택되면 정렬이 전혀 되지 않는다.

그럼 어떤 식으로 pivot을 선택할 것인가? ->
배열이 무질서하다면 pivot을 어떻게 선택하든 상관이 없으나, 그렇지 않다면?

예시
1. 배열의 left, mid, right 중 중간값을 pivot으로 선택
2. 배열의 앞의 세개를 선택해서 이 중의 중간값을 pivot으로 선택한다.

다시 한번 결론을 이야기하자면 피벗이 가급적 중간에 해당하는 값이 선택되어야 좋은 성능을 보인다는 것.

(5) 퀵 정렬 성능 평가

최선의 경우 ->
Best case는 가급적 중간값이 선택될 때인데,
를 위해 우리가 left, right, mid중 중간값을 선택하는 등의 노력을 기하기 때문에
사실상 이를 일반적인 경우로 보아도 무방하기 때문에 최선의 경우지만 사실상 일반적인 경우로 취급한다.

n개의 데이터가 퀵 정렬의 경우
8개를 예로 들면 8 -> 4/4 -> 2/2/2/2 -> 1 이 된다 가정하면 3번이면 된다.
(한번에 절반씩 공간이 나누어진다)

이런 식이면 퀵 정렬은 log_2 n 회 나누어지면 정렬이 끝난다.

그리고 한번 나누어질 때마다 (나누어진 구간의 데이터 개수 - 1)번의 연산이 진행되므로 대락 n번의 연산이 진행된다고 가정할 수 있다.

즉, 퀵 정렬은 O(n log_2 n)의 시간 복잡도를 가진다.

하지만 최악의 경우도 생각해야 한다. (최악의 경우 Big O 는 n^2이기 때문)
-> 중간값을 선택하려는 노력을 조금만 하더라도 퀵 정렬은 최악의 경우는 만들지 않는다.

퀵 정렬은 O(nlogn)의 성능을 보이는 정렬 알고리즘 중에서 편균적으로 가장 졸은 성능을 보이는 알고리즘이다.

다른 알고리즘에 비해서 데이터의 이동이 적고 -> 힙 정렬은 삽입과 출력 모두에서 계속해서 데이터가 이동, 병합 정렬 역시 한번 함수가 호출될 때마다 데이터가 굉장히 많이 이동
별도의 메모리 공간을 요구하지도 않는다. -> 힙 정렬도 힙을 따로 만들어야 하고, 병합정렬도 정렬을 해서 옮길 다른 배열 하나를 만들어야 한다.

0개의 댓글