본문 바로가기
코딩테스트 준비/백준

[백준 2750번][C++] 수 정렬하기 - 3. 퀵 정렬

by 스테디코디스트 2023. 7. 16.
반응형

<문제 소개>


<소스 코드>

#include <iostream>
#include <string>
#include <vector>
#include <sstream>
#include <map>
#include <cmath>
#include <stack>
#include <queue>
#include <algorithm>
#include <cstdlib>
using namespace std;

int Partition(vector<int>& v, int left, int right)
{
	// left번 인덱스부터 right번 인덱스를 pivot번 인덱스의 원소를 기준으로 큰 값, 작은 값을 나눔
	int pivot = left; // 기준 인덱스 값(고정)
	int swap = left; // 중간중간 swap에 사용될 인덱스 값

	for (int i = left + 1; i <= right; i++)
	{
		// pivot 값보다 작은 수들을 pivot의 앞자리로 차례대로 이동시킴
		if (v[pivot] > v[i])
		{
			swap++;

			// swap(i <-> swap)
			int temp = v[i];
			v[i] = v[swap];
			v[swap] = temp;
		}
	}

	// swap(pivot <-> swap) 
	// swap을 통해 큰 값, 작은 값이 나뉘게 되는 중간에 pivot이 위치하게 됨
	int temp = v[pivot];
	v[pivot] = v[swap];
	v[swap] = temp;

	pivot = swap; // 실제 pivot 값도 변경

	return pivot; // pivot의 인덱스를 리턴
}

void QuickSort(vector<int>& v, int left, int right)
{
	if (left < right)
	{
		int pivot = Partition(v, left, right); // pivot 값의 위치를 찾아서 저장

		QuickSort(v, left, pivot - 1); // pivot 기준 왼쪽부분(pivot보다 작은 부분) 정렬
		QuickSort(v, pivot + 1, right); // pivot 기준 오른쪽부분(pivot보다 큰 부분) 정렬
	}
}

int main()
{	
	int N; 
	cin >> N;

	vector<int> v;

	// 원소 입력
	while (N > 0)
	{
		int x;
		cin >> x;

		v.push_back(x);
		N--;
	}

	// 3. 퀵 정렬
	// 1. 특정 값(pivot)을 기준으로 삼음
	// 2. pivot을 기준으로 pivot보다 큰 값과 작은 값으로 나누는 과정을 반복	
	QuickSort(v, 0, v.size() - 1);

	// 원소 출력
	for (int i = 0; i < v.size(); i++)
	{
		cout << v[i] << endl;
	}

	return 0;
}

 


<풀이과정>

0. 퀵 정렬은 특정 값 pivot을 기준으로 오른쪽은 큰 값, 왼쪽은 작은 값으로 나누는 과정을 반복하여 정렬하는 방식이다.

1. 퀵 정렬은 QuickSort() 함수와 Partition() 함수로 나누어서 구현한다.

 

2. 먼저 Partition() 함수는 앞서말한 나누는 과정을 구현한 함수이다.

2-1. 인자로 벡터 v와 정렬할 벡터의 가장 처음(왼쪽) 원소의 인덱스 값 left, 그리고 정렬할 벡터의 마지막(오른쪽) 원소의 인덱스 값 right를 받아오고, 리턴형은 int형으로 한다.

2-2. 함수내에서 기준값이 되는 원소의 인덱스인 pivot을 임의로 left로 설정

2-3. 중간중간 swap에 사용될 변수 swap도 처음에는 left로 설정

2-4. 벡터의 left번째 원소부터 right번째 원소까지 반복하면서 pivot번째 원소 v[pivot]과 비교하여 해당 값 v[i]가 더 작은 경우 swap을 더해주고 해당 swap의 자리 v[swap]과 v[i]의 자리를 바꿔준다.

2-5. 즉, pivot의 다음 위치부터 차례대로 v[pivot]보다 작은 값을 가진 원소들이 나열되게 된다.

2-6. 이 과정이 끝나고 반복문을 나오게 되면 벡터 v의 형태는 아래와 같다.

