• 분류 전체보기
    • 갈무리
    • 씨플플
    • SPRING
    • PI
    • ALGORITHM
    • DIARY
    • (∗❛ᴗ❛∗)
    • (●'◡'●)
    • 민디의 취준일기 (202301~)
  1. [2792] 보석상자 2020.04.01
  2. [10026] 적록색약 2020.03.09
  3. [1431] 시리얼번호 2020.02.19
  4. [C] TCP 소켓프로그래밍 파일전송 (서버, 클라이언트) 2020.02.15
[C] 퀵소트 #ALGORITHM
2020. 2. 19.

동작과정만 알면 세상에서 제일 쉬운 퀵소트

 

void quicksort(int* a, int left, int right) {

                if (right > left) {

                                 // divide

                                 int pivot = partition(a, left, right);

                                 // conquer

                                 quicksort(a, left, pivot - 1 );

                                 quicksort(a, pivot + 1, right);

                 }

}

 

> 과정

Subarray A (코드에서 int* a) 를

 

 

1. Divide -  partition(a, left, right);

 

두 개의 subarray a[left, pivot] 과 a[pivot+ 1, right] 로 나눈다

partition 한 결과 배열을 두개로 나누는 기준점이 되는 pivot의 값을 return한다

 

 

2. Conquer - quicksort(a, left, pivot - 1 ); , quicksort(a, pivot + 1, right);

 

두 배열을 Quicksort 재귀호출을 이용해 정렬한다.

 

 

3. Combine - 합치기

 

partition 함수에서 이미 pivot값은 자기 자리를 찾아가 정렬이 되기 때문에 subarray 를 합치는 작업은 필요하지 않는다

 

 

 

 

> partition() 동작 과정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* psudo code */
partition(a, p, r){
    
// PARTITION always selects the last element A[r] in the subarray A[p .. r] as the pivot
    x <- a[r] // partion 함수는 subarray A[p .. r]의 마지막요소인 A[r]를 pivot으로 선택합니다
 
    i <- p – 1
    for(j<- p to r-1)
     do if a[j] <= x
                then i<= i+1
                exchange a[i] <-> a[j]
    exchange a[i+1] <-> a[r]
    
    return i + 1;
}
 
Colored by Color Scripter
cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* C */
int partition(int* a, int left, int right) {
    int pivot = a[right];
    int i = left – 1;
    for (int j = left; j < right; j++) {
        if a[j] <= pivot {
            i++;
        swap(a[i], a[j]);
        }
    }
    swap(a[i + 1], a[right]);
    
return i + 1;
}
 
Colored by Color Scripter
cs

 

Loop invariant:

                                 1. All entries in A[p .. i ] £ pivot

                                 2. All entries in A[i+1 .. j-1] > pivot

                                 3. A[r] = pivot

 

 

인덱스 i 는 pivot보다 작거나 같은 수들의 마지막 인덱스.

 

for 반복문으로  j를 right 이전 값 ( =  n - 1 ) 까지 검사해 ★pivot 보다 A[j]가 작다면 A[j]를 i + 1 위치에 이동★ 시킨다.

 

 

 

 

 

루프 for (int j = left; j < right; j++) 를 돌면서 확실한 건

A[left ... i ] <= pivot, pivot < A[i+1 ... j-1 ] 라는 점.

 

그리고 A[j … right - 1] 은 아직 확인되지 않은 값. A[j] 는 if (a[j] <= pivot) 을 거쳐 확인해봐야 pivot보다 작은 지 큰 지 알 수 있다.

 

 

 

 

반복문은 j가 right가 될 때 종료. 종료 이후에 A는 세가지의 경우로 나뉜다.

 

 

1. A[left… i] <= pivot

2. pivot < A[i+1 … right - 1]

3. A[right] = pivot

 

 

마지막 라인 swap(a[i+1], a[j]); 은 pivot 값을 두 subarrays의 사이로 옮기는 것.

이렇게 되면 pivot을 기준으로 pivot의 왼쪽은 작은놈, pivot의 오른쪽은 큰놈이 오게 된다.

 

 

pivot: 앞과 뒤의 요소들의 순서는 난 모르겠고 내앞에 작은놈 내뒤에는 큰놈들만 있으니 내 위치는 완벽

 

 

 

Time for partitioning (시간복잡도)

: pivot에 대해서 나머지 n-1  개의 각 원소들과 한번씩 비교를 하기 때문에 O(n-1) => O(n)

void quicksort(int* a, int left, int right) {

                 if (right > left) {

                                 int pivot = partition(a, left, right);

                                 quicksort(a, left, pivot - 1 );

                                 quicksort(a, pivot + 1, right);

                 }

}

<14> performance of Quicksort

quick sort의 running time 은 subarray 의 patitioning 에 아주아주 영향을 받음

 

<15> Worst Case = Array 분할 시 불균등하게

최악의 경우는 정렬하려는 리스트가 이미 한쪽으로 (오름차순이든 내림차순이든) 정렬 되어있는 상태

오름차순의 경우: ( 왼쪽 partition의 원소 0 오른쪽 partition의 원소 n – 1 (왼쪽 리스트가 텅 빈 상태))

내림차순의 경우: ( 왼쪽 partition의 원소 n-1 오른쪽 partition의 원소 0 (오른쪽 리스트가 텅 빈 상태))

>      이럴 때 sub-problem 이 하나만 생긴다

 

따라서 T(n) = T(n – 1) + n – 1 (n = 1) 이고

T(n) = T(n – 1) + n – 1 = T(n-1) + O(n) (n > 1)

 

T(n) = (n – 1) + (n – 2) + ( n - 3 ) + …. + 1 =  n(n-1)/2 가 되어 시간복잡도는 세타(n^2) 이 된다

<16> Best case = Array 분할 시 양쪽으로 균등하게 분할되는 경우

만약 pivot 값이 아주 이상적으로 선정되어 매 과정마다 거의 균등한 크기로 분할이 된다면 mergesort 만큼 빠르다!

T(n) = 0 (n = 1 일 경우)

T(n) = T(n/2) + T(n/2) + n – 1 = T(n/2) + T(n/2) + O(n) (n > 1) 일 경우

if the subarrays are balanced, then quicksort can run as fast as mergesort

=> 시간복잡도는 세타(nlogn) 이 된다

<17 - 18> 두개로 나뉘긴 했는데 그 중에서도 ‘두개로 나뉘긴 했는데 한쪽에 극단적으로 치우친 케이스’

T(n) <= T(9n/10) + T(n/10) + O(n) 임에도 불구하고 O(nlgn) 이 나온다는 사실

은 recursive tree 를 그려서 해결해보자

 

 

 

 

 

 

 

티스토리툴바