티스토리 뷰
배열이 주어졌을 때, 임의로 기준값을 정하여
기준보다 작은 수와 큰 수들의 집합으로 쪼개는 과정을
각 집합이 더이상 쪼개질 수 없을 때까지 반복한 후,
나누어진 집합들을 각각 정렬한 후 병합하는 알고리즘
Pivot?
noun. a fixed point supporting something that turns or balances
명사. 변하거나 균형을 맞추는 것들을 받쳐주는 고정된 지점
Quick Sort 적용 과정
1단계: 대소비교를 통해 세 개 내지는 두 개의 리스트로 분리,
각 리스트 내에는 기준값보다 크고 작은 수들이 모여 있도록 함.
import java.util.ArrayList;
import java.util.List;
public class QuickSort {
/* 1. 배열의 중간(크기 / 2)에 있는 원소를 기준 값으로 잡는다.
* 2. 모든 원소들과 비교하면서 기준 값보다 작은 값들은 left 에, 큰 값들은 right 에 넣는다.
* 3. 정렬해야 하는 배열의 크기는 계속해서 반으로 줄게 된다. (재귀 또는 List 내 원소 교환) */
public void divideIntoThree(int[] arrToBeSorted) {
int pivotIdx = arrToBeSorted.length / 2;
int pivot = arrToBeSorted[pivotIdx];
List<Integer> right = new ArrayList<>();
List<Integer> left = new ArrayList<>();
List<Integer> centre = new ArrayList<>();
for (int i : arrToBeSorted) {
if (pivot < i) right.add(i);
else if (pivot > i) left.add(i);
else centre.add(i);
}
System.out.print(left + "\n" + centre + "\n" + right);
}
public static void main(String[] args) {
QuickSort quickSort = new QuickSort();
int[] testArr = {20, 18, 5, 19, 40, 50, 5, 25};
quickSort.divideIntoThree(testArr);
}
}
여기에 보면 pivot 도 리스트에 담았다. 이는 pivot 이 기준값이 되면서 동시에 중복된 수인 경우 를 고려하여 사용한 방식이다. 기준값과 중복되는 값은 centre에 pivot 과 함께 저장될 것이다. centre은 정렬할 필요도 없고, 위치가 바뀔 일도 없다.
2단계: 재귀 호출 도입
package algorithms.quicksort;
import java.util.ArrayList;
import java.util.List;
public class QuickSort {
/* 1. 배열의 중간(크기 / 2)에 있는 원소를 기준 값으로 잡는다.
* 2. 모든 원소들과 비교하면서 기준 값보다 작은 값들은 left 에, 큰 값들은 right 에 넣는다.
* 3. 정렬해야 하는 배열의 크기는 계속해서 반으로 줄게 된다. (재귀 또는 List 내 원소 교환)
* 질문: 만약 중복되는 수가 기준값이 되었다면 분류할 수 없는데 어떤 방법을 써서 정렬할 수 있을까?
* 답: 어차피 중복되는 수 두개는 붙어있어야 하므로 두 개가 한 곳에 같이 있어도 상관 없다. */
public List<Integer> divideIntoThree(List<Integer> arrToBeSorted) {
if (arrToBeSorted.size() <= 1 || arrToBeSorted.stream().distinct().count() == 1)
return arrToBeSorted; // 더 이상 쪼갤 수 없을 때 현재 리스트를 리턴한다.
int pivotIdx = arrToBeSorted.size() / 2;
int pivot = arrToBeSorted.get(pivotIdx);
List<Integer> left = new ArrayList<>();
List<Integer> right = new ArrayList<>();
List<Integer> centre = new ArrayList<>();
for (int i : arrToBeSorted) {
if (pivot < i) right.add(i);
else if (pivot > i) left.add(i);
else centre.add(i);
}
// 리턴할 때 재귀적으로 나누어진 배열들을 더 쪼개고, 마지막에 병합함으로서 정렬한다. 이 부분을 외부 메소드로 분리해도 된다.
List<Integer> processed = divideIntoThree(left); // 결국 원소 하나의 리스트가 될 때까지 쪼개고 병합한 후에 processed 에 결과가 할당된다.
System.out.println("left:\n" + processed);
processed.addAll(divideIntoThree(centre)); // 정렬된 중간 리스트를 더한다.
System.out.println("centre:\n" + processed);
processed.addAll(divideIntoThree(right)); // 정렬된 오른쪽 리스트를 더한다.
System.out.println("right:\n" + processed);
System.out.println("--------------------");
return processed;
}
public static void main(String[] args) {
QuickSort quickSort = new QuickSort();
List<Integer> testArr = List.of(20, 18, 5, 19, 40, 50, 5, 25);
System.out.println(quickSort.divideIntoThree(testArr));
}
}
BASE CASE (탈출 조건)
중복된 수가 있을 때를 유의해서 조건을 설정해 주어야 한다.
예를 들어, 위 알고리즘에서는 아무리 리스트의 사이즈가 크더라도 모두 같은 수이면 더 이상 정렬할 여지가 없으며,
절대 사이즈가 1보다 작거나 같아질 여지가 없다.
즉, base case 에 도달하지 못하고 무한 루프에 빠지는 문제가 생긴다.
그래서 Base case 에 만일 모든 수가 중복되는 경우에는 더 이상 정렬이 필요하지 않다는 조건을 추가했다.
+ 나중에 알고 보니 내 코드가 centre, 즉 1단계에서 언급한 정렬이 필요없는 pivot 과 중복되는 수의 리스트 또한 재귀적으로 정렬하려고 했던 것이 문제였다. divideIntoThree(centre) 이 아니라 그냥 centre를 addAll 해주면 Base case 를 단순히 1보다 작은 경우에 리턴하는 조건으로만 설정해주어도 오류가 나지 않고 정렬이 된다.
+ 하지만 결국 모든 원소가 같은 리스트는 나타날 가능성이 있고, 밑의 for 문을 들어가지 않고 정렬이 필요 없는 리스트를 한 번 필터링해주는 원래의 조건은 유지하는 것이 효율적인 구현 방법이다.
Q. 컴퓨터는 그럼 재귀를 한 줄을 먼저 다 처리하고 돌아와서 두 번째 줄을 처리할까요??
A.실험해본다.재귀는 호출 스택을 사용한다. Stack 은 LIFO 를 따르기 때문에 결국 처음 호출 스택으로 들어간 함수들 (메소드들)이 리턴을 만나 해결되지 않으면 절대 그 이후에 들어간 함수들의 결과값을 낼 수 없다.
그림 1-1 스택 구조로 호출되는 함수
3단계: 리팩토링 - 마지막 병합 부분을 외부 메서드로 분리해서 구현함.
import java.util.ArrayList;
import java.util.List;
public class QuickSort {
/* 1. 배열의 중간(크기 / 2)에 있는 원소를 기준 값으로 잡는다.
* 2. 모든 원소들과 비교하면서 기준 값보다 작은 값들은 left 에, 큰 값들은 right 에 넣는다.
* 3. 정렬해야 하는 배열의 크기는 계속해서 반으로 줄게 된다. (재귀 또는 List 내 원소 교환)
* 질문: 만약 중복되는 수가 기준값이 되었다면 분류할 수 없는데 어떤 방법을 써서 정렬할 수 있을까?
* 답: 어차피 중복되는 수 두개는 붙어있어야 하므로 두 개가 한 곳에 같이 있어도 상관 없다. */
// 리턴할 때 재귀적으로 나누어진 배열들을 더 쪼개고, 마지막에 병합함으로서 정렬한다. 이 부분을 외부 메소드로 분리해도 된다.
private List<Integer> merge(List<Integer> left, List<Integer> centre, List<Integer> right) {
List<Integer> processed = divideIntoThree(left); // 결국 원소 하나의 리스트가 될 때까지 쪼개고 병합한 후에 processed 에 결과가 할당된다.
System.out.println("left:\n" + processed);
processed.addAll(centre); // 정렬된 중간 리스트를 더한다.
System.out.println("centre:\n" + processed);
processed.addAll(divideIntoThree(right)); // 정렬된 오른쪽 리스트를 더한다.
System.out.println("right:\n" + processed);
System.out.println("--------------------");
return processed;
}
public List<Integer> divideIntoThree(List<Integer> arrToBeSorted) {
if (arrToBeSorted.size() <= 1 || arrToBeSorted.stream().distinct().count() == 1)
return arrToBeSorted; // 더 이상 쪼갤 수 없을 때 현재 리스트를 리턴한다.
int pivotIdx = arrToBeSorted.size() / 2;
int pivot = arrToBeSorted.get(pivotIdx);
List<Integer> left = new ArrayList<>();
List<Integer> right = new ArrayList<>();
List<Integer> centre = new ArrayList<>();
for (int i : arrToBeSorted) {
if (pivot < i) right.add(i);
else if (pivot > i) left.add(i);
else centre.add(i);
}
return merge(left, centre, right);
}
public static void main(String[] args) {
QuickSort quickSort = new QuickSort();
List<Integer> testArr = List.of(20, 18, 5, 19, 40, 50, 5, 25);
System.out.println(quickSort.divideIntoThree(testArr));
}
}
! 배운 사실
- 알고리즘을 풀 때는 먼저 내가 짠 로직이 어떤 값을 출력할지 생각해볼 필요가 있다.
'Algorithms' 카테고리의 다른 글
[알고리즘] Algorithms Study with JAVA - Backtracking (0) | 2022.11.18 |
---|---|
[알고리즘] Quick Sort: 구현 (Array 2단계) (0) | 2022.11.17 |
[알고리즘] Quick Sort: 이론 (Array 1단계) (0) | 2022.11.16 |
[알고리즘] JAVA programming language - 2: Queue data structure (0) | 2022.08.05 |
[알고리즘] JAVA programming language - 1: Stack data structure (0) | 2022.07.14 |
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- Firebase
- Java Data Types
- gitlab
- 코테
- Jackson
- DTO
- 지연 로딩
- 실시간데이터
- DeSerialization
- 가상 서버
- Spring Boot
- 도커
- 인증/인가
- google cloud
- docker
- LazyInitializationException
- N+1
- FCM
- JOIN FETCH
- JPQL
- JPA
- json web token
- 기지국 설치
- 알고리즘
- @RequestBody
- 깃랩
- 역직렬화
- spring
- 프로그래머스
- ci/cd
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
글 보관함