Skip to content

Commit 8ce080f

Browse files
committed
algorithm: 병합 정렬
Merge Sort
1 parent b9a64d1 commit 8ce080f

File tree

2 files changed

+144
-1
lines changed

2 files changed

+144
-1
lines changed

ComputerScience/DataStructures&Algorithms/AlgorithmStudy/AlgorithmStudy/main.swift

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -686,4 +686,49 @@ func solution13460() {
686686

687687
//solution2169()
688688

689-
solution2661()
689+
//solution2661()
690+
691+
var array: [Int] = []
692+
693+
(0...10).forEach { array.append(Int.random(in: $0...1000)) }
694+
695+
print(array)
696+
697+
print(mergeSort(array))
698+
699+
func mergeSort(_ array: [Int]) -> [Int] {
700+
if array.count <= 1 { return array }
701+
let midIndex = array.count / 2
702+
let left = Array(array[0..<midIndex])
703+
let right = Array(array[midIndex..<array.count])
704+
705+
return merge(mergeSort(left), mergeSort(right))
706+
}
707+
708+
func merge(_ left: [Int], _ right: [Int]) -> [Int] {
709+
var result: [Int] = []
710+
var i = 0
711+
var j = 0
712+
713+
while i < left.count && j < right.count {
714+
if left[i] <= right[j] {
715+
result.append(left[i])
716+
i += 1
717+
} else {
718+
result.append(right[j])
719+
j += 1
720+
}
721+
}
722+
723+
while i < left.count {
724+
result.append(left[i])
725+
i += 1
726+
}
727+
728+
while j < right.count {
729+
result.append(right[j])
730+
j += 1
731+
}
732+
733+
return result
734+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# 병합 정렬(Merge Sort)
2+
3+
분할 정복(Divide and Conquer) 알고리즘을 기반으로 한 정렬 알고리즘.
4+
5+
합병 정렬이라고도 부르며, 퀵 정렬과 함께 많이 언급되는 정렬 방식.
6+
7+
분할 정복이란 말 그대로 큰 문제를 나눌 수 없을 때까지 나누어서 풀고, 각각의 해답을 합쳐서 전체 문제를 해결하는 알고리즘 기법.
8+
9+
## 기본 로직
10+
11+
1. 배열을 절반으로 나눈다.
12+
2. 이후 나온 배열들도 절반 씩 나눈다.
13+
3. 1, 2를 반복하며 나눌 수 있을 때 까지(크기가 1 또는 0) 나눈다.
14+
4. 이 후 나온 요소들을 2개 씩 병합하는데, 이 때 정렬(크기비교)를 하여 병합한다.
15+
5. 4를 반복 병합하여 하나의 배열이 나올 때까지 반복한다.
16+
17+
### 코드
18+
19+
```swift
20+
func mergeSort(_ array: [Int]) -> [Int] {
21+
if array.count <= 1 { return array }
22+
let midIndex = array.count / 2
23+
let left = Array(array[0..<midIndex])
24+
let right = Array(array[midIndex..<array.count])
25+
26+
return merge(mergeSort(left), mergeSort(right))
27+
}
28+
29+
func merge(_ left: [Int], _ right: [Int]) -> [Int] {
30+
var result: [Int] = []
31+
var i = 0
32+
var j = 0
33+
34+
while i < left.count && j < right.count {
35+
if left[i] <= right[j] {
36+
result.append(left[i])
37+
i += 1
38+
} else {
39+
result.append(right[j])
40+
j += 1
41+
}
42+
}
43+
44+
while i < left.count {
45+
result.append(left[i])
46+
i += 1
47+
}
48+
49+
while j < right.count {
50+
result.append(right[j])
51+
j += 1
52+
}
53+
54+
return result
55+
}
56+
57+
```
58+
59+
## 시간 복잡도
60+
61+
- **평균, 최선, 최악 모두의 경우 O(nlog₂n)**
62+
- 배열을 나누는 횟수, 합병(merge)하는 횟수 `(log₂n)`
63+
- 배열의 크기 n에 따라 2개로 나누어 깊어지기 때문에 일반화 할 경우 n = 2^k, k = log₂n 로 표현할 수 있다.
64+
- 이는 즉 순환 호출의 깊이, 순환 호출의 횟수이다.
65+
- 각 합병 단계의 비교 연산 `(n)`
66+
- 각 합병 단계에서 n/2 배열 1쌍을 n번 비교, n/4 배열 2쌍 n/2번 비교이기에 일반화 할 경우 n/2 * 2, n/4 * 4 즉 n으로 일반화 된다.
67+
-`(log₂n)` + `(nlog₂n)` 으로 표현할 수 있는데, 이를 빅오표기법을 이용한다면 시간복잡도는 **`O(nlog₂n)`**이 된다.
68+
69+
## 특징
70+
71+
**합병 정렬은 LinkedList를 정렬할 대 사용하면 매우 효율적이다.**
72+
73+
LinkedList는 삽입, 삭제 연산에 유용하지만 접근 연산에는 비효율 적이다.
74+
75+
이에 합병정렬은 순차적인 비교로 정렬을 하기 때문에 효율적이지만,
76+
77+
`+`
78+
79+
퀵정렬을 이용해 LinkedList를 정렬 한다면 성능이 좋지 않음.(순차 접근이 아니기 때문에)
80+
81+
임의 접근하는 퀵소트를 사용한다면 오버헤드 발생이 증가한다.
82+
83+
### 퀵정렬과의 차이점
84+
85+
- 퀵정렬: 피벗을 이용해 정렬 후 → 영역을 나눈다. → 그 이후 합친다.
86+
- 병합정렬: 영역을 나눌 수 있을 만큼 나눈 후 → 정렬하면서 합친다.
87+
88+
### **장점**
89+
90+
- 안정 정렬이다.
91+
- **LinkedList 정렬의 효과적이다.**
92+
- 이를 통해 in-place 정렬 구현할 수 있다.
93+
- 즉, **큰 용량의 데이터를 정렬할 경우 연결리스트와 함께 사용한다면, 매우 효과적이다.**
94+
95+
### 단점
96+
97+
- 배열로 구성할 경우 임시 배열이 필요해, in-place 정렬이 아니다.
98+
- 데이터의 용량이 클 경우, 이동 횟수가 많아저 시간적 낭비가 일어난다.

0 commit comments

Comments
 (0)