루프로는 할 수없는 재귀로 할 수있는 것이 있습니까?


126

루프를 사용하는 것보다 재귀를 사용하는 것이 좋을 때가 있고 재귀를 사용하는 것보다 루프를 사용하는 것이 더 좋은 경우가 있습니다. "올바른"것을 선택하면 리소스를 절약하거나 코드 줄을 줄일 수 있습니다.

루프가 아닌 재귀를 사용하여 작업을 수행 할 수있는 경우가 있습니까?


13
진심으로 의심합니다. 재귀는 영화 루프입니다.
궤도에서 가벼움 경주

6
답이가는 방향을보고 (그리고 더 나은 답을 제공하지 못한 경우), 당신은 조금 더 많은 배경과 당신이 어떤 종류의 대답을한다면 호의에 대답하려고하는 사람이라면 누구나 할 수 있습니다. 가상 머신에 대한 이론적 증거 (무제한 스토리지 및 런타임)를 원하십니까? 아니면 실제적인 예? "어리석게 복잡 할 것"은 "수행 할 수 없음"으로 간주 될 수 있습니다. 또는 다른 점이 있습니까?
5gon12eder

8
@LightnessRacesinOrbit 영어를 모국어로 사용하지 않는 사람의 귀에 "재귀는 영광스러운 루프입니다"라는 말은 "어디서나 재귀 호출 대신 루핑 구조를 사용할 수 있으며, 그 개념은 그 자체의 이름을 가질 가치가 없습니다." . 아마 나는 "무언가를 바친"관용구를 잘못 해석했을 것이다.
하이드

13
Ackermann 기능은 어떻습니까? en.wikipedia.org/wiki/Ackermann_function , 특히 유용하지는 않지만 루핑을 통해 할 수는 없습니다. ( Computerphile 의이 동영상 youtube.com/watch?v=i7sm9dzFtEI 를 확인하고 싶을 수도 있습니다 )
WizardOfMenlo

8
@WizardOfMenlo befunge 코드는 ERRE 솔루션 의 구현입니다 (이것은 또한 대화 형 솔루션입니다. 스택 포함). 스택을 사용한 반복적 인 접근 방식은 재귀 호출을 에뮬레이션 할 수 있습니다. 적절하게 강력한 프로그래밍에서 하나의 루핑 구조를 사용하여 다른 구조를 에뮬레이트 할 수 있습니다. 지침에 레지스터 기계는 INC (r), JZDEC (r, z)튜링 기계를 구현할 수 있습니다. '재귀'가 없습니다. 즉, Zero가 아니면 Jump가됩니다. Ackermann 기능이 계산 가능하면 (즉,) 해당 등록기에서 수행 할 수 있습니다.

답변:


164

예, 아니오 궁극적으로 반복이 계산할 수없는 재귀는 없지만 루프는 훨씬 더 많은 배관이 필요합니다. 따라서 루프가 할 수없는 재귀가 할 수있는 한 가지는 일부 작업을 매우 쉽게 만드는 것입니다.

나무를 걷다. 재귀와 함께 나무를 걷는 것은 어리석은 일입니다. 세상에서 가장 자연스러운 것입니다. 고리가 달린 나무를 걷는 것은 훨씬 간단합니다. 수행 한 작업을 추적하려면 스택 또는 다른 데이터 구조를 유지해야합니다.

종종 문제에 대한 재귀 적 해결책이 더 예쁘다. 그것은 기술적 용어이며 중요합니다.


120
기본적으로 재귀 대신 루프를 수행하면 스택을 수동으로 처리하는 것을 의미합니다.
Silviu Burcea

15
... 스택 . 다음 상황에서는 둘 이상의 스택을 갖는 것이 좋습니다. A트리에서 무언가를 찾는 하나의 재귀 함수 를 고려하십시오 . A해당 항목이 발생할 때 마다 B하위 트리에서 시작한 위치의 관련 항목을 찾는 다른 재귀 함수 를 시작합니다 A. 한 번 B가에 반환하는 재귀를 완료 A하고, 후자는 자신의 재귀를 계속한다. 하나의 스택 A과 하나의 스택을 선언 B하거나 B스택을 A루프 안에 넣을 수 있습니다. 하나의 스택 사용을 주장하면 상황이 정말 복잡해집니다.
rwong

