개발/Java

자료구조&기본 알고리즘 입문(자바)- 재귀 알고리즘(1)

inkor 2022. 2. 6. 20:10

● 재귀?
어떤 사건이 자기 자신을 포함하고 다시 자기 자신을 사용하여 정의될 떄 재귀적(rescursive)이라고 한다. 
이러한 재귀의 개념을 사용하면 1부터 시작하여 2,3, ...과 같이 무한하게 이어지는 자연수를 아래처럼 
정의할 수 있다 

1. 1은 자연수다
2. 자연수 n의 바로 다음 수도 자연수다 

재귀적 정의(recursive definition)에 의해 무한으로 존재하는 자연수를 위의 두 문장으로 정의 할 수 있다. 
재귀를 효과적으로 사용하면 이런 정의뿐만 아니라 프로그램도 간결하게 할 수 있다. 

 

● 팩토리얼 구하기
재귀의 사용예로 음이 아닌 정수의 팩토리얼(factorial)을 구하는 프로그램이 있다. 음이 아닌 정수 n의 팩토리얼(n!)은
다음과 같이 재귀적으로 정의할 수 있다. 

1. 0! = 1
2. n > 0 이면 n! = n x (n-1)!

예컨대 10의 팩토리얼인 10!은 10x9!로 구할 수 있고 그 우변에서 사요오디는 식 9!은 9x8!로 구할 수 있다. 

import java.util.Scanner;
// 팩토리얼을 재귀적으로 구현

class Factorial {
	// 양의 정수 n의 팩토리얼을 반환.
	static int factorial(int n) {
		if (n > 0)
			return n * factorial(n - 1);
		else
			return 1;
	}

	public static void main(String[] args) {
		Scanner stdIn = new Scanner(System.in);

		System.out.print("정수를 입력.:");
		int x = stdIn.nextInt();

		System.out.println(x + "의 팩토리얼은 " + factorial(x) );
	}
}

factorial 메서드는 매개변수 n에 전달받은 값이 0보다 크면 n*factorial(n-1)을 반환하고, 그렇지 않으면 1을 반환한다

factorial 메서드를 사용해 3으리 팩토리얼 값을 구체적으로 구하는 과정은 다음과 같다. (ex:정수 3을 입력했을 경우)

 

a. 메서드 호출식  factorial(3)을 실행하면 factorial 메서드가 시작된다. 이 메서드는 매개변수 n에 3을 전달받아 
3 * factorial(2)를 반환한다. 그런데 이 곱셈을 수행하려면 factorial(2)의 값을 구해야 한다. 2를 다시 매개변수로 
전달하고 factorial 메서드를 호출한다 

b. 호출된 factorial 메서드는 매개변수 n에 2를 전달받는다. 다시 곱셈 2 * factorial(1)을 수행하기 위해 
factorial 메서드를 호출한다 

c. 다시 호출된 factorial 메서드는 매개변수 n에 1을 전달받는다. 1 * factorial(0)을 수행하기 위해 
factorial 메서드를 호출한다 

d. 호출된 factorial 메서드는 매개변수 n에 전달받은 값이 0이므로 1을 반환한다. 

c.  반환된 값 1을 전달받은 factorial 메서드는 1*factorial(0)(1*1)을 반환한다.
b.  반환된 값 1을 전달받은 factorial 메서드는 2*factorial(1)(2*1)을 반환한다. 
c.  반환된 값 2를 전달받은 factorial 메서드는 3*factorial(2)(3*2)를 반환한다. 

위와 같은 방법으로 factorial(3)을 사용해 팩토리얼 값 6을 얻을 수 있다. factorial 메서드는 n-1의 팩토리얼 값을 구하기 
위해 다시 factorial 메서드를 호출한다. 이러한 메서드 호출 방식을 재귀 호출(recursive call)이라고 한다. 

 

 

