Chap 02. 재귀 (Recursion) [윤성우의 열혈 자료구조]

doriskim·2022년 11월 26일
0

*본 내용은 [윤성우의 열혈 자료구조] 책과 강의를 보고 공부하면서 요점 정리한 내용입니다.

Chap 02-1: 함수의 재귀적 호출의 이해

🔳 재귀함수의 기본적인 이해

  • 재귀는 재 진입의 개념이 아닌, 명령문의 복사본이 만들어져서 그 복사본이 실행된다고 생각하자.
  • 재귀함수는 탈출조건이 필요하다.

#include<stdio.h>
void Recursive(int num)
{
	if (num <= 0)	//재귀의 탈출조건
		return;		//재귀의 탈출
	printf("Recursive call! %d \n", num);
	Recursive(num - 1);
}

int main() 
{
	Recursive(3);
	return 0;
}

실행결과

Recursive call! 3
Recursive call! 2
Recursive call! 1

🔳 팩토리얼의 재귀적 구현

#include<stdio.h>
int Factorial(int n) 
{
    if (n == 0)
        return 1;
    else
        return n * Factorial(n - 1);
}

int main() 
{ 
    printf("1! = %d \n", Factorial(1));
    printf("2! = %d \n", Factorial(2));
    printf("3! = %d \n", Factorial(3));
    printf("4! = %d \n", Factorial(4));
    printf("9! = %d \n", Factorial(9));

    return 0; 
}

실행결과

1! = 1
2! = 2
3! = 6
4! = 24
9! = 362880

※ 재귀 공부할 때 주의할 점

  • 함수의 [호출 관계 + 호출 순서]를 파악하는 것은 재귀함수를 공부할 때 꼭 필요한 것은 아니다.
    재귀관계가 꼭 필요한 문제일수록 호출 관계, 호출 순서를 파악하는게 어려울 뿐만 아니라 이해에도 큰 도움이 되지 않는다. 특히 호출 순서를 파악하는 것이 어려운데 재귀를 공부하면서 호출 관계와 호출 순서를 파악하는 것에 집착하지 않는게 좋다.

  • 재귀적 문제 해결 방법

  1. 문제 확인
  2. 재귀적 해결책
  3. 코드 작성
  4. test --> 확인되면 OK!
    (호출 관계, 호출 순서에 집착 No!)

Chap 02-2: 재귀의 활용

🔳 피보나치 수열

  • 피보나치 수열
    첫 번째 항의 값이 0이고 두 번째 항의 값이 1일 때, 이후의 항들은 이전의 두 항을 더한 값으로 이루어지는 수열을 말한다.

    0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 ...

  • 피보나치 수열의 구성

    수열의 n번째 값 = 수열의 n-1번째 값 + 수열의 n-2번째 값

  • 피보나치 수열의 표현

  • 코드의 구현

  • 예제

#include<stdio.h>
int Fibo(int n) 
{
	if (n == 1)
		return 0;
	else if (n == 2)
		return 1;
	else
		return Fibo(n - 1) + Fibo(n - 2);
}

int main() 
{
	int i;
	for (i = 1; i < 15; i++)
		printf("%d ", Fibo(i));

	return 0;
}

실행 결과

0 1 1 2 3 5 8 13 21 34 55 89 144 233

🔳 이진 탐색 알고리즘

  • 이진 탐색의 알고리즘의 핵심
  1. 탐색 범위의 중앙에 목표 값이 저장되었는지 확인
  2. 저장되지 않았다면 탐색 범위를 반으로 줄여서 다시 탐색 시작
  • 예제
int BSearchRecur(int ar[], int first, int last, int target)
{
	int mid;
	if (first > last)	//탈출조건: first와 last가 역전되면 탈출
		return -1;		//-1의 반환은 탐색의 실패를 의미

	mid = (first + last) / 2;	//탐색의 대상에서 중심에 해당하는 인덱스 값 계산

	if (ar[mid] == target)	//타겟이 맞는지 확인
		return mid;
	else if (target < ar[mid])
		return BSearchRecur(ar, first, mid - 1, target);	//앞부분을 대상으로 재 탐색
	else
		return BSearchRecur(ar, mid + 1, last, target);		//뒷부분을 대상으로 재 탐색
}