35
Therefore, the one thing recursion can do that loops can't is make some tasks super easy. 루프가 재귀로 할 수없는 것은 일부 작업을 매우 쉽게 만드는 것입니다. 가장 자연스럽게 반복되는 문제를 순진 재귀에서 꼬리 재귀로 변환하여 스택을 날리지 않도록 해야하는 추악하고 직관적이지 않은 것을 보셨습니까?
메이슨 휠러

10
같은 그 "상황이"더 나은 재귀 연산자 내부에 캡슐화 할 수있는 시간의 @MasonWheeler 99 % map또는 fold(사실은 당신이 그 (것)들에게 기본 요소를 고려하도록 선택하는 경우, 당신이 사용할 수 있다고 생각 fold/ unfold루프에 세 번째 대안으로 또는 재귀). 라이브러리 코드를 작성하지 않는 한 반복해야 할 작업이 아니라 반복 구현에 대해 걱정 해야하는 경우가 많지 않습니다. 실제적으로 명시 적 루프와 명시 적 재귀는 모두 똑같이 좋지 않습니다. 최상위 레벨에서 피해야하는 추상화.
Leushenko

7
부분 문자열을 재귀 적으로 비교하여 두 문자열을 비교할 수 있지만 불일치가 나올 때까지 각 문자를 하나씩 하나씩 비교하면 독자가 더 잘 이해할 수 있습니다.
Steven Burnap

78

아니.

아래에 도착 매우 계산하기 위해 필요한 최소의 기본, 당신은 (혼자가 충분하지 않고, 필요한 구성 요소입니다) 루프 할 수 있어야합니다. 방법 은 중요하지 않습니다 .

Turing Machine을 구현할 수있는 모든 프로그래밍 언어를 Turing complete 라고 합니다. 그리고 완전한 언어가 많이 있습니다.

"실제로 작동합니까?" 튜링 완전성의이다 FRACTRAN 이다, 완전한 튜링을 . 하나의 루프 구조를 가지며 Turing 기계를 구현할 수 있습니다. 따라서 계산 가능한 모든 것은 재귀가없는 언어로 구현 될 수 있습니다. 따라서,이없는 것도 재귀 간단한 루프는 할 수 없습니다 계산 가능성의 측면에서 당신을 제공 할 수 있습니다.

이것은 실제로 몇 가지 요점으로 요약됩니다.

  • 계산 가능한 모든 것은 튜링 머신에서 계산할 수 있습니다.
  • Turing 기계를 구현할 수있는 모든 언어 (Turing complete라고 함)는 다른 언어가 할 수있는 모든 것을 계산할 수 있습니다.
  • 재귀가없는 언어로 된 튜링 머신이 있고 ( 다른 esolang에 들어갈 때만 재귀가있는 다른 머신이 있기 때문에) 재귀로 할 수있는 것은 없습니다. 루프 (그리고 재귀로는 할 수없는 루프로는 할 수없는 일).

이것은 반복이 아닌 재귀 또는 재귀가 아닌 반복으로 더 쉽게 생각할 수있는 몇 가지 문제 클래스가 있다고 말하는 것은 아닙니다. 그러나이 도구들도 마찬가지로 강력합니다.

그리고 나는 이것을 'esolang'극단으로 가져 갔지만 (주로 Turing이 완벽하고 다소 이상한 방식으로 구현 된 것을 찾을 수 있기 때문에) esolang이 선택 사항이라는 것을 의미하지는 않습니다. 매직 더 개더링, 센드 메일, 미디어 위키 템플릿, 스칼라 타입 시스템을 포함하여 실수로 튜링 완료된 것들 의 전체 목록이 있습니다. 실제로는 실제적인 작업을 수행 할 때 이러한 도구를 사용하여 계산 가능한 모든 항목을 계산할 수 있다는 점에서 최적의 성능과는 거리가 멀습니다.


이 동등성은 tail call이라고 알려진 특정 유형의 재귀에 들어갈 때 특히 흥미로울 수 있습니다 .

가지고 있다면, 다음과 같이 작성된 계승 법을 생각해 봅시다.

