호우동의 개발일지

Today :

article thumbnail

순환

 

개념

  • 알고리즘이나 함수가 수행 도중에 자기 자신을 다시 호출하여 문제를 해결하는 기법
  • 정의 자체가 순환적으로 되어 있는 경우 적합

 

 

 

프로그램에서 되풀이 해결방법

  • 순환(recursion)
    • 순환적인 문제에서는 자연스러움
    • 함수 호출에 오버헤드 발생 가능성

  • 반복(iteration)
    • 수행속도가 빠름
    • 순환적인 문제에 관해서는 프로그램 작성이 어려워질 수도 있음

 

 

 

팩토리얼

팩토리얼
팩토리얼

 

 

순환(Recursive) 구현

int factorial_recur(int x)
{
    if(x == 1) return 1; // 순환 비호출 부분
    return x*factorial_recur(x-1); // 순환 호출 부분
}

 

 

 

 

반복(Iter) 방식 구현

int factorial_iter(int x){
    if(x == 1) return 1;
    int result = 1;
    for(int i = x; i >= 1; i--){
        result *= i;
    }
    return result;
}

 

 

 

 

 

시간복잡도 비교

  • 순환 방식 : O(n)
  • 반복 방식 : O(n)

 

 

 

 

거듭제곱값 계산

거듭제곱값 계산
거듭제곱값 계산

 

 

 

 

순환(Recursive) 구현

int power_recur(int x,int n)
{
    if(n == 0) return 1;
    if(n % 2 == 0){
        return power_recur(x*x,n/2);
    }
    else return x*power_recur(x*x,(n-1)/2);
}

 

 

 

반복(Iter) 방식 구현

int power_iter(int x,int n)
{
    if(n == 0) return 1;
    int result = 1;
    for(int i = n; i >= 1; i--){
        result *= x;
    }
    return result;
}

 

 

 

시간복잡도 비교

  • 순환 방식 : O(logN) ← N = 1024라면 x^1024 → x^512 → x^256 … → x^4 → x^2 → x^1
  • 반복 방식 : O(n)
  • 순환 방식이 더 빠를 수도 있다.

 

 

 

피보나치수열

피보나치 수열
피보나치 수열

 

 

 

순환(Recursive) 구현

int fib_recur(int n)
{
    if(n == 0) return 0;
    if(n == 1) return 1;
    return fib(n-1) + fib(n-2);
}

 

 

 

 

반복(Iter) 방식 구현

int fib_iter(int n)
{
    if(n == 0) return 0;
    if(n == 1) return 1;
    int pre = 0;
    int cnt = 1;
    int result = 0;

    for(int i = 2; i <= n; i++){
        result = pre + cnt;
        pre = cnt;
        cnt = result;
    }
    return result;
}

 

 

 

 

순환 호출 사용 시 비효율성 발생

  • 피보나치수열 함수를 fib(N)이라고 할 때 fib(6) 계산을 도식화

중복이 나타나서 비효율적
중복이 나타나서 비효율적

 

 

 

순환적 피보나치수열 시간 복잡도 계산

시간복잡도 계산법
시간복잡도 계산법

 

  • 이런 중복 현상은 n이 커지면서 더 심해짐
    → 시간 낭비 커짐

  • 피보나치수열은 반복 방식이 더 효율적
    반복 방식 시간 복잡도 : O(n)

 

 

 

 

 

하노이 탑 문제

  • 3개의 막대 A, B, C / 서로 다른 크기의 원판 n개

  • 문제 : 막대 A에 있는 쌓여있는 원판 n개를 막대 C로 옮기는 것

  • 조건
    • 한 번에 하나의 원판만 이동 가능
    • 맨 위에 있는 원판만 이동 가능
    • 크기가 작은 원판 위에 큰 원판 쌓을 수 없음
    • 중간의 막대를 임시 사용 가능, 하지만 앞의 조건을 지켜야 함
  • 예시 (n = 3)

하노이의 탑 과정 예시
하노이의 탑 과정 예시

 

    • 일반적인 접근법을 이용한 순환적 알고리즘 n개의 원판을 C 막대로 옮기는 것을
      일반적으로 푼다면 이런 식으로 푸는 것이 가장 간단




    • 원판을 두 그룹으로 나눈다. 1개(빨간색)와 n-1개(파란색)

두 원판

1. 파란색 그룹을 B막대로 옮긴다.
2. 빨간색을 C막대에 옮긴다
3. 파란색 그룹을 C막대에 옮기면 끝

이를 하노이 문제의 조건을 적용시킬 수 있을까? 가능하다.


어떻게? 원판의 그룹을 계속계속 나눠주면 된다.
그러면 1개가 남을 때까지 계속될 것이다.

즉 해당 로직을 함수라고 쳤을 때 이를 재귀함수로 호출해 주면 된다는 소리이다.

 

 

 

 

 

하노이 순환적 알고리즘 유사코드 및 구현

 

  • 유사코드
// 막대 from에 쌓여있는 n개의 원판을 막대 tmp를 사용해 막대 to로 옮긴다.
void hanoi(int n, char from, char tmp, char to)
{
    if(n == 1) from에서 to로 옮긴다.

    else{
        (1) hanoi(n-1,from,to,tmp)
        (2) from에 있는 하나 남은 원판을 to로 옮긴다.
        (3) hanoi(n-1,tmp,from,to)
    }
}

 

 

  • 구현
void hanoi(int n, char from, char tmp, char to)
{
    if(n == 1) printf("원판 1을 %c에서 %c로 옮긴다\\",from,to);

    else{
        hanoi(n-1,from,to,tmp);
        printf("원판 %d를 %c에서 %c로 옮긴다\n",n,from,to);
        hanoi(n-1,tmp,from,to);
    }
}

 

 

 

 

알아두면 좋은 관련 지식 및 문제

  • 팩토리얼 계산하는 순환 호출에서 매개변수로 5가 주어졌을 때 최대 활성 레코드 수는?
    • 5가 4를 호출, 4가 3을 호출, 3이 2를 호출, 2가 1을 호출, 1부터는 종료
    • 5,4,3,2,1 → 최대 5개

  • 활성 레코드에 저장되는 것 → 매개변수 값, 함수호출 끝나고 복귀 주소, 지역변수
  • 해당 함수를 호출하고 “recursive”를 입력한 다음 엔터키를 누르면 출력되는 것은?

 

 

 

void unknown()
{
	int ch;
	if((ch=getchar()) != '\n')
		unknown();
	putchar(ch);
}

답 : evisrucer ← 간접적으로 스택에 저장되는 것과 비슷하기 때문에 역순으로 호출