Chap 02-3: 하노이 타워

🔳 하노이 타워

  • 하노이 타워
    원반을 A에서 C로 이동하기. 옮기는 과정에서 B를 활용한다.

  • 제약사항

  1. 원반은 한 번에 하나씩만 옮길 수 있다.
  2. 옮기는 과정에서 작은 원반 위에 큰 원반이 올려져서는 안된다.
  • 하노이 타워의 반복패턴 연구
    '원반 4개를 A에서 C로 이동해야하는 문제'를 '원반 3개를 옮기는 문제'로 축소할 수 있고,
    이를 일반화 시키면 다음과 같다.

(원반 3개를 하나의 원반덩이로 생각하면 편하다.)

하노이 타워반복패턴일반화
목표. 원반 4개를 A에서 C로 이동목표. 큰 원반 n개를 A에서 C로 이동
1. 작은 원반 3개를 A에서 B로 이동1. 작은 원반 n-1개를 A에서 B로 이동
2. 큰 원반 1개를 A에서 C로 이동2. 큰 원반 1개를 A에서 C로 이동
3. 작은 원반 3개를 B에서 C로 이동3. 작은 원반 n-1개를 B에서 C로 이동

☞꼭 'A'에서 'C'로 이동하는 것이라고만 생각할게 아니라,
'원래 원반이 있던 자리'에서 '옮기고 싶은 자리'로 이동하는 것이라고 생각하자.
☞[4개-->3개-->2개-->1개]를 옮기는 문제로 점점 축소시킬 수 있고, 이는 재귀를 이용할 수 있다.

🔳 하노이 타워 문제의 해결

  • 하노이 타워 함수의 기본 골격
    원반 num의 수에 해당하는 원반을 from에서 to로 이동을 시키되 그 과정에서 by를 활용한다.
    ex) HanoiTowerMove(4, 'A', 'B', 'C'); // 4개의 원반을 A-->C로 옮기되 B를 거쳐서 옮겨라

    void HanoiTowerMove(int num, char from, char by, char to)
    {
    ...
    }
  • 일반화하노이 타워 함수
    목표. 큰 원반 n개를 A에서 C로 이동HanoiTowerMove(num, from, by, to);
    1. 작은 원반 n-1개를 A에서 B로 이동HanoiTowerMove(num-1, from, to, by);
    2. 큰 원반 1개를 A에서 C로 이동printf(...); //출력을 통해 확인
    3. 작은 원반 n-1개를 B에서 C로 이동HanoiTowerMove(num-1, by, from, to);
  • 구현
#include <stdio.h>

void HanoiTowerMove(int num, char from, char by, char to)
{
	if (num == 1)	//탈출조건: 이동할 원반의 수가 1개라면(남은 원반의 수가 1개라면)
	{
		printf("원반1을 %c에서 %c로 이동 \n", from, to);
	}
	else
	{
		HanoiTowerMove(num - 1, from, to, by);
		printf("원반%d을(를) %c에서 %c로 이동 \n", num, from, to);
		HanoiTowerMove(num - 1, by, from, to);
	}
}

int main() 
{
	//막대A의 원반 3개를 막대B를 경유하여 막대C로 옮기기
	HanoiTowerMove(3, 'A', 'B', 'C');
	return 0;
}

실행 결과

원반1을 A에서 C로 이동
원반2을(를) A에서 B로 이동
원반1을 C에서 B로 이동
원반3을(를) A에서 C로 이동
원반1을 B에서 A로 이동
원반2을(를) B에서 C로 이동
원반1을 A에서 C로 이동

0개의 댓글

관련 채용 정보