int factorial(int n){
if(n <= 1) return 1;
else return n * factorial(n-1);
}
factorial(3) = 3 * factorial(2);
= 3 * 2 * factorial(1);
= 3 * 2 * 1
= 3 * 2
= 6
int factorial(int n){
printf("factorial (%d)\n",n);
if(n <= 1) return 1;
else return n * factorial(n-1);
}
int factorial_iter(int n){
int k, v = 1;
for(k = n; k > 0; k--){
v = v * k;
}
return v;
}
장점: 순환 알고리즘은 이해하기 쉽고, 쉽게 프로그램 할 수 있따.
단점: 수행 시간돠 기억 공간의 사용에 있어서 비효율적인 경우가 많음
int sub(int n){
if(n < 0) return 0;
return n + sub(n-3);
}
sub(10) = 10 + sub(7)
= 10 + 7 + sub(4)
= 10 + 7 + 4 + sub(1)
= 10 + 7 + 4 + 1 + sub(-2)
= 10 + 7 + 4 + 1 + 0
= 10 + 7 + 4 + 1
= 10 + 7 + 5
= 10 + 12
= 22
int sub(int n)
{
int sum = 0;
for (int i = n; i > 0;i-=3){
sum += i;
}
return sum;
}
int slow_power(double x, int n){
double r = 1.0;
for(int i=0;i<n;i++){
r = r*x;
}
return r;
} // -> O(n)
power(x,n)
if n=0
return 1;
else if n이 짝수
then return power(x^2,n/2);
else if n이 홀수
then return x * power(x^2,(n-1)/2);
(1) n이 짝수인 경우
power(x,n) = power(x^2, n/2)
= (x^2)^(n/2)
= x^2^(n/2)
= x^n
(2) n이 홀수인 경우
power(x,n) = x * power(x^2,(n-1)/2)
= x*(x^2)^((n-1)/2)
= x * x^(n-1)
= x^n
double power(double x, int n){
if(n == 0) return 1;
else if(n%2 == 0) return power(x*x, n/2);
else return x*power(x*x,(n-1)/2);
} // -> O(logn)
-> 반복적인 거듭제급 계산보다 순환적인 거듭제곱 계산이 더 빠름, 순환을 호출할 때마다 문제의 크기가 약 절반으로 줄어들기 때문
power(2,6) = power(4, 3)
= 4 * power (16, 1)
= 4 * 16 * power(256 , 0)
= 4 * 16 * 1
= 4 * 16
= 64
double power(double x, int n){
if(n == 0)
return 1;
else
return x * power(x, n - 1);
}
실행시간은 줄어들지 않는다 -> O(n)
-> 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...
#include <stdio.h>
int fib(int n){
if(n == 0) return 0;
else if(n == 1) retuern 1;
else return fib(n-2) + fib(n-1);
}
fib(6) = fib(4) + fib(5)
= fib(2) + fib(3) + fib(3) + fib(4)
= fib(0) + fib(1) + fib(1) + fib(2) + fib(1) + fib(2) + fib(2) + fib(3)
= 0 + 1 + 1 + fib(0) + fib(1) + 1 + fib(0) + fib(1) + fib(0) + fib(1) + fib(1) + fib(2)
= 0 + 1 + 1 + 0 + 1 + 1 + 0 + 1 + 0 + 1 + 1 + fib(0) + fib(1)
= 0 + 1 + 1 + 0 + 1 + 1 + 0 + 1 + 0 + 1 + 1 + 0 + 1
= 8
int fib_iter(int n){
int tmp, current =1, last = 0;
for(int i=2;i<=n;i++){
tmp = current;
current += last;
last = tmp;
}
return current;
}
fib(7) = fib(5) + fib(6)
= fib(3) + fib(4) + fib(4) + fib(5)
= fib(3) + fib(4) + fib(4) + fib(3) + fib(4)
하노이탑 조건
n개의 원판을 가지는 하노이탐 문제의 해답
1. A의 n-1개 원판을 B로 옮긴다.
2. A의 제일 밑에 있는 원판을 C로 옮긴다.
3. B의 n-1개 원판을 C로 옮긴다.
// 막대 from에 쌓여 있는 n개의 원찬을 막대 tmp를 사용하여 막대 to로 옮긴다.
void hanoi_tower(int n, char from, char tmp, char to){
if(n == 1){
from에서 to로 원판을 옮긴다.
}else{
1. from의 맨 밑에 있는 원판을 제외한 나머지 원판들을 tmp로 옮긴다.
2. from에 있는 한 개의 원판을 to로 옮긴다.
3. tmp의 원판들을 to로 옮긴다.
}
}
// 막대 from에 쌓여 있는 n개의 원찬을 막대 tmp를 사용하여 막대 to로 옮긴다.
void hanoi_tower(int n, char from, char tmp, char to){
if(n == 1){
from에서 to로 원판을 옮긴다.
}else{
hanoi_tower(n-1, from, to, tmp); // to를 사용하여 from에서 tmp로 n-1개 원판을 이동
from에 있는 한 개의 원판을 to로 옮긴다.
hanoi_tower(n-1, tmp, from, to); // from을 사용하여 tmp에서 to로 n-1개 원판을 이동
}
}
#include <stdio.h>
void hanoi_tower(int n, char from, char tmp, char to){
if(n == 1){
printf("원판 1을 %c에서 %c으로 옮긴다.\n",from, to);
}else{
hanoi_tower(n-1, from, to, tmp);
printf("원판 1을 %c에서 %c으로 옮긴다.\n",from, to);
hanoi_tower(n-1, tmp, from, to);
}
}
main(){
hanoi_tower(4,'A','B','C');
}
다음 팩토리얼 예제에서 차이점
1. return n * factorial(n-1);
2. return factorial(n-1) * n;
영역 채색(blob coloring), 연결 화소 분석법(connected component labeling): g흑과 백의 화소 값만을 갖는 이진 영상(binary image)에서 연결된 객체를 찾는 방법
#include <cstdio>
#define WIDTH 20
#define HEIGHT 9
using namespace std;
// 순환 호출 함수 (다중 순환의 예)
void labelComponent(unsigned char img[HEIGHT][WIDTH], int x, int y, int label)
{
if (x < 0 || y < 0 || x >= WIDTH || y >= HEIGHT) // 영상의 밖이면 return
return;
if (img[y][x] == 9) { // 처리 안된 전경 화소이면
img[y][x] = label; // label로 화소 값을 바꾸고
labelComponent(img, x - 1, y, label); // 순환 호출: 좌측 이웃화소
labelComponent(img, x, y - 1, label); // 순환 호출: 상측 이웃화소
labelComponent(img, x + 1, y, label); // 순환 호출: 우측 이웃화소
labelComponent(img, x, y + 1, label); // 순환 호출: 하측 이웃화소
}
}
// 이진 영상의 영역 채색(blob coloring) 함수
void blobColoring(unsigned char img[HEIGHT][WIDTH])
{
int label = 1; // label은 1부터 시작함
for (int y = 0; y < HEIGHT; y++) { // 영상의 모든 화소에 대해
for (int x = 0; x < WIDTH; x++) {
if (img[y][x] == 9) // 처리가 안된 전경 화소이면
labelComponent(img, x, y, label++); // 연결화소 채색 시작
}
}
}
// 영상의 화소 값을 화면에 출력하는 함수
void printImage(unsigned char img[HEIGHT][WIDTH], char* msg)
{
printf("%s\n", msg);
for (int y = 0; y < HEIGHT; y++) {
for (int x = 0; x < WIDTH; x++) {
if (img[y][x] == 0) {
printf(".");
} else {
printf("%-1d", img[y][x]);
}
}
printf("\n");
}
printf("\n");
}
// 주 함수
int main()
{
// 입력 영상 : 자료 !
unsigned char img[HEIGHT][WIDTH] = {
0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 9, 9, 9, 9, 9, 0, 0, 9, 9,
9, 9, 9, 9, 9, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 9, 9,
0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 0, 9, 9, 9, 9, 9, 0, 0, 9, 9,
0, 9, 9, 9, 0, 0, 9, 9, 9, 0, 0, 9, 0, 0, 0, 0, 0, 0, 9, 9,
0, 9, 0, 9, 0, 0, 9, 0, 0, 0, 0, 9, 9, 9, 9, 9, 0, 0, 9, 9,
9, 9, 0, 9, 9, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9,
9, 0, 0, 0, 9, 0, 9, 0, 0, 0, 0, 0, 9, 0, 9, 0, 0, 0, 0, 0,
9, 0, 0, 0, 9, 0, 9, 0, 0, 0, 0, 0, 9, 0, 9, 0, 0, 0, 9, 9,
0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 9, 9, 9, 9, 9, 0, 0, 9, 9
};
printImage(img, (char*)"<Original image>");
blobColoring(img);
printImage(img, (char*)"<Labelled image>");
return 0;
}
-> 입력 영상의 "자료!"란 글자의 각 연결된 부분들 (ㅈ, ㅏ, ㄹ, ㅛ, 느낌표(!)의 두 영역)이 각각의 객체가 되어 채색되었으며 전체 6개의 객체가 검출된 것을 알 수 있음.
-> 이 프로그램은 실제로 객체 영역의 크기가 크지 않은 경우 매우 안정적으로 실행됨
-> 영상의 크기가 크고, 객체 영역의 면적이 매우 넓은 경우 실행속도가 느려지며 심한 경우 스택 오버플로우가 발생할 수 있음.
-> 따라서, 일반적으로는 주사선 알고리즘(scanline algorithm)을 사용함, 반복문을 사용해서 코드가 매우 복잡하지만 처리시간이 빠르고, 영상의 크기와 상관없이 일정한 시간 안에 결과를 얻을 수 있음.
[Location2D.h]
struct Location2D{
int row; // 현재 위치의 행 번호
int col; // 현재 위치의 열 번호
Location2D(int r = 0, int c = 0) {
row = r;
col = c;
}
void set(int r,int c){
row = r;
col = c;
}
// 위치 p가 자신의 이웃인지 검사하는 함수
bool isNeighbor(Location2D &p){
return ((row == p.row && (col == p.col - 1 || col == p.col + 1)) || (col == p.col && (row == p.row - 1 || row == p.row + 1)));
}
// 위치 p가 자심과 같은 위치인지를 검사하는 함수(연산자 오버로딩 사용)
bool operator==(Location2D& p) { return row == p.row && col == p.col; }
};
#include "Location2D.h"
#include <cstdio>
const int MAZE_SIZE = 6;
char map[MAZE_SIZE][MAZE_SIZE] = {
{ '1', '1', '1', '1', '1', '1' },
{ '0', '0', '1', '0', '0', '1' },
{ '1', '0', '0', '0', '1', '1' },
{ '1', '0', '1', '0', '1', '1' },
{ '1', '0', '1', '0', '0', 'x' },
{ '1', '1', '1', '1', '1', '1' }
};
bool isValidLoc(int r, int c){
if(r<0 || c <0||r>=MAZE_SIZE||c>=MAZE_SIZE)
return false;
else
return map[r][c] == '0' || map[r][c] == 'x';
}
Location2D locEntry, locExit; // 입구와 출구 위치
bool done = false; // 탐색 성공 여부
// 순환으로 구현한 미로 탐색 구현
void searchRecur(Location2D pt){
printf("(%d,%d) ", pt.row, pt.col); // 현재 위치 화면에 출력
if(done) // 이미 탐색에 성공했다면 return
return;
if(pt == locExit){ // 현재 위치가 출구이면 성공
done = true;
return;
}
int r = pt.row;
int c = pt.col;
map[r][c] = '5';
// 4방향 이웃에 대해 순환 호출
if(isValidLoc(r-1,c))
searchRecur(Location2D(r - 1, c));
if(isValidLoc(r,c-1))
searchRecur(Location2D(r, c - 1));
if(isValidLoc(r+1,c))
searchRecur(Location2D(r + 1, c));
if(isValidLoc(r,c+1))
searchRecur(Location2D(r, c + 1));
}
// 미로 탐색 프로그램 주 함수
int main(){
locEntry.set(1, 0);
locExit.set(4, 5);
searchRecur(locEntry); // 미로 탐색 시작
if(done)
printf("\n ==> Find Exit.\n");
else
printf("\n ==> Not Find Exit.\n");
return 0;
}
<참고자료>
천인국 · 공용해 · 하상호 지음,『C언어로 쉽게 풀어쓴 자료구조』, 생능출판(2016)
천인국 · 최영규 지음,『C++로 쉽게 풀어쓴 자료구조』, 생능출판(2016)