-> 재귀(Recursion)를 스스로의 정의로 설명하는 것을 말합니다.
이러한 방식으로 재귀 메소드는 문제를 작게 분할하고 해결하는 동시에 재귀 호출을 제어하여 원하는 결과를 얻을 수 있습니다
Search:
middle page = (first page + last page)/2
Open the phone book to middle page;
If (name is on middle page)
then done; //this is the base case
else if (name is alphabetically before middle page)
last page = middle page //redefine search area to front half
Search//recursive call with reduced number of pages
else //name must be after middle page
first page = middle page //redefine search area to back half
Search//recursive call with reduced number of pages
위의 재귀 알고리즘은 알파벳순으로 정렬된 전화번호부에서 이름을 검색하는 방법 중 하나입니다.
Search 알고리즘은 다음과 같은 절차를 따릅니다:
이 알고리즘은 이진 검색(Binary Search)의 재귀적인 형태로 구현되었으며, 알파벳순으로 정렬된 리스트에서 효과적인 검색을 수행할 수 있습니다.
public class RecursiveCountdown
{
public static void main(String[] args)
{
countDown(3);
}
public static void countDown(int num)
{
if (num <= 0)
{
System.out.println();
}
else
{
System.out.print(num);
countDown(num-1);
}
}
}
countDown 메소드는 정수 num을 매개변수로 받습니다. 메소드는 num이 0보다 작거나 같을 때까지 재귀적으로 호출됩니다.
이 코드에선 처음에 num의 값이 3으로 주어집니다. num이 0보다 작거나 같지 않으므로 else 블록이 실행됩니다.
먼저, System.out.print(num) 문장이 실행되어 현재 num의 값을 출력합니다. 그리고 countDown(num-1)을 호출하여 num의 값을 1만큼 감소시킨 후 다시 countDown 메소드를 재귀적으로 호출합니다.
이렇게 재귀 호출이 반복되면서 num의 값은 1씩 감소하고 출력되는 숫자는 역순으로 출력됩니다.
base case기저 사례는 num이 0보다 작거나 같을 때입니다. 이때는 아무 작업도 수행되지 않고, 빈 줄이 출력됩니다. 이를 통해 재귀 호출이 종료되고 메소드가 끝납니다.
따라서 위의 코드는 3부터 시작하여 3, 2, 1 순서로 출력하는 재귀적인 카운트다운을 수행합니다.
이를 도식화하면 아래와 같습니다.