int fact(int n) {
    return fact(n, 1);
}

int fact(int n, int accum) {
    if(n == 0) { return 1; }
    if(n == 1) { return accum; }
    return fact(n-1, n * accum);
}

이 유형의 재귀는 사용 된 스택이없는 루프로 다시 작성됩니다. 이러한 접근법은 종종 등가 루프가 작성되는 것보다 더 우아하고 이해하기 쉽지만, 모든 재귀 호출에 대해 등가 루프가 작성 될 수 있으며 모든 루프에 대해 재귀 호출이 작성 될 수 있습니다.

복잡하고 될 수있는 꼬리 호출 재귀 호출로 간단한 루프를 변환 시간도 있습니다 이해하기 어렵다.


이론 측면으로 들어가려면 교회 튜링 논문을 참조하십시오 . CS.SE 의 교회 튜링 논문 이 유용하다는 것을 알 수 있습니다.


29
튜링 완성도는 중요하게 너무 많이 던져집니다. 많은 것들이 튜링 컴플리트 ( Magic the Gathering )와 같지만 튜링 컴플리트와 다른 것이 아닙니다. 적어도 중요한 수준은 아닙니다. Magic The Gathering으로 나무를 걷고 싶지 않습니다.
Scant Roger

7
일단 "이것은 튜링 머신과 동등한 성능을 갖습니다"라는 문제를 줄일 수 있으면 충분합니다. 튜링 머신은 다소 낮은 장애물이지만 필요한 전부입니다. 재귀가 할 수없는 루프도 할 수 없으며 그 반대도 마찬가지입니다.

4
이 답변의 진술은 물론 정확하지만 나는 그 주장이 실제로 설득력이 없다는 것을 감히 말할 수 있습니다. 튜링 머신에는 재귀에 대한 직접적인 개념이 없으므로“재귀없이 튜링 머신을 시뮬레이션 할 수 있습니다”라고 말하는 것은 실제로 아무 것도 증명하지 않습니다. 진술을 증명하기 위해 보여 주어야 할 것은 튜링 머신이 재귀를 시뮬레이션 할 수 있다는 것입니다. 이것을 표시하지 않으면 교회 튜링 가설도 재귀를 유지한다고 가정해야하지만 OP는 이에 의문을 제기합니다.
5gon12eder

10
OP의 질문은 "최고"가 아닌 "수", "가장 효율적"또는 다른 한정자입니다. "완료"는 재귀로 수행 할 수있는 모든 작업을 루프로 수행 할 수도 있음을 의미합니다. 특정 언어 구현에서 가장 좋은 방법인지 여부는 완전히 다른 질문입니다.
Steven Burnap

7
"Can"은 "best"와 같은 것이 아닙니다. "할 수 없음"을 "최선이 아닌"것으로 잘못 판단하면 어떤 방식 으로든 아무리 좋은 방법이 있기 때문에 마비됩니다.
Steven Burnap

31

루프가 아닌 재귀를 사용하여 작업을 수행 할 수있는 경우가 있습니까?

재귀 알고리즘은 루프로 전환 할 수 있습니다. 재귀 호출은 정확히 현재 상태를 스택에 저장하고 알고리즘을 진행하기 때문에 AKA 스택을 사용하여 임시 상태를 저장합니다. 나중에 상태를 복원합니다. 그래서 짧은 대답은 : 아니요, 그런 경우는 없습니다 .

