21-03-05 알고리즘 강의 노트정리
이진검색
- 정렬된 리스트에서 특정한 값의 위치를 찾는 알고리즘
- 전화번호부 반으로 찢어 찾기 (from CS50)
- True/False 결과로 최적해를 탐색하는 방법
- 탐색 배열의 가운데 인덱스를 구함
- 만약 가운데 값이 해당 값과 같다면 찾은 것
- 주어지는 배열은 정렬되어 있으므로 가운데 값이 해당 값보다 작다면 우측 반을 검색범위로 지정 (mid+1, high)
- 반대의 경우라면 (low, mid+1)로 검색범위를 지정
- 위 과정을 반복하여 값을 찾음 (단, low > high가 된다면 해당 값이 배열에 없는 경우)
while문을 사용한 이진검색
1 | const binarySearch = ( |
재귀를 사용한 이진검색
1 | const binarySearchRecursive = ( |
True/False 결과로 최적해를 탐색하는 방법
- 구슬의 개수 N과 그룹의 수 M이 주어질 때 임의의 그룹 내 요소들의 최대값이 최소가 되게 하는 문제
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67const check = mid => {
let group = 1, sum = 0;
for (let i = 0; i < N; ++i) {
// 첫번째 구슬부터 더하여 mid값보다 커지면 그룹화하고 새 그룹으로 넘어감
if (sum + arr[i] > mid) {
// mid가 10이라 가정하고 1, 2번째 값의 합이 8이 되고
// arr[2]값이 4라면 sum + arr[2] = 12가 되어 8보다 커지므로
// sum값을 arr[2]값으로 초기화하고 그룹 수를 늘린다
sum = arr[i];
group++;
} else {
// 만약 합이 최대값을 넘기지 않는다면 더해주어도 괜찮음
sum += arr[i]
}
}
return group <= M
}
let low = 0, high = 0;
let N = 8, M = 3
let arr = [5, 4, 2, 6, 9, 3, 8, 7]
const input = () => {
console.log(`${N}개의 구슬을 ${M}개의 그룹으로 나누어야 합니다`)
for (let i = 0; i < N; ++i){
// console.log(arr[i])
// 각 구슬 중에 최소값을 구함
if (low < arr[i]) low = arr[i];
// 구슬의 총합을 최대값으로 구함
high += arr[i];
}
}
const output = () => {
console.log(answer);
let count = 0, sum = 0, limit = M; // limit: 그룹 수 제한
for (let i = 0; i <= N; ++i) {
// 구한 해답이 arr[i]번째까지의 합보다 크거나
// 한 그룹당 하나 이상의 구슬이 있어야 하므로 N - M < i 보다 작아야 한다
if (sum + arr[i] > answer || N - i < limit) {
console.log(`그룹${i+1}:${count}`) // 그룹 수를 출력
count = sum = 0; // 초기화
limit--; // 남은 그룹 수 줄이기
}
sum += arr[i];
count++;
}
}
const process = () => {
while (low <= high) {
let mid = Math.floor((low + high) / 2);
if (check(mid)) {
answer = mid;
high = mid - 1;
}
// group <= M 가 false를 리턴 할 때 (그룹 수가 M을 넘어갈 때)
else low = mid + 1
}
}
const solution = () => {
input();
process();
output();
}
solution(); - 배우면서 이해가기 어려웠던 점
- mid값은 index값이 아닌가? 왜 mid를 최대값으로 설정하여 check를 할까?
→ mid값은 최소값 + 최대값의 중간값
→ check 함수는 group <= M의 boolean 값을 리턴하므로 이 함수가 True값을 리턴할 때만 구슬의 합을 계산하면 된다
- mid값은 index값이 아닌가? 왜 mid를 최대값으로 설정하여 check를 할까?
책 복사하기
- 주어진 대본들의 페이지(m[])를 k명의 서기공이 한 파트 이상 복사할 때 가장 많은 페이지를 맡은 서기공의 페이지를 구하는 문제
- 답이 두 개 이상 있다면 첫 번째 서기공이 할 일이 가장 작아야 하며 두 번째 서기공이 할 일이 작아야 한다
- 입력값이 100,000으로 커서 순차 탐색보다 이진 탐색을 사용해야 한다
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54let arr = [100, 200, 300, 400, 500, 600, 700, 800, 900];
let M = arr.length, K = 3;
let low = 0, high = 0;
const input = () => {
console.log(`${M}개로 나누어진 대본을 ${K}명의 서기공이 나누어 일을 해야 합니다`);
for (let i = 0; i < M; ++i) {
// 숫자 구슬과 유사함
if (low < arr[i]) low = arr[i];
high += arr[i];
}
}
const check = mid => {
let group = 1, sum = 0;
for (let i = 0; i < M; ++i) {
if (sum + arr[i] > mid) {
sum = arr[i];
group++;
} else sum += arr[i];
}
return group <= K
}
const process = () => {
while (low <= high) {
let mid = Math.floor((low + high)/2);
if (check(mid)) {
answer = mid;
high = mid - 1
} else low = mid + 1
}
}
const output = (index, sum, people) => {
if (index < 0) return;
if (sum + arr[index] > low || index < people) {
output(index - 1, arr[index], people - 1);
console.log(`${arr[index]} / `)
} else {
output(index -1, sum + arr[index], people);
console.log(`${arr[index]} `)
}
}
const solution = () => {
input();
process();
output(M - 1, 0, K - 1);
// 배열의 마지막 번째(M-1)값부터 탐색해 들어가면 재귀를 통해 첫번째 index부터 실행된다
// 사람이 K명일때는 "/"가 K-1개 들어가야 하므로 K-1번만 수행한다
}
solution(); - 숫자 구슬 문제를 이해하고 나면 생각보다 이해하기 쉬웠음
- 재귀적으로 호출될 때 마지막부터 탐색한다면 LIFO 콜스택에 의해 가장 먼저 출력되는 것은 0번째 index일 것이다 (탈출구문은 0보다 작을 때로 작성)