Company
교육 철학

Quick Sort(퀵 정렬)

퀵정렬 (Quick Sort) 상세 구현

퀵정렬 특징

시간 복잡도에 따라서 o(nlogn)의 빠른 속도이지만, 데이터 값에 따라 O(n^2)나올 수 있다.
특정 범위에서 (start ~ end) 가장 왼쪽에 있는 숫자를 pivot으로 배치한다.

퀵정렬 핵심 아이디어

1. 피벗(Pivot) 선택

배열: [4, 3, 9, 8, 5, 7, 1, 2] ↑ pivot 특정 범위의 가장 왼쪽 원소를 피벗으로 선택
Plain Text
복사

2. 분할(Partition) 과정

여러번의 swap 작업을 통해 pivot의 위치를 결정하고,pivot 왼쪽에는 pivot보다 작은숫자를pivot 오른쪽에는 pivot보다 큰 숫자를 배치한다.
목표: pivot(4)를 기준으로 분할 - 왼쪽: 4보다 작은 수들 (순서는 섞여도 됨) - 오른쪽: 4보다 큰 수들 (순서는 섞여도 됨)
Plain Text
복사

분할 과정 상세 분석

이번에는 구간을 나누어서 같은 작업을 진행하면 된다.

초기 상태: [4, 3, 9, 8, 5, 7, 1, 2]
Plain Text
복사
가장 작은 구간이 될때까지 재귀호출을 통해서 반복하여 위치를 실행해준다.

1단계: 두 부분으로 분할

원본: [1, 3, 2, 4, 5, 7, 8, 9] ←----- pivot보다 작은수들 pivot pivot보다 큰 수들 ----→ (순서는 섞여도 됨) (순서는 섞여도 됨)
Plain Text
복사

2단계: 각 부분을 재귀적으로 정렬

왼쪽 부분: [1, 3, 2] → pivot으로 다시 분할 오른쪽 부분: [5, 7, 8, 9] → pivot으로 다시 분할
Plain Text
복사

세부과정: 투 포인터 분할 알고리즘

범수 a를 start + 1, 범수 b를 end로 설정해준다.

a++ 하면서 pivot보다 큰숫자를 찾는다. b-- 하면서 pivot보다 작은숫자를 찾는다.

1단계: a는 오른쪽으로, b는 왼쪽으로 이동 [4, 3, 9, 8, 5, 7, 1, 2] ↑ ↑ a(9>4 발견) b(2<4 발견)
Plain Text
복사

찾았으면 swap하여 바꿔주고, 이과정을 a와 b가 엇갈리기 전까지 반복해준다.

2단계: 9와 2를 교환 [4, 3, 2, 8, 5, 7, 1, 9] ↑ ↑ a b 3단계: 계속 진행 [4, 3, 2, 1, 5, 7, 8, 9] ↑ ↑ b a (엇갈림!)
Plain Text
복사

엇갈렸으면 마무리 단계로 pivot과 b를 swap하면 된다.

최종: pivot(4)과 b위치(1) 교환 [1, 3, 2, 4, 5, 7, 8, 9] ↑ pivot 최종 위치
Plain Text
복사
이제 pivot원쪽에는 전부 pivot보다 작은숫자들이 배치고, 오른쪽에는 큰 숫자들이 배치하게 된다.

투 포인터 방식 구현 (방법 1)

#include <iostream> int vect[10] = { 4,3,9,8,5,7,1,2 }; int n = 8; void quicksort(int start, int end) { if (start >= end) return; int pivot = start; // 첫 번째 원소를 피벗으로 int a = start + 1; // 왼쪽에서 시작 int b = end; // 오른쪽에서 시작 while (true) { // a: pivot보다 큰 원소를 찾을 때까지 오른쪽으로 이동 while (a <= end && vect[a] <= vect[pivot]) a++; // b: pivot보다 작은 원소를 찾을 때까지 왼쪽으로 이동 while (b >= start && vect[b] > vect[pivot]) b--; // a와 b가 엇갈렸으면 종료 if (a > b) break; // 찾은 원소들을 교환 std::swap(vect[a], vect[b]); } // pivot을 올바른 위치(b)로 이동 std::swap(vect[pivot], vect[b]); // 재귀 호출로 좌우 부분 정렬 quicksort(start, b - 1); // 왼쪽 부분 quicksort(b + 1, end); // 오른쪽 부분 } int main() { quicksort(0, n - 1); // 결과 출력 for (int i = 0; i < n; i++) { std::cout << vect[i] << " "; } return 0; }
C++
복사

Hoare 분할 방식 구현 (방법 2)

#include <iostream> int vect[10] = { 4,3,9,8,5,7,1,2 }; int n = 8; void quicksort(int left, int right) { int i = left, j = right; int pivot = vect[(left + right) / 2]; // 중간값을 피벗으로 /* partition */ while (i <= j) { // 왼쪽에서 피벗보다 큰 값 찾기 while (vect[i] < pivot) i++; // 오른쪽에서 피벗보다 작은 값 찾기 while (vect[j] > pivot) j--; // 교환 조건 만족시 교환 if (i <= j) { std::swap(vect[i++], vect[j--]); } } /* recursion */ if (left < j) quicksort(left, j); // 왼쪽 부분 재귀 if (i < right) quicksort(i, right); // 오른쪽 부분 재귀 } int main() { quicksort(0, n - 1); return 0; }
C++
복사