● 직접 재귀와 간접 재귀 
factorial 메서드는 그 내부에서 factorial 메서드를 호출한다. 이처럼 자신과 같은 메서드를 호출하면 직접(direct)재귀이다.  간접(indirect) 재귀는 메서드 a가 메서드 b를 호출하고, 다시 메서드 b가 메서드 a를 호출하는 구조로 이루어진다. 

a. 직접재귀 
메서드 a            메서드 a 
a( );            ->    a( );    

b. 간접 재귀 
메서드 a            메서드 b              메서드 a
b();             ->     a();           ->         b();

재귀 알고리즘에 알맞은 경우는 '풀어야 할 문제', '계산할 메서드', '처리할 데이터 구조'가 재귀로 정의되는 경우. 
앞에서 살펴본 '팩토리얼 값을 구하는 예' 는 많은 재귀 알고리즘 사용 방법 가운데 하나다. 

 

○ 유클리드 호제법
두 정수의 최대공약수(greatest common divisor)를 재귀적으로 구하는 방법. 두 정수를 직사각형의 두 변의 길이라고 
생각하면 두 정수의 최대공약수를 구하는 문제는 다음 문제로 바꿀 수 있따. 

직사각형을 정사각형으로 완전히 채운다. 이렇게 만들 수 있는 정사각형의 가장 긴 변의 길이를 구하라.

예를 들어 각 변의 길이가 22와 8일 직사각형으로 구체적인 과정을 설명하면 다음과 같다. 

1. 22 x 8 크기의 직사각형에서 짧은 변(8)을 한 변으로 하는 정사각형으로 분할한다. 이렇게 하면 8x8크기의 
정사각형 타일 2장이 생긴다. 그리고 8x6크긱의 직사각형이 1개 남는다 

2. 남은 8 x 6 크기의 직사각형으로 다시 같은 과정을 수행한다. 6 x 6 크기의 정사각형이 1개, 6 x 2 크기의 
직사각형이 1개 남는다 

3. 다시 남은 6 x 2 크기의 직사각형으로 같은 과정을 수행한다. 이번에는 2 x 2 크기의 정사각형 3개로 나눌 수 있다. 여기서 얻은 2가 최대공약수이다. 

이렇게 두 정수가 주어질 경우 큰 값을 작은 값으로 나누었을 떄 나누어 떨어지는 가장 작은 값이 최대공약수이다. 
나누어지지 않으면 작은 값(얻은 나머지)에 대해 나누어 떨어질때까지 같은 과정을 재귀적으로 반복한다. 

이 과정을 좀 더 수학적으로 표현하기 위해 두 정 수  x, y의 최대공약수를 gcd(x,y)로 표기하고 x = az와 y = bz를 
만족하는 정수 a , b 와 최대의 정수 z가 존재할 때 z를 gcd(x,y)라고 할 수 있다. 다시 말해 최대공약수는 y가 0이면
x이고, y가 0이 아니면 gcd(y,x%y)로 구한다. 이 알고리즘을 유클리드 호제법(Euclidean method if mutual division)이라고 한다. 다음은 유클리드 호제법에 의해 두 정수의 최대공약수를 구하는 프로그램이다 

 

import java.util.Scanner;
// 유클리드 호제법으로 최대공약수 구하기

class EuclidGCD {
	// 정수 x, y의 최대공약수를 구하여 반환. 
	static int gcd(int x, int y) {
		if (y == 0)
			return x;
		else
			return gcd(y, x % y);
	}

	public static void main(String[] args) {
		Scanner stdIn = new Scanner(System.in);

		System.out.println("두 정수의 최대공약수를 구하기.");

		System.out.print("정수를 입력하세요:");	int x = stdIn.nextInt();
		System.out.print("정수를 입력하세요:");	int y = stdIn.nextInt();

		System.out.println("최대공약수는 " + gcd(x, y));
	}
}

-출처: 자료구조와 함꼐 배우는 알고리즘 입문[자바편] 책 중
http://www.yes24.com/Product/Goods/60547893