그러나 "예"에 대한 주장은 가능합니다. 구체적이고 쉬운 예를 들어 보자 : 병합 정렬. 데이터를 두 부분으로 나누고, 부분을 병합 정렬 한 다음 결합해야합니다. 부품에 대해 병합 정렬을 수행하기 위해 병합 정렬을 위해 실제 프로그래밍 언어 함수 호출을 수행하지 않더라도 실제로 함수 호출을 수행하는 것과 동일한 기능을 구현해야합니다 (상태를 자신의 스택에 푸시하고 다른 시작 매개 변수로 루프를 시작한 다음 나중에 스택에서 상태를 팝하십시오.

재귀 호출을 직접 구현하면 별도의 "푸시 상태"와 "시작으로 점프"및 "팝 상태"단계와 같이 재귀입니까? 그리고 그 대답은 : 아니요, 여전히 재귀라고하지 않으며 명시 적 스택을 사용한 반복 이라고 합니다 (기존의 용어를 사용하려는 경우).


이것은 또한 "작업"의 정의에 의존합니다. 작업이 정렬되어야하는 경우 많은 알고리즘을 사용하여 수행 할 수 있으며 많은 알고리즘은 재귀가 필요하지 않습니다. 작업이 merge sort와 같은 특정 알고리즘을 구현하는 경우 위의 모호성이 적용됩니다.

따라서 재귀와 같은 알고리즘 만있는 일반적인 작업이 있습니까? 이 질문에서 @WizardOfMenlo의 의견에서 Ackermann 함수 는 그 간단한 예입니다. 따라서 재귀의 개념은 다른 컴퓨터 프로그램 구조 (명시 적 스택을 사용한 반복)로 구현할 수 있더라도 자체적으로 존재합니다.


2
스택리스 프로세서 용 어셈블리를 다룰 때이 두 기술은 갑자기 하나가됩니다.
Joshua

@Joshua 실제로! 그것은 추상화 수준의 문제입니다. 당신이 한두 단계 더 낮아지면, 그것은 단지 논리 문입니다.
하이드

2
그것은 정확하지 않습니다. 반복으로 재귀를 에뮬레이트하려면 임의 액세스가 가능한 스택이 필요합니다. 랜덤 액세스 및 유한 한 양의 직접 액세스 가능한 메모리가없는 단일 스택은 튜링이 완료되지 않은 PDA입니다.
Gilles

@Gilles Old post, 왜 랜덤 액세스 스택이 필요한가요? 또한 실제 컴퓨터는 PDA보다 적지 않습니다. 직접 액세스 가능한 메모리는 한정되어 있고 스택은 전혀 없기 때문에 (메모리를 사용하지 않는 경우)? "실제로는 재귀를 할 수 없다"고 말하면 이것은 매우 실용적인 추상화처럼 보이지 않습니다.
하이드

20

"재귀"를 얼마나 엄격하게 정의 하느냐에 달려 있습니다.

콜 스택 (또는 프로그램 상태를 유지하기위한 메커니즘이 사용되는)을 엄격히 요구하는 경우 항상 그렇지 않은 것으로 대체 할 수 있습니다. 실제로 자연스럽게 재귀를 많이 사용하는 언어는 테일 콜 최적화를 많이 사용하는 컴파일러를 사용하는 경향이 있으므로 작성하는 것은 재귀 적이지만 실행하는 것은 반복적입니다.

그러나 재귀 호출을하고 그 재귀 호출에 대해 재귀 호출의 결과를 사용하는 경우를 고려하십시오.

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 함수가 스택을 넘치지 않게 하려면 어떻게해야합니까? 더 많은 최적화를 통해 실제로는 더 많은 입력을 반환 할 수 있습니다]).

재귀가 일반적으로 구현되는 방식을 고려하여 콜 스택을 사용하는 코드를 보류중인 작업을 유지하기 위해 다른 스택을 사용하는 코드로 전환했습니다. 따라서 저수준에서 고려할 때 여전히 재귀 적이라고 주장 할 수 있습니다.

그리고 그 수준에서 실제로 다른 방법은 없습니다. 따라서 해당 방법을 재귀 적이라고 생각하면 실제로 방법 없이는 할 수없는 일이 있습니다. 일반적으로 이러한 코드에는 재귀 레이블을 지정하지 않습니다. 재귀 라는 용어 는 특정 접근 방식을 다루고 이에 대해 이야기 할 수있는 방법을 제공하기 때문에 유용하며 더 이상 그 중 하나를 사용하지 않습니다.

물론,이 모든 것이 당신이 선택할 수 있다고 가정합니다. 재귀 호출을 금지하는 언어와 반복에 필요한 반복 구조가없는 언어가 있습니다.


호출 스택이 바인드되거나 호출 스택 외부의 무한 메모리에 액세스 할 수있는 경우에만 호출 스택을 동등한 것으로 대체 할 수 있습니다. 무제한 콜 스택을 갖지만 그렇지 않으면 유한 한 수의 상태 만 가질 수있는 푸시 다운 오토마타로 해결할 수있는 상당한 종류의 문제가 있습니다.
supercat