두 구현 방식 비교

구분
방법 1 (Lomuto-style)
방법 2 (Hoare-style)
피벗 선택
첫 번째 원소
중간 원소
포인터 이동
한 방향씩 순차적
양방향 동시
교환 횟수
상대적으로 많음
상대적으로 적음
구현 난이도
이해하기 쉬움
약간 복잡
성능
보통
약간 더 빠름

퀵정렬 동작 과정 시뮬레이션

초기 배열: [4, 3, 9, 8, 5, 7, 1, 2] 1차 분할 후: [1, 3, 2, 4, 5, 7, 8, 9] ←--left--→ ↑ ←--right--→ pivot(4) 2차 분할: 왼쪽 [1, 3, 2]: pivot=1 → [1, 2, 3] 오른쪽 [5, 7, 8, 9]: 이미 정렬됨 3차 분할: [2, 3]: pivot=2 → [2, 3] 최종 결과: [1, 2, 3, 4, 5, 7, 8, 9]
Plain Text
복사

퀵정렬의 한계와 해결책

최악의 경우 O(n²)

이미 정렬된 배열: [1, 2, 3, 4, 5] 첫 번째 원소를 피벗으로 선택하면: - 왼쪽: [] (빈 배열) - 오른쪽: [2, 3, 4, 5] (거의 모든 원소) 이런 불균형 분할이 계속되면 O(n²)
Plain Text
복사

해결책들

1.
랜덤 피벗: 무작위로 피벗 선택
2.
중간값 피벗: 첫째, 중간, 마지막 중 중간값 선택
3.
Median-of-3: 세 값의 중간값을 피벗으로
4.
Intro Sort: 재귀 깊이가 깊어지면 힙정렬로 전환

최적화된 퀵정렬

void optimizedQuickSort(int arr[], int low, int high) { if (low < high) { // 작은 배열은 삽입정렬 사용 if (high - low < 10) { insertionSort(arr, low, high); return; } // Median-of-3 피벗 선택 int mid = (low + high) / 2; if (arr[mid] < arr[low]) swap(arr[mid], arr[low]); if (arr[high] < arr[low]) swap(arr[high], arr[low]); if (arr[high] < arr[mid]) swap(arr[high], arr[mid]); int pi = partition(arr, low, high); optimizedQuickSort(arr, low, pi - 1); optimizedQuickSort(arr, pi + 1, high); } }
C++
복사

핵심 포인트 정리

분할(Partition)의 핵심

1.
피벗 선택: 보통 첫 번째, 마지막, 또는 중간 원소
2.
투 포인터: 양쪽에서 접근하여 교환할 원소 찾기
3.
교환: 조건에 맞는 원소들 위치 바꾸기
4.
피벗 배치: 최종적으로 피벗을 올바른 위치에 배치

재귀 호출

분할 완료 후: 피벗 기준으로 좌우 부분을 독립적으로 정렬
기저 조건: start >= end일 때 재귀 종료
점점 작은 문제: 전체 → 부분 → 더 작은 부분

시간복잡도

평균: O(nlogn) - 균등 분할
최악: O(n²) - 불균등 분할
공간: O(logn) - 재귀 스택
퀵정렬은 실제로 가장 많이 사용되는 정렬 알고리즘 중 하나입니다!

“강의는 많은데, 왜 나는 아직도 코드를 못 짤까?”

혼자 공부하다 보면 누구나 이런 고민을 하게 됩니다.
강의는 다 들었지만 막상 손이 안 움직이고,
복습을 하려 해도 무엇을 다시 봐야 할지 모르겠고,
질문할 곳도 없고,
유튜브는 결국 정답을 따라 치는 것밖에 안 되는 것 같고.
문제는 ‘연습’이 빠졌기 때문입니다.
단순히 강의를 듣는 것만으로는 실력이 늘지 않습니다.
실제 문제를 풀고, 고민하고, 직접 구현해보는 시간이 반드시 필요합니다.

그래서, 얌얌코딩 코칭은 다릅니다.

그냥 가르치지 않습니다.
스스로 설계하고, 코딩할 수 있게 만듭니다.
얌얌코딩 코칭에서는 단순한 예제가 아닌,
스스로 문제를 분석하고 구현해야 하는 연습문제를 제공합니다.
이 연습문제들은 다음과 같은 역량을 키우기 위해 설계되어 있습니다:
문제를 스스로 쪼개고 설계하는 힘
다양한 조건을 만족시키는 실제 구현 능력
기능 단위가 아닌, 프로그램 단위로 사고하는 습관
마침내 자신의 힘으로 코드를 끝까지 작성하는 경험

지금 필요한 건 더 많은 강의가 아닙니다.

코드를 스스로 완성해 나가는 훈련,
그것이 지금 실력을 끌어올릴 가장 현실적인 방법입니다.
자세한 안내 보기: 프리미엄 코칭 안내 바로가기
또는 카카오톡 상담방: 얌얌코딩 상담방