반응형
정렬 알고리즘 별 시간 복잡도
정렬 알고리즘 | 시간 복잡도 |
---|---|
Bubble Sort (버블 정렬) | O(n^2) |
Selection Sort (선택 정렬) | O(n^2) |
Insertion Sort (삽입 정렬) | O(n^2) |
Quick Sort (퀵 정렬) | 평균 : O(nlogn) / 최악 : O(n^2) |
Merge Sort (병합 정렬) | O(nlogn) |
Radix Sort (기수 정렬) | O(kn) / k : 최대 데이터의 자릿수 |
Arrays.sort() | 평균 : O(nlogn) / 최악 : O(n^2) |
Collections.sort() | O(nlogn) |
- 위 표를 보면 병합정렬이 퀵정렬보다 빠르다고 생각할 수 있음
- 최악의 상황에선 퀵정렬이 느리지만, 보통은 퀵정렬이 더 빠르고, 병합 정렬은 메모리를 더 많이 차지함
- 이제 이 정렬들의 개념과 코드 정리해보자
- 모든 개념들은 오름차순 정렬을 기준으로 정리
Bubble Sort
개념
- 순서대로 근접한 두 수를 비교해서 오른쪽 수가 왼쪽 수보다 더 작으면 교환
- 이 작업을 한 번 수행 할때마다 맨 끝자리에 가장 큰 수가 가게 됨
- 이 작업을 최대 n - 1 번 반복하면 정렬 완료 -> 시간복잡도 : O(n^2)
- ex) 3 4 1 5 2 를 정렬한다고 가정
- 1번째 loop => 3 4 1 5 2 -> 3 1 4 5 2 -> 3 1 4 2 5 => 5 확정
- 2번째 loop => 3 1 4 2 5 -> 1 3 4 2 5 -> 1 3 2 4 5 => 4 확정
- 3번째 loop => 1 3 2 4 5 -> 1 2 3 4 5 => 3확정
- 4번째 loop => 1 2 3 4 5 => 2확정, 1확정
코드
for(int i = 0 ; i < n - 1 ; i ++) {
boolean change = false;
for(int j = 0 ; j < n - 1 - i ; j ++){
if(arr[j] > arr[j + 1]) {
change = true;
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
if(change == false) break;
}
Selection Sort
개념
- for문을 통해 가장 작은 값을 찾고, 맨 앞자리와 교환
- 다음 for문에선 맨 앞자리값을 제외한 값 중 가장 작은 값을 찾고, 두번째 앞자리와 교환
- 이 작업을 최대 n - 1 번 반복하면 정렬 완료 -> 시간복잡도 : O(n^2)
- ex) 3 4 1 5 2 를 정렬한다고 가정
- 1번째 loop => 최소값 = 1 -> 1 4 3 5 2 => 1 확정
- 2번째 loop => 최소값 = 2 -> 1 2 3 5 4 => 2 확정
- 3번째 loop => 최소값 = 3 -> 1 2 3 5 4 => 3 확정
- 4번째 loop => 최소값 = 4 -> 1 2 3 4 5 => 4확정, 5확정
코드
for(int i = 0 ; i < n - 1 ; i ++) {
int min_index = i;
for(int j = i + 1 ; j < n ; j ++) {
if(arr[min_index] > arr[j]) {
min_index = j;
}
}
int temp = arr[min_index];
arr[min_index] = arr[i];
arr[i] = temp;
}
Insertion Sort
개념
- for문을 돌리면서 해당 index가 index 앞까지의 부분에서 삽입될 위치를 찾아 삽입
- 이 작업을 최대 n - 1 번 반복하면 정렬 완료 -> 시간복잡도 : O(n^2)
- 삽입될 위치를 찾을때 이진탐색으로 탐색하면 시간복잡도를 줄일 수 있음
- ex) 3 4 1 5 2 를 정렬한다고 가정
- 2번째 index = 4 -> 3 4 1 5 2
- 3번째 index = 1 -> 1은 3, 4 보다 앞에 삽입되야 함 -> 1 3 4 5 2
- 4번째 index = 5 -> 1 3 4 5 2
- 5번째 index = 2 -> 2는 1과 3 사이에 삽입되야 함 -> 1 2 3 4 5
코드
for(int i = 1 ; i < n ; i ++) {
int temp = arr[i];
for(int j = i ; j >= 0 ; j --) {
if(j == 0) {
arr[0] = temp;
}else if(arr[j - 1] <= temp) {
arr[j] = temp;
break;
} else {
arr[j] = arr[j - 1];
}
}
}
Quick Sort
개념
- pivot(기준점)을 정함 -> 아무기준으로 정해도 상관없음 -> 중간 지점을 기준점으로 지정하고 진행해봄
- quickSort(Start, End) 호출 -> 처음에는 Start = 0, End = n - 1
- Left는 Start에서 시작해 pivot보다 크거나 같은 수를 만날때까지 증가
- Rigth는 End에서 시작해 pivot보다 작거나 같은 수를 만날때까지 감소
- Left와 Right가 정해졌을 때
- Left <= Right 이면 두 수를 바꾸고, Left 증가, Right 감소 후 다시 Left, Right를 증감하며 구함
- Left > Right가 되었을 때
- Right > Start이면 quickSort(Start, Right) 호출
- Left < End이면 quickSort(Left, End) 호출 (구간 분리)
- 이 작업을 모든 분리된 구간의 길이가 1이 될 때 까지 진행 -> 정렬 완료
- 시간복잡도 : O(nlogn), 최악의 경우 : O(n^2)
- ex) 5 1 6 3 4 2 7 을 정렬한다고 가정
- quickSort(0,6) => Start = 0, End = 6, pivot = arr[ (0 + 6) / 2 ] = arr[ 3 ] = 3
- Left는 0에서 시작해서 3보다 큰 수를 만날때 까지 증가, 5를 만나면 멈춤 -> Left = 0
- Right는 6에서 시작해서 3보다 작은 수를 만날때 까지 감소, 2를 만나면 멈춤 -> Right = 5
- Left <= Right 이기 때문에 두 수를 바꾸면 2 1 6 3 4 5 7 이 되고, Left = 1, Right = 4 에서 다시 진행
- Left는 1에서 시작해서 6을 만나면 멈춤 -> Left = 2
- Right는 4에서 시작해서 3을 만나면 멈춤 -> Right = 3
- Left <= Right 이기 때문에 두 수를 바꾸면 2 1 3 6 4 5 7 이 되고, Left = 3, Right = 2 에서 다시 진행
- Left > Right 가 되었음 -> Start < Right, Left < End 임으로 quickSort(0,2), quickSort(3,6) 호출
- quickSort(0,2) => Start = 0, End = 2, pivot = arr[ (0 + 2) / 2] = arr[ 1 ] = 1
- Left는 0에서 시작해서 2를 만나면 멈춤 -> Left = 0
- Right는 2에서 시작해서 1을 만나면 멈춤 -> Right = 1
- Left <= Right 이기 때문에 두 수를 바꾸면 1 2 3 6 4 5 7 이 되고, Left = 1, Right = 0 에서 다시 진행
- Left > Right 가 되었음 -> Start = Right, Left < End 임으로 quickSort(1,2)만 호출
- quickSort(1,2) = > Start = 1, End = 2, pivot = arr[ (1 + 2) / 2 ] = arr [ 1 ] = 2
- Left는 1에서 시작해서 2를 만나면 멈춤 -> Left = 1
- Right는 2에서 시작해서 2를 만나면 멈춤 -> Right = 1
- Left <= Right 이기 때문에 두 수를 바꾸면 1 2 3 6 4 5 7 이 되고, Left = 2, Right = 0 에서 다시 진행
- Left > Right 가 되었음 -> Start > Right, Left = End 임으로 더 이상 호출 X
- 이 때의 결과값을 보면 1 2 3 6 4 5 7 인데, 이는 index 0~2 까지는 정렬된 것을 확인할 수 있음
- 마찬가지로 index 3~6까지도 다음과 같이 진행하면 index 0~6까지 모두 정렬 됨
코드
public static void quickSort(int Start, int End) {
if(Start >= End) { return; }
int pivot = arr[ ( Start + End ) / 2 ];
int Left = Start;
int Right = End;
while(Left <= Right) {
while(arr[Left] < pivot) Left ++;
while(arr[Right] > pivot) Right --;
if(Left <= Right) {
int temp = arr[Right];
arr[Right] = arr[Left];
arr[Left] = temp;
Left ++;
Right --;
}
}
if(Start < Right) quickSort(Start, Right);
if(Left < End) quickSort(Left, End);
}
시간 단축 방법
- Quick Sort는 pivot이 어떻게 정해지느냐에 따라 시간복잡도가 달라짐
- n개의 pivot이 모두 가장 작은 수 혹은 가장 큰 수로 뽑혔을 때 가장 오랙 걸리고 이 때의 시간 복잡도가 O(n^2)
- 따라서 pivot을 정하는 방식이 중요할 수 있음
- 위에서 구현한 코드는 중앙의 pivot을 무작위로 정했었는데 이 방법이 아닌 처음, 중간, 끝 값 중 2번째로 큰 값으로 pivot을 정하는 방법, 3개의 구간을 나눠 중간값을 구하고 그 값의 중간값을 pivot을 정하는 방법 등이 있음
Merge Sort
개념
- n개의 입력이 들어오면, n개의 그룹으로 나누고, 2개씩 그룹을 합치면서 동시에 정렬
- 1개의 그룹이 남을때 까지 반복하면 정렬 완료
- 시간복잡도 : O(nlogn)
- ex) 3 4 1 5 2 8 7 6 를 정렬한다고 가정
- 8개 그룹 -> 3, 4, 1, 5, 2, 8, 7, 6
- 4개 그룹 -> (3, 4), (1, 5), (2, 8), (6, 7)
- 2개 그룹 -> (1, 3, 4, 5), (2, 6, 7, 8)
- 1개 그룹 -> (1, 2, 3, 4, 5, 6, 7, 8)
- (1, 3, 4, 5) 그룹과 (2, 6, 7, 8) 그룹을 합치는 법
- 첫번째 그룹의 첫번째 숫자와 두번째 그룹의 첫번째 숫자를 비교했을 때, 1이 더 작음
- 합치는 그룹의 첫번째에는 1을 넣어줌
- 첫번째 그룹의 두번째 숫자와 두번째 그룹의 첫번째 숫자 비교 후 더 작은 숫자를 넣어줌
- 합치는 그룹의 두번째에는 2가 들어감
- 첫번째 그룹의 두번째 숫자와 두번째 그룹의 두번째 숫자 비교 후 더 작은 숫자를 넣어줌
- 이런 식으로 그룹을 합침
코드
public static void mergeSort(int Start, int End) {
if(End - Start < 1) return;
int Middle = ( End + Start ) / 2;
mergeSort(Start, Middle);
mergeSort(Middle + 1, End);
for(int i = Start ; i <= End ; i ++) {
temp[i] = arr[i];
}
int k = Start;
int x = Start;
int y = Middle + 1;
while(x <= Middle && y <= End) {
if(temp[x] < temp[y]) {
arr[k ++] = temp[x ++];
} else {
arr[k ++] = temp[y ++];
}
}
while(x <= Middle) {
arr[k ++] = temp[x ++];
}
while(y <= End) {
arr[k ++] = temp[y ++];
}
}
Radix Sort
개념
- 기수정렬은 입력되는 수의 개수가 많지만, 수의 범위가 적을때 사용하면 유용
- 10개의 Queue(FIFO)가 필요, 0~9번째 Queue라고 생각
- 처음에는 0번째 Queue에는 일의 자리가 0인숫자, 1번째 Queue에는 일의 자리가 1인 숫자, ... 이런 식으로 삽입
- 0번째 Queue부터 9번째 Queue까지 돌면서 순서대로 모두 poll해줌
- 다음에는 십의 자리 숫자 기준, 그 다음에는 백의 자리 숫자 기준 이런식으로 해줌
- 결과적으로 이 작업을 최대 k(최대 숫자의 자릿수)번 반복하면 정렬 완료
- 시간복잡도 : O(kn)
- ex) 195 10 5 7 25 61 255 36 4 를 정렬한다고 가정
- 일의 자리 기준
- 0번째 Queue : 10
- 1번째 Queue : 61
- 4번째 Queue : 4
- 5번째 Queue : 195 5 25 255
- 6번째 Queue : 36
- 7번째 Queue : 7
- 2, 3, 8, 9번째 Queue : 비어있음
- Queue 순서대로 모두 poll 하면 10 61 4 195 5 25 255 36 7 이 됨
- 십의 자리 기준으로 같은 작업을 마치고 나면 4 5 7 10 25 36 255 61 195
- 백의 자리 기준으로 같은 작업을 마치고 나면 4 5 7 10 25 36 61 195 255
- 정렬 완료
- 일의 자리 기준
코드
// 자리수 구하기
int max = 0, max_size = 1;
while(max >= 10) {
max_size ++;
max /= 10;
}
// Queue 배열 생성
LinkedList[] queues = new LinkedList[10];
for(int i = 0 ; i <= 9 ; i ++) {
queues[i] = new LinkedList();
}
// 기수정렬
int t = 1, index;
for(int i = 1 ; i <= max_size ; i ++) {
for(int j = 0 ; j < n ; j ++) {
queues[ ( arr[j] / t ) % 10 ].add(arr[j]);
}
index = 0;
for(int j = 0 ; j <= 9 ; j ++) {
while(!queues[j].isEmpty()) {
arr[index ++] = Integer.parseInt( queues[j].poll().toString() );
}
}
t *= 10;
}
Arrays.sort()
개념
- java.util.Arrays 클래스에서 제공하는 Method
- Dual Pivot QuickSort를 사용한 정렬 방식으로 보통 QuickSort보다 더 빠르지만, 최악의 경우 O(n^2)이 발생할 수 있음
코드
Arrays.sort(arr);
Collections.sort()
개념
- java.util.Collections 클래스에서 제공하는 Method
- TimeSort 라는 삽입정렬과 병합정렬을 결합한 정렬방식을 사용
- 시간복잡도는 평균, 최악의 상황에 상관없이 O(nlogn)을 갖음
- 대신 배열에서 사용할 수 없고, List, Map 등의 컬렉션 프레임워크의 구조에서 사용 가능
코드
Collections.sort(numList);
Arrays.sort() : Index와 같이 정렬
- 코딩을 하게 되면 Arrays.sort()를 쓰는 상황이 매우 많음
- 만약 정렬을 해야되는데 정렬된 값이 정렬 전에 몇번째 index였는지 알아야 되는 상황이 발생할 수도 있음
- 이런 상황에서는 이 방법을 사용해 index와 값을 같이 묶어 정렬하면 됨
import java.util.Arrays;
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
myFormat[] arr = new myFormat[n];
for(int i = 0 ; i < n ; i ++) {
arr[i] = new myFormat(i, sc.nextInt());
}
Arrays.sort(arr);
for(int i = 0 ; i < n ; i ++) {
System.out.println(arr[i].value + " ( 정렬 전 index : " + arr[i].index + ")");
}
}
public static class myFormat implements Comparable<myFormat> {
int index, value;
public myFormat(int index, int value) {
this.index = index;
this.value = value;
}
@Override
public int compareTo(myFormat o) {
return this.value - o.value;
}
}
}
결과
5
1 4 3 2 5
1 ( 정렬 전 index : 0)
2 ( 정렬 전 index : 3)
3 ( 정렬 전 index : 2)
4 ( 정렬 전 index : 1)
5 ( 정렬 전 index : 4)
반응형
'JAVA > 알고리즘 개념 정리' 카테고리의 다른 글
[JAVA] 플로이드-워셜 알고리즘 (Floyd-Warshall) (0) | 2022.06.05 |
---|---|
[JAVA] 벨만포드 알고리즘 (Bellman-Ford) (0) | 2022.06.05 |
[JAVA] 다익스트라 (Dijkstra) (+ Priority Queue을 사용한 시간 단축) (0) | 2022.06.04 |
[JAVA] DFS, BFS (0) | 2022.05.31 |
[JAVA] 재귀함수 (0) | 2021.08.17 |