이것이 가장 좋은 대답 일 것입니다. 두 번째 예조차도 여전히 재귀 적 이며이 수준에서 원래 질문에 대한 대답은 no 입니다. 재귀에 대한 광범위한 정의로 Ackermann 함수의 재귀는 피할 수 없습니다.
gerrit

@gerrit과 더 좁을수록 피할 수 있습니다. 궁극적으로 특정 코드에 사용하는 유용한 레이블을 적용하거나 적용하지 않는 것의 가장자리에 도달합니다.
존 한나

1
투표에 참여하기 위해 사이트에 가입했습니다. Ackermann 함수는 본질적으로 재귀 적입니다. 루프와 스택을 사용하여 재귀 구조를 구현한다고해서 반복적 인 솔루션이되는 것은 아니며, 재귀를 사용자 공간으로 옮겼습니다.
Aaron McMillin

9

고전적인 대답은 "아니오"이지만 "예"가 더 나은 대답이라고 생각하는 이유를 자세히 설명하겠습니다.


계속하기 전에 계산 가능성 및 복잡성 관점에서 무언가 벗어나십시오.

  • 루핑 할 때 보조 스택을 가질 수 있으면 대답은 "아니오"입니다.
  • 루핑 할 때 추가 데이터가 허용되지 않으면 대답은 "예"입니다.

자, 이제 한 발을 연습용 땅에 놓고 다른 발을 이론 용 땅에 두겠습니다.


호출 스택은 제어 구조 인 반면 수동 스택은 데이터 구조입니다. 제어 및 데이터는 없는 동일한 개념 있지만있어 등가 가 될 수 있다는 점에서 감소 계산 가능성이나 복잡도 측면에서 (서로를 통해 "에뮬 레이팅"또는) 서로.

이 차이는 언제 중요할까요? 실제 도구로 작업 할 때 예를 들면 다음과 같습니다.

N-way를 구현한다고 가정 해보십시오 mergesort. forN세그먼트를 통과 mergesort하고 개별적으로 호출 한 다음 결과를 병합 하는 루프 가있을 수 있습니다 .

이것을 OpenMP와 어떻게 병렬화 할 수 있습니까?

재귀 영역에서는 매우 간단합니다. #pragma omp parallel for루프를 1에서 N으로 바꾸면 끝납니다. 반복 영역에서는이 작업을 수행 할 수 없습니다. 스레드를 수동으로 생성하고 수행 할 작업을 알 수 있도록 적절한 데이터를 수동으로 전달해야합니다.

반면에 #pragma vector루프와 함께 작동하지만 재귀에는 전혀 쓸모가없는 다른 도구 (예 : 자동 벡터 라이저 )가 있습니다.

요컨대, 두 패러다임이 수학적으로 동등하다는 것을 증명할 수 있다고해서 실제로 동일하다는 것을 의미하지는 않습니다. 한 패러다임에서 자동화하기가 쉽지 않은 문제 (예 : 루프 병렬화)는 다른 패러다임에서 해결하기가 훨씬 더 어려울 수 있습니다.

, 하나의 패러다임 도구는 다른 패러다임으로 자동 변환되지 않습니다.

결과적으로 문제를 해결하기 위해 도구가 필요한 경우 도구가 특정 종류의 접근 방식으로 만 작동하므로 결과적으로 수학적으로 문제를 입증 할 수 있다고해도 다른 방법으로 문제를 해결하지 못할 가능성이 있습니다 어느 쪽이든 해결할 수 있습니다.


그 외에도, 푸시 다운 오토 마톤으로 해결할 수있는 문제는 유한 오토 마톤으로 해결할 수있는 세트보다 크지 만 (결정적이든 아니든) 유한 한 오토 마톤으로 해결할 수있는 세트보다 크지 만 튜링 기계.
supercat

8

