기본적으로 데이터를 분할하여 배열에 0개 또는 1개의 항목이 남을 때까지 분할하여 개별적으로 정렬되는 방식
항목은 하나 배열
피벗 포인트라 부르는 단일 요소를 선택하여 수행
어떤 배열에서 어떤 요소를 선택하든 사실상 문제가 되지 않음
중앙에 있는 요소 선택했을 경우, 해당 숫자보다 작은 숫자를 왼쪽으로 옮기고, 그 숫자보다 큰 숫자는 오른쪽으로 옮김
모두 정렬하려는게 아닌 한쪽으로 옮기는 것
그 숫자 하나만 올바른 위치이고, 다른 숫자들이 오른쪽이나 왼쪽에 있지만 정확한 위치는 모름
위의 과정을 왼쪽과 오른쪽에 반복
예시
[5,2,1,8,4,7,6,3] 있을 경우
5보다 작은 2,1,4,3은 왼쪽으로 옮겨짐
[2,1,4,3,'5',8,7,6] 이렇게 5는 제 위치를 찾은 것
이 과정을 계속해서 반복
재귀를 작성하고 코드를 시각화하려면 살짝 까다로울 수 있음
실제 동작할 때는 [5,2,1,4,3,8,7,6] 이렇게 한 다음에 5를 제 위치로 옮김
🐣 피봇 helper
퀵 정렬 첫 번째
파티션, 피벗 같은 의미
배열이 주어지면 요소를 피벗 포인트로 지정하여 배열 속 요소를 재배치하는 함수를 작성
피벗보다 작은 값은 모두 왼쪽으로 이동하며 피벗보다 큰 값은 모두 오른쪽으로 이동
양쪽의 순서는 중요하지 않고, 올바른 쪽에서 피벗보다 작거나 커야 함
중요한 것은 헬퍼가 제자리에서 수행해야 함
새 배열을 만들면 안 되고, 피벗 인덱스를 반환해야 함
제자리에서 모두 수행하기 때문에 새 배열을 만들지 않음
퀵 정렬의 실행 시간은 피벗 선택 위치에 따라 달라질 수 있기 때문에 피벗 선택은 중요한 것임
이상적으로 데이터 집합의 중간값이 되도록 선택해야 함
하지만 데이터가 무엇인지, 순서가 어떻게 되어 있는지 모른다면 쉽지 않음
다른 전략으로는 첫 번째 요소, 아니면 마지막 요소, 아니면 중간, 무작위 요소를 선택
우리는 첫 번째 요소만 선택해보겠음. 빅오에 영향을 미치긴 하지만..간단히 하기 위해서!
let arr = [5,2,1,8,4,7,6,3]
pivot(arr); //4
arr;
// any one of these is an acceptable mutation:
// [2,1,4,3,5,8,7,6]
// [1,4,3,1,5,7,6,8]
// [3,2,1,4,5,7,6,8]
// [4,1,2,3,5,6,8,7]
// there are other acceptable mutations too
위의 배열이 있고 피벗 함수를 호출한다면 인덱스 4를 반환
주의할 점은 배열을 반환하지 않음
하지만 arr 호출했을때 배열이 바뀐 것을 볼 수 있는데 이러한 구성 모두 타당함
첫 번재 요소를 골라서 피벗으로 선택
5를 선택하고, 5기준으로 작은 숫자 왼쪽에 배열 재배치
1,2,3,4의 순서는 상관없음 5 기준으로 왼쪽에 작은 숫자가 있으면 됨
반대로 5 오른쪽에는 순서상관없이 5보다 큰 숫자 6,7,8이 있음
가능한 모든 조합 가능. 순서 상관 없음!_!
중요한 것은 5가 최종 위치에 정렬된 위치에 있다는 것. 의사코드임
방법
피벗 또는 파티션이라 불리는 함수 작성
이 함수는 배열, 시작 인덱스, 끝 인덱스 세 개의 인수 받음
기본값으로 시작 인덱스 0, 끝 인덱스는 배열.length -1
배열 시작 부분에서 피벗 선택(중간이나 마지막, 무작위 위치로 변경 가능)
편의상 맨 첫 부분 선택, 현재의 피벗 인덱스를 변수로 저장
마지막에 피벗을 바꿀 위치를 계속 확인
시작부터 끝까지 배열에 루프를 수행
살펴보는 요소보다 피벗이 클 경우 피벗 인덱스 변수를 증가시킨 다음 현재 요소와 피벗 인덱스 요소를 교환
맨 마지막에는 시작했던 피벗과 피벗 인덱스를 바꾼 다음 피벗 인덱스 반환
[28,41,4,11,16,1,40,14,36,37,42,18]
예를들어 이렇게 있을 경우
처음에 첫 번째 요소 28를 피벗으로 선택
다른 요소에서 모든 루프를 수행. 28과 41 비교. 41이 더 크기 때문에 아무것도 하지 않음
두번째는 4임. 28이 4보다 크기 때문에 4를 28 왼쪽에 둬야함. 하지만 여기서는 옮겨두고 28왼쪽으로 옮기는건 나중에 함
그리고 피벗 인덱스라는 변수를 지정
28보다 작은 요수의 수를 계속해서 파악해서 맨 마지막에 28을 올바른 위치로 교환하게 됨
4를 옮김. 피벗 뒤로
그다음 11을 보면 28보다 작음 . 41과 값을 바꾸고 피벗 인덱스를 1만큼 증가 (= 28보다 작은 값 현재 2개)
16의 경우도 동일 (= 28보다 작은 값 현재 3개)
1,14,18도 동일 (= 28보다 작은 값 현재 6개)
피벗 인덱스도 6임. 작은 값을 찾을 때마다 +1 했기 때문
28의 경우 인덱스 6에 들어가면 되고, 작았던 값들은 그 왼쪽에 위치 시키면 됨 (인덱스 6에 들어감 / 피벗보다 작은값은 6개 존재, 즉 7번째 위치)
피봇 helper 함수 구현
pivot.js
// First Version
function pivot(arr, start=0, end=arr.length+1){
function swap(array, i, j) {
var temp = array[i];
array[i] = array[j];
array[j] = temp;
}
var pivot = arr[start];
var swapIdx = start;
for(var i = start + 1; i < arr.length; i++){
if(pivot > arr[i]){
swapIdx++;
swap(arr,swapIdx,i);
}
}
swap(arr,start,swapIdx);
return swapIdx;
}
// Version with ES2015 Syntax
function pivot(arr, start = 0, end = arr.length - 1) {
const swap = (arr, idx1, idx2) => {
[arr[idx1], arr[idx2]] = [arr[idx2], arr[idx1]];
};
// We are assuming the pivot is always the first element
let pivot = arr[start];
let swapIdx = start;
for (let i = start + 1; i <= end; i++) {
if (pivot > arr[i]) {
swapIdx++;
swap(arr, swapIdx, i);
}
}
// Swap the pivot from the start the swapPoint
swap(arr, start, swapIdx);
return swapIdx;
}
pivot([4,8,2,1,5,7,6,3])
pivot 에 start index 값 넣고 즉 위에서는 4임
swapIdx에는 start 0 값
for(var i = start + 1; i < arr.length; i++)는 i는 0이 아닌 start+1값이고, length 길이만큼
즉 4와 4를 비교한 다음 8로 넘어가는게 아니라 그냥 4로 지정한 뒤 바로 8로 넘어가면서 루프 시작
if(pivot > arr[i]) 피벗(4)와 arr[i] 즉 4와 8를 비교하여 더 클 경우 옮기는데, 이 경우는 그대로임. 반복문 한번 끝나고 다시 돌음
i++해서 피벗(4)와 arr[i] 즉 4와 2를 비교
2가 더 작기 때문에 if문 작동하고 swapIdx++ 해서 1됨
그리고 바꿔주면 되는데 swap 함수에 넣어서 위치 바꿔주기. 2가 현재 index 2에 위치하고 있는데, swapIdx는 1이고 i는 2이기 때문에 둘의 위치를 변경해줌
[4,8,2,1 ...] 에서 [4,2,8,1 ...]로 됨
계속해서 반복하여 [4,2,1,3...] 이렇게 위치하게 되고, 반복문 종료됨
그 다음 swap함수 한번더 동작하여서 arr의 start위치와 swapIdx 위치로 변경해줌
즉 4보다 작은 값이 3개 있기 때문에 swapIdx는 3이 되었고, start는 0이니 두개 위치를 바꾸면 [3,2,1,4...]로 변경
아래는 자세한 과정
요점은 마지막에 swapIdx를 반환하는 것임
실행하면 3을 반환함
pivot([4,8,2,1,5,7,6,3])
[4,8,2,1,5,7,6,3] // 4와 8
[4,2,8,1,5,7,6,3] // 4와 2 , 2와 8 위치 변경(swapIdx=1, i=2)
[4,2,1,8,5,7,6,3] // 4와 1 , 1과 8 위치 변경(swapIdx=2, i=3)
[4,2,1,8,5,7,6,3] // 4와 5 , 변경 없음 (i=4)
[4,2,1,8,5,7,6,3] // 4와 7 , 변경 없음 (i=5)
[4,2,1,8,5,7,6,3] // 4와 6 , 변경 없음 (i=6)
[4,2,1,3,5,7,6,8] // 4와 3 , 3과 8 위치 변경(swapIdx=3, i=7)
[3,2,1,4,5,7,6,8] // swapIdx랑 start 변경, 3과 4 위치 변경(swapIdx=3, start=0)
🐣 퀵 정렬 구현
설명
앞에서 살펴 본 것처럼 처음 4라는 피벗을 찾아 인덱스를 옮기는 과정까지 하고 나면
다음에 할 것은 퀵 정렬을 호출 하는 것
예를들어 [4,6,9,1,2,5,3]일 경우
[3,2,1,4,5,7,6,8]로 4가 이동하고 나면 [3,2,1] 즉 피벗인 4를 제외 한 앞에 것을 퀵 정렬