필자의 대학 코스 Advanced Programming에서 사용된 예시를 발췌, 정리했습니다.
Calculate Power
- Base Case (y = 0)
- \[x^{0} = 1\]
- Recursive Case (y >= 1)
- \[x^{y} = x \times x^{y-1}\]
1
2
3
4
5
6
7
unsigned long power(unsigned int x, unsigned int y)
{
if (y == 0)
return 1;
else
return x * power(x, y - 1);
}
하지만 이 코드는 y
만큼의 스택을 생성하기에 Stack Overflow가 발생할 가능성이 다분하다. 따라서 다음과 같이 코드를 작성하여 해결할 수도 있다.
- Base Case (y = 0)
- \[x^{0} = 1\]
- Recursive Case (y >= 1)
- \[x^{y} = \begin{cases} x \times x^{y-1} & \text{ if y = odd } \\ x^{y/2} \times x^{y/2} & \text{ if y = even } \end{cases}\]
1
2
3
4
5
6
7
8
9
10
11
unsigned long power(unsigned int x, unsigned int y)
{
if (y == 0)
return 1;
else
if (y % 2 == 0)
unsigned long x_y2 = power(x, y / 2);
return x_y2 * x_y2;
else
return x * power(x, y - 1);
}
가능하면 Reduce by RATIO, not by CONSTANT.
Calculate Nth Fibonacci Number
- Base Case (n = 0 or 1)
- \[\text{fib(0)} = 0\]
- \[\text{fib(1)} = 1\]
- Recursive Case (n >= 2)
- \[\text{fib(n)} = \text{fib(n - 1)} + \text{fib(n - 2)}\]
1
2
3
4
5
6
unsigned long fib(int n)
{
if (n <= 1)
return n;
return fib(n - 1) + fib(n - 2);
}
Calculate N Factorial
- Base Case (n = 0)
- \[\text{fac(0)} = 1\]
- Recursive Case (n >= 1)
- \[\text{fac(n)} = \text{n} \times \text{fac(n - 1)}\]
1
2
3
4
5
6
unsigned long fac(int n)
{
if (n == 0)
return 1;
return n * fac(n - 1);
}
Find a Square Root using a Binary Search
What is Binary Search or Bisection Method?
- Choose an initial lower boundary and an upper boundary for the ANSWER.
- Progressively narrow boundaries, until they are narrow enough for our purpose.
예를 들어, 7의 제곱근을 찾는다고 해보자.
- 7의 제곱근은 0에서 7 사이에 있을거야.
- Midpoint(= 3.5)를 시도해보자.
\(3.5^{2} = 12.25\) - 12.25는 7보다 크므로 7의 제곱근은 0에서 3.5 사이에 있을거야.
- Midpoint(= 1.75)를 시도해보자.
\(1.75^{2} = 3.0625\) - 3.0625는 7보다 작으므로 7의 제곱근은 1.75에서 3.5 사이에 있을거야.
- Midpoint(= 2.625)를 시도해보자.
\(2.625^{2} = 6.890625\) - 6.890625는 7보다 작으므로 7의 제곱근은 2.625에서 3.5 사이에 있을거야.
- Midpoint(= 3.0625)를 시도해보자.
\(3.0625^{2} = 9.37890625\) - 9.37890625는 7보다 크므로 7의 제곱근은 2.625에서 3.0625 사이에 있을거야.
- Midpoint를 시도해보자.
- …
이러한 과정이 바로 Binary Search 이다!
- Base Case (close enough to answer)
- \[\text{return midpoint;} \text{ if } \text{midpoint}^{2} = \text{n}\]
- Recursive Case (not close enough to answer)
- \[\begin{cases} \text{return binary_search(n, lower, midpoint)} & \text{ if } \text{midpoint}^{2} > \text{n} \\ \text{return binary_search(n, midpoint, upper)} & \text{ if } \text{midpoint}^{2} < \text{n} \end{cases}\]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
double binary_search(double n, double lower, double upper)
{
double mid = (lower + upper) / 2;
double mid2 = mid * mid;
if (fabs(mid2 - n) <= 0.001) // #include<cmath>
return mid;
else
{
if (mid2 > n)
return binary_search(n, lower, mid);
else
return binary_search(n, mid, upper);
}
}
double square_root(double val)
{
return binary_search(val, 0, val);
}
위와 같은 방식으로 인자를 3개 받는 함수를 실행하기 위해서 그 함수를 실행시키는 것이 아니라, 인자를 하나 받는 함수를 구현한 후 인자를 3개 받는 함수를 호출할 수도 있다.
Is Palindrome?
Palindrome이란 앞으로, 뒤로 읽어도 동일한 문자열을 말한다. 예를 들어 ‘radar’, ‘noon’ 등이 이에 속한다.
c_str()
method- C 스타일의
string
을 C++에서 사용할 수 있도록 해준다. 이들은 문자열의 첫번째 글자를 가리키는 포인터ptr
과 문자열의 길이len
으로 구성된다.
- Base Case (len <= 1)
- \[\text{ return True } \text{ if } \text{ len } <= 1\]
- Recursive Case (len >= 2) (양끝이 동일 and 내부 문자열이 palindrome)
- \[\text{return (ptr[0] == ptr[len -1]) and is_palindrome(ptr + 1, len - 2)}\]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
bool is_palindrome(char const* ptr, size_t len)
{
if (len <= 1)
return true;
return (ptr[0] == ptr[len - 1] && is_palindrome(ptr + 1, len - 2));
}
int main()
{
string x;
getline(cin, x);
cout << is_palindrome(x.c_str(), x.length()) << endl;
return 0;
}
getline()
,c_str()
,length()
메소드의 사용법을 잘 숙지해놓자.
gdb [executable]
을 console에 입력함으로써 재귀함수의 스택을 추적할 수 있다.
Print Tower of Hanoi Solution
Tower of Hanoi은 한 봉에 있는 서로 다른 크기의 Disk들을 다른 봉으로 옮기는 문제이다.
Rules
- 한 번에 하나의 Disk를 이동 가능
- 쌓여있는 Disk들 중 가장 위의 Disk만 이동 가능
- Disk는 자신보다 크기가 큰 Disk 위에만 놓일 수 있음
- Base Case (1 Disk)
- \[\text{print(from → to) } \text{ if } \text{1 Disk}\]
- Recursive Case
- \(\text{1. move(n - 1 disks from → other)}\) \
- \(\text{2. move(1 disk from → to)}\) \
- \(\text{3. move(n - 1 disks other → to)}\) \
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void move_discs(int num_discs, int from, int to)
{
if (num_discs == 1)
cout << "from " << from << " to " << to << endl;
else
{
// 0. other rod 찾기
int other;
for (other = 1; other == from || other == to; other++)
;
// 1. move (n - 1 disks from → other)
move_discs(num_discs - 1, from, other);
// 2. move (1 disk from → to)
cout << "from " << from << " to " << to << endl;
// 3. move (n - 1 disks other → to)
move_discs(num_discs - 1, other, to);
}
}
ㄷㄷ 너무 소름돋는다…! 잘 보이지 않더라도 무지성으로 적으면 해결이 되다니!
Solving Simple Sudoku
Backtracking에 대하여… Backtracking (choose-explore-unchoose) : 어떤 경우가 나올지 모르는 경우를 선택 - 직접 해보며 되는지 안되는지 관찰 - 안되면 Undo하는 과정을 말한다.
3X3 스도쿠가 있을 때,
- 빈 칸에 특정 숫자(1~3)을 넣어보고
- 오류가 있으면 (illegal state) 빡구 (backtrack)
- 오류가 없으면 Winnning State, 종료
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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
// BLANK는 -1로 채움
#define BLANK -1
// 각 행들에 중복되는 원소가 없는지 체크
bool has_invalid_row(int const** board)
{
for (int row = 0; row < 3; row++)
{
// 등장한 원소는 true로 바꿀 거임
bool seen[3] = { false, false, false };
for (int col = 0; col < 3; col++)
{
if (board[row][col] != BLANK) // 채워져있는 칸에 대하여
if (seen[board[row][col]]) // 만약 본 게 또 채워져 있으면
return true; // invalid
else
seen[board[row][col]] = true; // 못 본 놈이면 봤다고 체크하기
}
}
return false; // 모든 원소에 대하여 문제 없으면 valid
}
// 각 열들에 중복되는 원소가 없는지 체크
bool has_invalid_column(int const** board)
{
for (int col = 0; col < 3; col++)
{
// 등장한 원소는 true로 바꿀 거임
bool seen[3] = { false, false, false };
for (int row = 0; row < 3; row++)
{
if (board[row][col] != BLANK) // 채워져있는 칸에 대하여
if (seen[board[row][col]]) // 만약 본 게 또 채워져 있으면
return true; // invalid
else
seen[board[row][col]] = true; // 못 본 놈이면 봤다고 체크하기
}
}
return false; // 모든 원소에 대하여 문제 없으면 valid
}
bool is_invalid(int const** board)
{
return has_invalid_row(board) || has_invalid_column(board);
}
bool rows_win(int const** board)
{
for (int row = 0; row < 3; row++)
{
bool seen[3] = { false, false, false };
for (int col = 0; col < 3; col++)
if (board[row][col] != BLANK)
seen[board[row][col]] = true; // 모든 원소에 대하여 본 놈들은 true로 바꾸기
for (int i = 0; i < 3; i++)
if (!seen[i]) // 만약 못 본 원소가 있다면 invalid
return false;
}
return true; // 못 본 원소없이 모두 봤으면 win!
}
bool columns_win(int const** board)
{
for (int col = 0; col < 3; col++)
{
bool seen[3] = { false, false, false };
for (int row = 0; row < 3; row++)
if (board[row][col] != BLANK)
seen[board[row][col]] = true; // 모든 원소에 대하여 본 놈들은 true로 바꾸기
for (int i = 0; i < 3; i++)
if (!seen[i]) // 만약 못 본 원소가 있다면 invalid
return false;
}
return true; // 못 본 원소없이 모두 봤으면 win!
}
bool wins(int const** board)
{
return rows_win(board) && columns_win(board);
}
bool solve_puzzle(int** board)
{
if (is_invalid((int const**)board))
return false;
if (wins((int const**)board))
return true;
for (int row = 0; row < 3; row++)
for (int col = 0; col < 3; col++)
if (board[row][col] == BLANK) // 모든 빈칸들에 대하여
{
for (int guess = 0; guess < 3; guess++)
{
board[row][col] = guess;
if (solve_puzzle(board))
return true; // valid면 유지
}
board[row][col] = BLANK;
return false; // invalid면 backtrack, false 반환
}
return false;
}
int main()
{
int row1[] = {1, 2, 0};
int row2[] = { BLANK, BLANK, BLANK };
int row3[] = { BLANK, BLANK, BLANK };
int* rows[] = { row1, row2, row3 };
if (solve_puzzle(rows))
for (int r = 0; r < 3; r++)
for (int c = 0; c < 3; c++)
cout << rows[r][c] << " ";
cout << endl;
}