v = { // v[pivot] // -> // v[pivot] 보다 작은 값을 가지는 원소들 // -> // v[pivot]보다 큰 값을 가지는 원소들 // }

2-7. 이때 swap이 가리키는 원소 v[swap]은 v[pivot]보다 작은 값을 가지는 원소들 중 마지막 값의 인덱스와 같다.

2-8. 그러므로 이 swap의 값과 pivot의 값을 바꾸면 아래와 같이 pivot이 작은 값과 큰 값들 사이에 위치하게 된다.

v = { // v[pivot] 보다 작은 값을 가지는 원소들 // ->  // v[pivot] // -> // v[pivot]보다 큰 값을 가지는 원소들 // }

2-9. 마지막으로 바뀐 pivot의 값을 리턴시켜준다.

 

3. 다음으로 QuickSort는 위에서 구한 Partition을 이용해 정렬은 진행시키는 함수이다.

3-1. 마찬가지로 벡터 v, 왼쪽 인덱스 값 left, 오른쪽 인덱스 값 right를 인자로 받는다.

3-2. 입력된 left값과 right값을 비교하여 left값이 right값보다 크거나 같은 경우 이미 정렬이 끝나있는 상태이므로 Partition을 진행하지 않고, left값이 right값보다 작아 아직 정렬되지 않은 벡터에만 Partition을 진행한다.

3-3. left가 right보다 작은 경우, 앞서구한 Partition() 함수를 이용해 한번 정렬하여 리턴값 pivot을 받아온다.

3-4. 받아온 리턴값은 작은 값과 큰 값 사이에 있는 중간 값이므로, QuickSort를 벡터 v의 좌측부인 left부터 pivot-1번째 까지를 다시 인자로 받아 호출하여 정렬하는 것을 재귀적으로 반복하여 좌측을 정렬한다.

3-5. 우측부인 pivot+1부터 right까지도 마찬가지로 QuickSort를 해당범위를 인자로 받아 호출하여 우측을 정렬한다.

 

4. 이렇게 QuickSort 내에서 Partition을 이용하여 퀵 정렬을 정의했으므로,

벡터 v를 정렬하려면 메인함수에서 QuickSort(v, 0, v.size() - 1)을 호출하여 주면된다.

 

<코멘트>

이번엔 세번째 방법인 퀵 정렬로 풀어보았다!

퀵 정렬은 조금 어려워서 다른 분의 코드를 참고해서 이해하면서 구현하였다.

벡터를 인자로 받을때, 벡터의 주소를 참조하여 함수를 실행했을 때 실제 v의 값들이 변경하도록 해야했다.

Partition() 함수의 반복이 끝난 뒤 v[pivot]과 v[swap]을 swap 할때, pivot도 swap의 값으로 변경해서 리턴해줘야 한다.

 

- 퀵 정렬 -

장점 

- 기준값에 의한 분할을 통해 구현하는 정렬법으로써, 분할 과정에서 logN이라는 시간이 걸리게 되고, 전체적으로 보게 되면 NlogN이므로 실행시간이 준수한 편이다.

 

단점

- 기준값인 pivot에 따라서 시간복잡도가 크게 달라진다. -> 최악의 경우 O(n^2)이라는 시간복잡도를 갖게된다.

 

특징 

1. 시간복잡도

- 최악 : O(n^2)

- 평균 : O(nlogn)

2. 공간복잡도 O(nlogn)

3. in-place정렬 

-> 퀵 정렬은 재귀적으로 정의되므로 재귀 호출에 따른 스택이 사용되는데 이때 스택의 깊이는 n개의 원소에 대해 logn에 비례하므로 공간복잡도는 O(nlogn)이다. 따라서 in-place 정렬이라고 하기 힘들지만 실용적으로는 상대적으로 작은 메모리만을 사용하므로 흔히 in-place 정렬이라고 말한다.

4. not-stable정렬 

 

<cf>

1. stable vs not-stable

- stable 정렬은 중복된 키 값이 있을 때 이를 순서대로 정렬하는 알고리즘을 뜻함.

- 즉 , 정렬했을 때 중복된 키 값이 원래 순서대로 정렬되어있으면 stable 아니면 not-stable이다.

2. in-place vs not in-place

- in- place 정렬은 원소들의 개수에 비해 충분히 무시할 만한 저장 공간만을 더 사용하는 정렬 알고리즘을 뜻함.

- not in-place 정렬은 원소들의 개수에 비례하여 저장 공간을 더 사용하는 정렬 알고리즘을 뜻함.


<제출 결과>

<퀵 정렬> - 준수한 시간이라 선택정렬과 비슷한 시간대가 나온듯..?

<비교 - 선택정렬> - 버블정렬에 비해 조금 더 빠르다는 것을 알 수 있음

<비교 - 버블정렬>