이론적 추론을 제외하고 (하드웨어 또는 가상) 머신 관점에서 재귀와 루프가 어떻게 보이는지 살펴 보겠습니다. 재귀는 제어 흐름의 조합으로, 일부 코드의 실행을 시작하고 완료시 (신호 및 예외가 무시되는 경우 간단한보기로), 해당 다른 코드 (인수)에 전달되어 반환 된 데이터의 조합으로 이루어집니다. 그것은 (결과). 일반적으로 명시 적 메모리 관리는 포함되지 않지만 반환 주소, 인수, 결과 및 중간 로컬 데이터를 저장하기 위해 스택 메모리를 암시 적으로 할당 합니다.

루프는 제어 흐름과 로컬 데이터의 조합입니다. 이것을 재귀와 비교하면이 경우 데이터의 양이 고정되어 있음을 알 수 있습니다. 이 한계를 극복하는 유일한 방법은 필요할 때마다 할당 (및 해제) 할 수있는 동적 메모리 ( heap 라고도 함 )를 사용하는 것입니다.

요약:

  • 재귀 사례 = 제어 흐름 + 스택 (+ 힙)
  • 루프 케이스 = 제어 흐름 + 힙

제어 흐름 부분이 상당히 강력 하다고 가정하면 사용 가능한 메모리 유형 만 다릅니다. 따라서 우리는 4 가지 사례로 남습니다 (표현력은 괄호 안에 표시됨).

  1. 스택, 힙 없음 : 재귀 및 동적 구조는 불가능합니다. (재귀 = 루프)
  2. 스택, 힙 없음 : 재귀는 정상이며 동적 구조는 불가능합니다. (재귀> 루프)
  3. 스택, 힙 없음 : 재귀가 불가능하고 동적 구조가 정상입니다. (재귀 = 루프)
  4. 스택, 힙 : 재귀 및 동적 구조는 정상입니다. (재귀 = 루프)

게임 규칙이 조금 더 엄격하고 재귀 구현에서 루프를 사용할 수없는 경우 대신 다음과 같은 결과를 얻습니다.

  1. 스택, 힙 없음 : 재귀 및 동적 구조는 불가능합니다. (재귀 <루프)
  2. 스택, 힙 없음 : 재귀는 정상이며 동적 구조는 불가능합니다. (재귀> 루프)
  3. 스택, 힙 없음 : 재귀가 불가능하고 동적 구조가 정상입니다. (재귀 <루프)
  4. 스택, 힙 : 재귀 및 동적 구조는 정상입니다. (재귀 = 루프)

이전 시나리오와의 주요 차이점은 스택 메모리 부족으로 인해 루프가없는 재귀가 실행 중에 코드 줄보다 더 많은 단계를 수행 할 수 없다는 것입니다.


2

예. 재귀를 사용하여 달성하기 쉽지만 루프만으로는 불가능한 몇 가지 일반적인 작업이 있습니다.

  • 스택 오버플로가 발생합니다.
  • 완전히 혼란스러운 초보자 프로그래머.
  • 실제로 O (n ^ n) 인 빠른 모양의 함수 만들기

3
제발, 이것들은 루프로 정말 쉽습니다. 항상 봅니다. 약간의 노력으로 루프가 필요하지 않습니다. 재귀가 더 쉬운 경우에도 마찬가지입니다.
AviD

1
실제로, A (0, n) = n + 1; m> 0 인 경우 A (m, 0) = A (m-1,1); m> 0, n> 0이 O (n ^ n)보다 조금 더 빠르게 증가하면 A (m, n) = A (m-1, A (m, n-1)) (m = n) :)
John Donn

1
@JohnDonn 조금 이상, 그것은 지수입니다. n = 3의 경우 n ^ n ^ n n = 4의 경우 n ^ n ^ n ^ n ^ n 등입니다. n의 n 승 n 배
Aaron McMillin

1

재귀 함수와 원시 재귀 함수에는 차이가 있습니다. 프리미티브 재귀 함수는 루프를 사용하여 계산되는 함수로, 루프 실행이 시작되기 전에 각 루프의 최대 반복 횟수가 계산됩니다. (그리고 여기서 "재귀"는 재귀를 사용하는 것과 아무 관련이 없습니다).

원시 재귀 함수는 재귀 함수보다 강력하지 않습니다. 재귀의 최대 깊이를 미리 계산해야하는 재귀를 사용하는 함수를 사용하면 동일한 결과를 얻을 수 있습니다.


