재귀 알고리즘 분석
재귀 알고리즘 분석에는 두가지 분석 방법이 있습니다.
아래의 예제를 하여금 분석해봅시다
#include <stdio.h >
/*--- 재귀 함수 recur( ) ---*/
void recur(int n)
{
if (n > 0) {
recur(n - 1);
printf("%d\n", n);
recur(n - 2);
}
}
int main(void)
{
int x;
printf("정수를 입력하세요 : ");
scanf("%d", &x);
recur(x);
return 0;
}
이 예제의 재귀 호출은 함수 안에서 2번 실행됩니다. 그러면 실제 동작은 매우 복잡해집니다. 이런 복잡한 구조의 재귀 함수는 좀 더 전략적으로 분석해야 합니다. recur함수를 하향식과 상향식 두가지 방법으로 분석합니다. 입력값이 4이면 출력 결과 값은 1 2 3 1 4 1 2 입니다.
햐향식 분석
중요한점은 위의 1번 실행이 완료가 되어야 2, 3번도 실행이 됩니다. 따라서 recur(3)을 실행합니다.
전달 받은 값(n)이 0이하면 recur 함수는 아무 일도 하지 않으므로 검은색 빈 상자입니다.
recur(3)부터 시작하여야 함으로 왼쪽 화살표를 따라서 이동합니다. 그리고 빈 상자를 만나면 다시 원래 상자로 돌아오면서 가운데 출력하고 이어서 오른쪽 화살표를 따라 한칸 아래 상자로 이동합니다. 이렇게 하나의 작업이 완료되어야 한 칸 위의 상자로 돌아갈 수 있습니다.
이처럼 가장 위쪽에 위치한 함수 호출부터 시작해 계단식으로 자세히 조사해 가는 분석 기법을 하향식 분석(top-down analysis)이라고 합니다. 이 안에는 recur(1), recur(2)의 호출이 여러 번 있습니다. 꼭대기(top)부터 아래로 분석하면 이렇게 같은 함수의 호출이 여러 번 나올 수 있기 때문에 '햐향식 분석이 반드시 효율적이다'라고는 말할 수는 없습니다.
상향식 분석
하향식 분석과는 반대로 아래쪽부터 쌓아 올리며 분석하는 방법이 상향식 분석(bottom-up analysis)입니다. recur 함수는 n이 양수일 때만 실행하므로 먼저 recur(1)을 생각합니다.
여기서 1, 3번은 출력할 내용이 없습니다. 따라서 2번의 1만 출력합니다.
1번은 1을 출력하고 2번은 2을 출력하고 3번은 출력할 내용이 없습니다.
재귀 알고리즘의 비재귀적 표현
recur 함수를 재귀 호출을 사용하지 않고 구현하는 방법에 대해 살펴보겠습니다.
#include <stdio.h>
/*--- 함수 recur (맨끝 재귀를 삭제) ---*/
void recur(int n)
{
Top:
if (n > 0) {
recur(n - 1);
printf("%d\n", n);
n = n - 2;
goto Top;
}
}
int main(void)
{
int x;
printf("정수를 입력하세요. :");
scanf("%d", &x);
recur(x);
return 0;
}
그림으로 보자면 아래와 같습니다.
이렇게 하면 꼬리 재귀를 제거할 수 있습니다.
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int max; /* 스택 용량 */
int ptr; /* 스택 포인터 */
int* stk; /* 스택의 첫 요소에 대한 포인터 */
} IntStack;
/*--- 스택 초기화 ---*/
int Initialize(IntStack* s, int max)
{
s->ptr = 0;
if ((s->stk = calloc(max, sizeof(int))) == NULL) {
s->max = 0; /* 배열의 생성에 실패 */
return -1;
}
s->max = max;
return 0;
}
/*--- 스택에 데이터를 푸시---*/
int Push(IntStack* s, int x)
{
if (s->ptr >= s->max) /* 스택이 가득 참 */
return -1;
s->stk[s->ptr++] = x;
return 0;
}
/*--- 스택에서 데이터를 팝 ---*/
int Pop(IntStack* s, int* x)
{
if (s->ptr <= 0) /* 스택이 비어 있음 */
return -1;
*x = s->stk[--s->ptr];
return 0;
}
/*--- 스택 종료 ---*/
void Terminate(IntStack* s)
{
if (s->stk != NULL)
free(s->stk); /* 배열을 삭제 */
s->max = s->ptr = 0;
}
/*--- 스택이 비어 있는가? ---*/
int IsEmpty(const IntStack* s)
{
return s->ptr <= 0;
}
/*--- 함수 recur (재귀를 삭제) ---*/
void recur(int n)
{
IntStack stk; /* 스택 */
Initialize(&stk, 100);
Top:
if (n > 0) {
Push(&stk, n); /* n 값을 푸시 */
n = n - 1;
goto Top;
}
if (!IsEmpty(&stk)) { /* 스택이 비어 있지 않으면 */
Pop(&stk, &n); /* 값을 저장한 n을 팝 */
printf("%d\n", n);
n = n - 2;
goto Top;
}
Terminate(&stk);
}
int main(void)
{
int x;
printf("정수를 입력하세요. :");
scanf("%d", &x);
recur(x);
return 0;
}
위의 그림과 아래를 참고하여 과정을 살펴보면
recur 함수의 코드를 자세히 살펴봅시다.
글 잘 보고 갑니다 ! 같은 책을 공부 중인 사람인데 글이 너무 보기좋네요 ! 혹시 이미지나 이런 건 직접 만드시나요 ??