"재귀"를 얼마나 엄격하게 정의 하느냐에 달려 있습니다.
콜 스택 (또는 프로그램 상태를 유지하기위한 메커니즘이 사용되는)을 엄격히 요구하는 경우 항상 그렇지 않은 것으로 대체 할 수 있습니다. 실제로 자연스럽게 재귀를 많이 사용하는 언어는 테일 콜 최적화를 많이 사용하는 컴파일러를 사용하는 경향이 있으므로 작성하는 것은 재귀 적이지만 실행하는 것은 반복적입니다.
그러나 재귀 호출을하고 그 재귀 호출에 대해 재귀 호출의 결과를 사용하는 경우를 고려하십시오.
public static BigInteger Ackermann(BigInteger m, BigInteger n)
{
if (m == 0)
return n+1;
if (n == 0)
return Ackermann(m - 1, 1);
else
return Ackermann(m - 1, Ackermann(m, n - 1));
}
첫 번째 재귀 호출을 반복적으로 만드는 것은 쉽습니다.
public static BigInteger Ackermann(BigInteger m, BigInteger n)
{
restart:
if (m == 0)
return n+1;
if (n == 0)
{
m--;
n = 1;
goto restart;
}
else
return Ackermann(m - 1, Ackermann(m, n - 1));
}
우리는 다음 청소는 제거 할 수 있습니다 goto
병동 벨로키랍토르 와 다 익스트라의 그늘 :
public static BigInteger Ackermann(BigInteger m, BigInteger n)
{
while(m != 0)
{
if (n == 0)
{
m--;
n = 1;
}
else
return Ackermann(m - 1, Ackermann(m, n - 1));
}
return n+1;
}
그러나 다른 재귀 호출을 제거하려면 일부 호출 값을 스택에 저장해야합니다.
public static BigInteger Ackermann(BigInteger m, BigInteger n)
{
Stack<BigInteger> stack = new Stack<BigInteger>();
stack.Push(m);
while(stack.Count != 0)
{
m = stack.Pop();
if(m == 0)
n = n + 1;
else if(n == 0)
{
stack.Push(m - 1);
n = 1;
}
else
{
stack.Push(m - 1);
stack.Push(m);
--n;
}
}
return n;
}
이제 소스 코드를 고려할 때 재귀 메서드를 반복 메서드로 바꿨습니다.
이것이 컴파일 된 것을 고려하여, 우리는 호출 스택을 사용하는 코드를 재귀를 구현하지 않는 코드로 변환했습니다 (그리고 그렇게 작은 코드에서도 아주 작은 값에 대한 스택 오버플로 예외를 코드로 변환합니다) 반환하는 데 엄청나게 오랜 시간이 걸립니다 [ Ackerman 함수가 스택을 넘치지 않게 하려면 어떻게해야합니까? 더 많은 최적화를 통해 실제로는 더 많은 입력을 반환 할 수 있습니다]).
재귀가 일반적으로 구현되는 방식을 고려하여 콜 스택을 사용하는 코드를 보류중인 작업을 유지하기 위해 다른 스택을 사용하는 코드로 전환했습니다. 따라서 저수준에서 고려할 때 여전히 재귀 적이라고 주장 할 수 있습니다.
그리고 그 수준에서 실제로 다른 방법은 없습니다. 따라서 해당 방법을 재귀 적이라고 생각하면 실제로 방법 없이는 할 수없는 일이 있습니다. 일반적으로 이러한 코드에는 재귀 레이블을 지정하지 않습니다. 재귀 라는 용어 는 특정 접근 방식을 다루고 이에 대해 이야기 할 수있는 방법을 제공하기 때문에 유용하며 더 이상 그 중 하나를 사용하지 않습니다.
물론,이 모든 것이 당신이 선택할 수 있다고 가정합니다. 재귀 호출을 금지하는 언어와 반복에 필요한 반복 구조가없는 언어가 있습니다.