3
위의 질문에 이것이 어떻게 적용되는지 잘 모르겠습니다. 연결을 더 명확하게 해줄 수 있습니까?
Yakk

1
부정확 한 "루프"를 "반복 제한 횟수가있는 루프"와 "무제한 반복 횟수가있는 루프"사이의 중요한 차이점으로 대체했습니다. CS 101에서 모든 사람들이 알 것이라고 생각했습니다.
gnasher729

그러나 여전히 질문에 적용되지는 않습니다. 문제는 기본 재귀와 재귀가 아니라 루핑과 재귀에 관한 것입니다. 누군가 C / C ++의 차이점에 대해 물어보고 K & R C와 Ansi C의 차이점에 대해 대답했다고 상상해보십시오.
Yakk

1

c ++로 프로그래밍하고 c ++ 11을 사용하는 경우 재귀를 사용하여 수행해야 할 것은 constexpr 함수입니다. 그러나이 대답 에서 설명한대로 표준은 512로 제한합니다 . 이 경우 루프를 사용할 수 없습니다.이 경우 함수는 constexpr 일 수 없지만 c ++ 14에서 변경되기 때문입니다.


0
  • 재귀 호출이 재귀 함수의 첫 번째 또는 마지막 문인 경우 (조건 확인 제외) 루핑 구조로 변환하는 것은 매우 쉽습니다.
  • 그러나 함수가 재귀 호출 전후에 다른 작업 을 수행하면 루프로 변환하는 것이 번거로울 것입니다.
  • 함수에 재귀 호출 이 여러 개 있으면 루프를 사용하는 코드로 변환하는 것은 거의 불가능합니다. 데이터를 유지하려면 일부 스택이 필요합니다. 재귀에서 호출 스택 자체는 데이터 스택으로 작동합니다.

나무 걷기에는 여러 재귀 호출이 있지만 (자녀 당 하나씩) 명시 적 스택을 사용하여 루프로 간단하게 변환됩니다. 반면 파서는 종종 변환하기가 성가시다.
코드 InChaos

@CodesInChaos가 편집되었습니다.
굴샨

-6

다른 질문에 동의합니다. 루프로는 할 수없는 재귀로 할 수있는 일은 없습니다.

그러나 내 의견으로는 재귀는 매우 위험 할 수 있습니다. 첫째, 코드에서 실제로 일어나는 일을 이해하기가 더 어렵습니다. 둘째, 적어도 C ++ (Java 확실하지 않음)의 경우 각 메소드 호출로 인해 메모리 누적 및 메소드 헤더 초기화가 발생하기 때문에 각 재귀 단계가 메모리에 영향을 미칩니다. 이렇게하면 스택을 날려 버릴 수 있습니다. 입력 값이 높은 피보나치 수의 재귀를 시도하십시오.


2
재귀와 피보나치 수의 순진 재귀 구현은 스택 공간이 부족하기 전에 "시간이 지남"으로 실행됩니다. 이 예제에 더 좋은 다른 문제가 있다고 생각합니다. 또한 많은 문제에서 루프 버전은 재귀 적 메모리 스택과 동일한 메모리 영향을 가지며 스택 대신 힙에 힙에 영향을 미칩니다 (프로그래밍 언어가 구별하는 경우).
Paŭlo Ebermann

6
루프 변수를 증가시키는 것을 잊어 버리면 루프는 "매우 위험 할 수 있습니다"
h22

2
실제로 스택 오버플로를 의도적으로 생성하는 것은 재귀를 사용하지 않고 매우 까다로워지는 작업입니다.
5gon12eder

@ 우리에게 가져다 5gon12eder 방법은 재귀 알고리즘에 스택 오버플로가 방지하기 위해 무엇입니까? -TCO 참여를위한 글쓰기 또는 메모가 유용 할 수 있습니다. 반복적 접근과 재귀 적 접근은 피보나치에 대한 두 가지 재귀 적 접근을 다루기 때문에 흥미 롭습니다.

1
대부분의 경우 재귀에서 스택 오버플로가 발생하면 반복 버전이 중단되었을 것입니다. 적어도 전자는 스택 추적으로 던집니다.
Jon Hanna
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.