import java.util.Scanner;
public class recursion
{
public static void main(String[] args)
{
System.out.println("Enter an integer:");
Scanner keyboard = new Scanner(System.in);
int number = keyboard.nextInt( );
System.out.println("The digits in that number are:");
displayAsWords(number);
System.out.println( );
System.out.println("If you add ten to that number, ");
System.out.println("the digits in the new number are:");
number = number + 10;
displayAsWords(number);
System.out.println( );
}
/**
Precondition: number >= 0
Displays the digits in number as words.
*/
public static void displayAsWords(int number)
{
if (number < 10)
System.out.print(getWordFromDigit(number) + " ");
else //number has two or more digits
{
displayAsWords(number / 10);
System.out.print(getWordFromDigit(number % 10) + " ");
}
}
/**
Precondition: 0 <= digit <= 9
Returns the word for the argument digit.
*/
private static String getWordFromDigit(int digit)
{
String result = null;
switch (digit)
{
case 0: result = "zero"; break;
case 1: result = "one"; break;
case 2: result = "two"; break;
case 3: result = "three"; break;
case 4: result = "four"; break;
case 5: result = "five"; break;
case 6: result = "six"; break;
case 7: result = "seven"; break;
case 8: result = "eight"; break;
case 9: result = "nine"; break;
default:
System.out.println("Fatal Error.");
System.exit(0);
break;
}
return result;
}
}
이 코드는 사용자로부터 정수를 입력받고, 해당 정수의 각 자리 숫자를 영어 단어로 출력하는 프로그램입니다. 코드의 구조는 다음과 같습니다:
main 메소드:
사용자로부터 정수를 입력받습니다.
displayAsWords 메소드를 호출하여 입력받은 정수의 각 자리 숫자를 출력합니다.
입력받은 정수에 10을 더한 후 다시 displayAsWords 메소드를 호출하여 새로운 정수의 각 자리 숫자를 출력합니다.
주어진 숫자를 처리하여 각 자리 숫자를 출력합니다.
만약 숫자가 10보다 작으면, getWordFromDigit 메소드를 호출하여 해당 숫자의 영어 단어를 출력합니다.
숫자가 10 이상인 경우, 재귀적으로 displayAsWords 메소드를 호출하여 숫자를 자릿수 별로 분리하고 각 자릿수의 영어 단어를 출력합니다.
주어진 숫자에 해당하는 영어 단어를 반환합니다.
switch 문을 사용하여 숫자에 따라 영어 단어를 결정합니다.
주어진 숫자가 0부터 9까지의 범위를 벗어나는 경우, "Fatal Error."를 출력하고 프로그램을 종료합니다.
All recursive algorithms/methodscan be rewritten without recursion
->모든 재귀 알고리즘과 메소드는 재귀 없이 작성할 수 있다.
그렇다면 언제 재귀를 사용해야 할까요? 일반적으로 다음과 같은 경우에 재귀를 사용합니다:
재귀 호출은 반복보다 실행 시간과 메모리 사용량이 더 많을 수 있습니다. 따라서 재귀를 사용하는 것은 효율성이 중요하지 않은 경우입니다. 작은 규모의 문제나 알고리즘이나 재귀가 자연스럽게 적용되는 경우에 주로 사용됩니다.
재귀는 문제를 작은 부분으로 분할하여 해결하기 때문에 문제의 구조나 동작을 더 직관적으로 표현할 수 있습니다. 특히 수학적이거나 재귀적인 구조를 가진 문제의 해결에 재귀가 적합합니다. 재귀를 사용하면 코드의 가독성과 이해도를 높일 수 있습니다.
따라서 재귀는 효율성이 크게 중요하지 않고 코드의 가독성과 이해도를 높이는데 중점을 둘 때 유용합니다. 재귀는 문제를 분할하여 해결하고, 재귀적인 구조를 갖는 문제에 적합한 프로그래밍 기법입니다. 그러나 큰 규모의 문제나 반복문으로 더 효율적으로 해결할 수 있는 경우에는 반복 메소드를 사용하는 것이 좋습니다.
위의 코드도 재귀없이 다시 작성할 수 있습니다.
import java.util.Scanner;
public class IterativeDemo
{
public static void main(String[] args)
{
System.out.println("Enter an integer:");
Scanner keyboard = new Scanner(System.in);
int number = keyboard.nextInt( );
System.out.println("The digits in that number are:");
displayAsWords(number);
System.out.println( );
System.out.println("If you add ten to that number, ");
System.out.println("the digits in the new number are:");
number = number + 10;displayAsWords(number);
System.out.println();
}
/**Precondition: number >= 0
Displays the digits in number as words.
*/
public static void displayAsWords(int number)
{
int divisor = getPowerOfTen(number);
int next = number;
while (divisor >= 10)
{
System.out.print(getWordFromDigit(next / divisor) + " ");
next = next % divisor;
divisor = divisor / 10;
}
System.out.print(getWordFromDigit(next / divisor) + " ");
}
// Precondition: n >= 0.
// Returns 10 raised to the power n.
private static int getPowerOfTen(int n) //987 -> 100
{
int result = 1;
while (n >= 10)
{
result = result * 10;
n = n / 10;
}
return result;
}
// Precondition: 0 <= digit <= 9
// Returns the word for the argument digit.
private static String getWordFromDigit(int digit)
{
String result = null;
switch (digit)
{
case 0: result = "zero"; break;
case 1: result = "one"; break;
case 2: result = "two"; break;
case 3: result = "three"; break;
case 4: result = "four"; break;
case 5: result = "five"; break;
case 6: result = "six"; break;
case 7: result = "seven"; break;
case 8: result = "eight"; break;
case 9: result = "nine"; break;
default:
System.out.println("Fatal Error.");
System.exit(0);
break;
}
return result;
}
}
재귀 메서드는 반복적인 방법과 비교해 몇 가지 단점을 가지고 있습니다. 이는 실행 중에 재귀 호출과 함수 호출 스택 관리로 인한 오버헤드 때문입니다.
재귀 메서드가 호출될 때마다 호출 스택에 현재 메서드 호출에 대한 정보가 저장되는데, 이는 지역 변수, 매개변수, 반환 주소 등을 포함합니다. 재귀 호출마다 이러한 할당이 발생하며, 동일한 작업을 하는 반복적인 솔루션과 비교했을 때 메모리 사용량이 더 크게 될 수 있습니다.
또한, 재귀 메서드는 반복 호출 오버헤드로 인해 반복적인 해결책에 비해 더 느릴 수 있습니다. 각 재귀 호출은 함수 프레임 설정, 인수 전달 및 호출 스택 관리를 위한 추가 시간이 필요합니다. 이러한 오버헤드는 누적될 수 있으며, 깊거나 복잡한 재귀에 대해 더욱 느려질 수 있습니다.
그러나 이러한 단점에도 불구하고 재귀는 특정 프로그래밍 작업에서 더 나은 선택이 되기도 하며, 더 우아한 해결책을 제공할 수 있습니다. 재귀 알고리즘은 종종 문제의 내재적인 재귀적 구조를 반영하여 코드를 직관적이고 이해하기 쉽게 만들어줍니다. 재귀적인 해결책은 간결하게 표현되고 문제의 논리를 직접적으로 나타내므로 코드가 더 읽기 쉽고 유지보수가 용이해집니다.
또한, 특정 경우에는 재귀와 반복적인 해결책 간의 성능 차이가 메모리 사용량과 실행 시간 성능을 상쇄할만큼 크지 않을 수 있습니다. 문제의 특성, 제약 사항, 코드의 가독성과 유지보수성을 고려할 때, 재귀의 오버헤드가 무시할 정도로 작고, 더 우아한 해결책을 선택하는 것이 정당화될 수 있습니다.
효과적인 메모리 관리와 빠른 실행 속도가 중요한 경우에는 반복적인 해결책을 선택하는 것이 일반적입니다. 이는 특히 대규모 데이터 처리, 반복적인 계산 작업 또는 성능에 민감한 애플리케이션에서 유용합니다. 반복적인 알고리즘은 반복문을 사용하여 특정 작업을 반복하므로, 반복 호출 오버헤드가 없고 메모리 사용량이 작아집니다. 이로 인해 실행 속도가 더 빠르고 효율적인 알고리즘을 구현할 수 있습니다.
반면에 재귀는 주어진 문제가 재귀적인 구조를 가지고 있을 때, 코드의 가독성과 이해도를 높이는 데 도움이 됩니다. 재귀 알고리즘은 문제를 작은 하위 문제로 분할하고 각 하위 문제에 대해 재귀 호출을 수행하여 해결합니다. 이로 인해 코드가 더 간결하고 직관적이며, 문제의 본질에 집중할 수 있습니다. 특히 재귀적인 구조가 자연스럽게 문제를 해결하는 데 적합한 경우에는 재귀를 사용하는 것이 더 좋은 선택일 수 있습니다.
따라서 재귀를 선택하는 기준은 문제의 특성과 요구사항, 코드의 가독성과 유지보수성에 따라 다를 수 있습니다. 성능과 효율성이 우선이라면 반복적인 해결책을 고려해야 하지만, 코드의 가독성과 이해도가 더 중요하다면 재귀를 사용할 수 있습니다. 또한, 재귀와 반복 사이에서 최적의 해결책을 찾기 위해서는 문제의 크기와 입력에 따른 성능 테스트를 수행하고 비교해보는 것이 좋습니다.
import java.util.Scanner;
public class recursion
{
public static void main(String[] args)
{
System.out.println("Enter a nonnegative number:");
Scanner keyboard = new Scanner(System.in);
int number = keyboard.nextInt( );
System.out.println(number + " contains " +getNumberOfZeros(number)+ " zeros.");
}
/**
Precondition: n >= 0
Returns the number of zero digits in n.
*/
public static int getNumberOfZeros(int n)
{
int result;
if (n == 0)result = 1;
else if (n < 10) result = 0;
//n has one digit that is not 0
else if (n % 10 == 0)
result = getNumberOfZeros(n / 10) + 1;
else
//n % 10 != 0
result = getNumberOfZeros(n / 10);
return result;
}
}