문제

는 배우기 시작하는 lisp,가기 꼬리-재귀.그것은 정확히 무슨 뜻?

도움이 되었습니까?

해결책

간단한 기능을 추가하는 첫 번째 N 의 정수입니다.(예: sum(5) = 1 + 2 + 3 + 4 + 5 = 15).

여기에 간단한 JavaScript 를 구현하는 재귀를 사용:

function recsum(x) {
    if (x===1) {
        return x;
    } else {
        return x + recsum(x-1);
    }
}

는 경우에 당신 recsum(5), 이는 어떤 자바 스크립트 인터프리터을 평가할 것 이라고:

recsum(5)
5 + recsum(4)
5 + (4 + recsum(3))
5 + (4 + (3 + recsum(2)))
5 + (4 + (3 + (2 + recsum(1))))
5 + (4 + (3 + (2 + 1)))
15

참고 어떻게 모든적화를 완료하기 전에 자바 스크립트 인터프리터을 시작하는 실제로 계산하는 작업 조화를 이루었습니다.

여기에서의 꼬리-재귀 같은 기능:

function tailrecsum(x, running_total=0) {
    if (x===0) {
        return running_total;
    } else {
        return tailrecsum(x-1, running_total+x);
    }
}

여기의 순서는 이벤트 발생하는 경우에 당신 tailrecsum(5),(는 것이 효과적으로 수 tailrecsum(5, 0), 기 때문에 기본 second argument).

tailrecsum(5, 0)
tailrecsum(4, 5)
tailrecsum(3, 9)
tailrecsum(2, 12)
tailrecsum(1, 14)
tailrecsum(0, 15)
15

꼬리에서-재귀한 경우,각각의 평가적화,시 running_total 입 업데이트됩니다.

참고:원래 사용한 예에서 Python.이러한 변경되었을 자바스크립트,이후 현대 JavaScript 통역 지원 리 호출 최적화 하지만 파이썬 통하지 않습니다.

다른 팁

전통적인 재귀, 일반적인 모델을 수행하는 귀하의 재귀 전화 번째,그리고 당신의 반환 값적화하고 계산 결과입니다.이러한 방식으로,당신이하지 않는 결과를 얻을 당신의 계산할 때까지 반환에서 모든 재귀적이다.

꼬리 재귀,계산을 수행하는 첫째,그리고 당신은 실적화,전달의 결과는 현재 단계는 다음 재귀적인 단계입니다.이 결과에 마지막 문의 양식 (return (recursive-function params)). 기본적으로 반환 값의 주어진 재귀적 단계로 반환 값은 다음을 재귀적화.

이 결과는 일단 당신을 수행할 준비가 되어 있는 다음을 재귀적인 단계가 필요 없습은 현재 스택 구조다.이에 대한 최적화입니다.사실,적절한 기록 컴파일러가 없어야 합니다 stack overflow snicker 꼬리적화.단순히 다시 사용할 스택 구조에 대한 다음 재귀적인 단계입니다.나는 확 패키지 않는다.

중요한 점은 꼬리 재귀적으로 동등한 보안 시스템을 제공합니다.그것은 단지 문제를 컴파일러 최적화,그러나 기본적인 사실에 대해 표현.이 두 가지 방법으로 갑:취할 수있는 모든 루프의 양식

while(E) { S }; return Q

EQ 은 표정과 S 은 시퀀스의 문으로 꼬리 재귀적 기능

f() = if E then { S; return f() } else { return Q }

물론, E, S, 고 Q 가 정의할 수 있을 계산하는 몇 가지 흥미로운 값은 일부 변수가 있습니다.예를 들어,루프 기능

sum(n) {
  int i = 1, k = 0;
  while( i <= n ) {
    k += i;
    ++i;
  }
  return k;
}

에 해당하는 꼬리-재귀적 기능(s)

sum_aux(n,i,k) {
  if( i <= n ) {
    return sum_aux(n,i+1,k+i);
  } else {
    return k;
  }
}

sum(n) {
  return sum_aux(n,1,0);
}

(이하"포장"의 꼬리-재귀적 기능과 기능을 가진 더 적은 매개 변수는 일반적인 기능을 사용합니다.)

이 책에서 발췌 프로그래밍에 Lua을 만드는 방법은 적절한 꼬리 재귀 (루아지지만,적용하여 패키지 너무)그리고 왜 그것은 더 낫다.

A 꼬리 전화 [꼬리 재귀]종류의 옷을 입고 goto 으로 호출합니다.꼬리를 호출할 때 발생 함수 호출의 다른으로 지난 작업,그래서 그것은 아무 것도 할 수 있다.예를 들어,다음 코드 전화 g 꼬리 전화:

function f (x)
  return g(x)
end

후에 f 전화 g,그것은 다른 아무것도 니다.이러한 상황에서,프로그램 이 필요하지 않습을 반환하는 전화 기능 때 호출 기능 종료됩니다.따라서,후 꼬리 전화 프로그램이 필요하지 않습 유지 에 대한 정보를 호출 기능 에서 스택입니다....

기 때문에 적절한 꼬리 전화를 사용하지 않기 스택에 공간 제한이 없에 숫자의"중첩"꼬리는 전화 프로그램을 만들 수 있습니다.예를 들어,우리는 호출을 다음과 같은 기능을 가진 모든 수으로 인수;그것은 결코 오버플로우 스택:

function foo (n)
  if n > 0 then return foo(n - 1) end
end

...앞서 말했듯이,꼬리 전화 종류의 goto.따라서,아주 유용한 응용 프로그램의 적절한 꼬리에서 전화 Lua 가 프로그램을 위한 국가 기계입니다.이러한 응용 프로그램를 나타낼 수 있습 각 국가 함수에 의해;을 변경 상태 을 이동하는 것입(또는 전화)정 기능입니다.예를 들어,우리 간단한 미로 게임이다.미 는 여러 객실을 보유하고 있으며,각각 최대 four doors:북쪽,남쪽,동쪽,그리고 west.각 단계에서,사용자 입력 운동 방향입니다.이 있는 경우 문 에서는 방향으로,사용자가 간다 해당 방;그렇지 않으면 프로그램으로 인쇄합니다.목표가 에서 초기 룸서 최종 객실.

이 게임은 일반적인 상태 기계, 현재 방은 상태입니다.우리가 구현할 수 있는 이러한 미로 한 기능공합니다.우리가 사용하는 꼬리 전화를 한 방에서 이동 니다.작은 미로 네 개의 객실 은 다음과 같이 표시될 수 있습니다:

function room1 ()
  local move = io.read()
  if move == "south" then return room3()
  elseif move == "east" then return room2()
  else print("invalid move")
       return room1()   -- stay in the same room
  end
end

function room2 ()
  local move = io.read()
  if move == "south" then return room4()
  elseif move == "west" then return room1()
  else print("invalid move")
       return room2()
  end
end

function room3 ()
  local move = io.read()
  if move == "north" then return room1()
  elseif move == "east" then return room4()
  else print("invalid move")
       return room3()
  end
end

function room4 ()
  print("congratulations!")
end

그래서 당신이 볼 때,당신은 재귀처럼 전화:

function x(n)
  if n==0 then return 0
  n= n-2
  return x(n) + 1
end

이것은 꼬리 재귀적이기 때문에 당신은 여전히 해야 할 일이(1)에서는 기능을 한 후 귀가했다.입력하면 매우 높은 수입이 발생 stack overflow.

를 사용하여 정기적인 재귀,각각적화를 밀어 다른 항목에 호출 스택입니다.면 재귀 완료,응용 프로그램은 다음은 팝업이 각 항목 모든 방법으로 다시 아래로.

꼬리 재귀,에 따라 언어로 컴파일할 수 있는 붕괴의 스택 중 하나에 항목을 저장할 스택에 공간다 큰 재귀 쿼리가 실제로 발생할 수 있습니다 스택 오버플로우가 발생합니다.

기본적으로 꼬리 recursions 할 수 있는 최적으로 반복합니다.

대신 그것을 설명하는 단어로,예제는 다음과 같습니다.이 계획 버전의 계승 기능:

(define (factorial x)
  (if (= x 0) 1
      (* x (factorial (- x 1)))))

여기에는 버전의 계승하는 꼬리-recursive:

(define factorial
  (letrec ((fact (lambda (x accum)
                   (if (= x 0) accum
                       (fact (- x 1) (* accum x))))))
    (lambda (x)
      (fact x 1))))

당신은 통지에서 첫 번째는 버전을 재귀한 호출이 사실은 공급 곱셈 표현,따라서 국가를 저장할 스택에 만들 때적화.꼬리에서-재귀 버전을 다른 길은 없 S-식리의 값을 재귀적화,그리고 없기 때문에 추가 작업을 수행,국가지를 저장할 스택에.원칙적으로,꼬리 계획-재귀적 기능을 사용하여 일정한 스택에 공간입니다.

전문 용어 파일이 있는 이 말의 정의에 관해서는 꼬리 재귀:

꼬리 재귀 /n./

지 않는 경우에는 아픈 이미 그것을 참조하십시오 꼬리 재귀.

꼬리 재귀를 의미합적화되는 것에서 마지막 마지막 로직에서 교육을 재귀적 알고리즘이 있습니다.

일반적으로 재귀,당신은 당신 기본 경우 어떤 중지 재귀를 호출을 시작한 터 호출 스택입니다.사용하는 전형적인 예지만,더 많은 C-틱 보다는 Lisp,계승 기능을 보여 줍니다 꼬리 재귀.재귀화가 발생합 후에 검사의 기본 경우 상태입니다.

factorial(x, fac=1) {
  if (x == 1)
     return fac;
   else
     return factorial(x-1, x*fac);
}

초기 호출을 계승 것 factorial(n)fac=1 (기본값)및 n 수 있는 요인을 도출하기는 쉽지 않습니다.

는 것을 의미를 필요로 하기 보다는 오히려를 밀어시 포인터 스택에서,당신은 단순히 점프의 상단에 재귀적 함수와 계속 실행합니다.이를 통해 기능을 재귀적으로 사용없이 무기한이 넘쳐나 스택입니다.

블로그 게시물 주제에는 그래픽 예제의 스 프레임을 같이합니다.

여기에 빠른 코드 조각을 비교하는 두 가지 기능이 있습니다.첫 번째는 전통적인 재귀를 찾는 요인의 번호입니다.두 번째 사용하는 꼬리 재귀.

매우 간단하고 직관적인 이해합니다.

는 쉬운 방법을 말하는 경우 재귀적 기능은 꼬리 재귀는 경우에 반환하는 구체적인 값을 사례가 있습니다.하는 것을 의미하지 않는 1 을 반환 또는 사실 나는 것 같습니다.그것 보다는 더 많은 것이 일부 변형 중 하나의 방법을 매개 변수입니다.

다른 방법은 말은 경우에는 재귀적 무료 전화입의 추가,연산,수정,등등....그것의 의미를 아무것도 하지만 순수한 재귀적이다.

public static int factorial(int mynumber) {
    if (mynumber == 1) {
        return 1;
    } else {            
        return mynumber * factorial(--mynumber);
    }
}

public static int tail_factorial(int mynumber, int sofar) {
    if (mynumber == 1) {
        return sofar;
    } else {
        return tail_factorial(--mynumber, sofar * mynumber);
    }
}

최고의 방법 이해 tail call recursion 은 특별한 경우 재귀가 마지막 호출(또는 꼬리 콜)기능이다.

비교에서 제공되는 예제에서는 Python:

def recsum(x):
 if x == 1:
  return x
 else:
  return x + recsum(x - 1)

^재귀

def tailrecsum(x, running_total=0):
  if x == 0:
    return running_total
  else:
    return tailrecsum(x - 1, running_total + x)

^꼬리 재귀

에서 볼 수 있듯이 일반적인 재귀 버전,최종 통화 코드 블록 x + recsum(x - 1).그래서 호출 한 후 이 recsum 방법,거기에 또 다른 작업 x + ...

그러나,꼬리에서 재귀 버전,최종 통화(또는 꼬리 콜)코드 블록 tailrecsum(x - 1, running_total + x) 는 것을 의미하는 마지막 호출하는 방법을 자체가 없습니다.

이 점이 중요하기 때문에 꼬리를 재귀 같이 여기에 본지는 메모리 성장하기 때문에 기본 VM 을 보고 자신을 호출하는 함수에서 꼬리치(마지막 표현을 평가에 기능),그것을 제거한 현재 스택 구조로 알려져있는,꼬리 전화를 최적화(TCO).

편집

NB.는 것을 명심하십시오 위의 예에 기록된 Python 된 런타임이 지원하지 않할 수 있습니다.이것은 단지 예제를 설명하는 점이다.TCO 이에서 지원되는 언어는 다음과 같은 계획,Haskell 등

Java,여기에 가능한 꼬리 재귀의 구현 피보나치 기능:

public int tailRecursive(final int n) {
    if (n <= 2)
        return 1;
    return tailRecursiveAux(n, 1, 1);
}

private int tailRecursiveAux(int n, int iter, int acc) {
    if (iter == n)
        return acc;
    return tailRecursiveAux(n, ++iter, acc + iter);
}

이 표준은 재귀 구현:

public int recursive(final int n) {
    if (n <= 2)
        return 1;
    return recursive(n - 1) + recursive(n - 2);
}

여기에 공통적인 예는 계승을 사용하여 꼬리-재귀.때문에 스택이 적은 자연에,하나를 수행할 수 있습이 미친 듯이 큰 요인 계산을...

그 다음에 대한 재미있는 시도할 수 있습니다 (format nil "~R" (! 25))

나는 Lisp 프로그래머지만,나는 생각한 도움이 될 것입니다.

기본적으로 그 스타일의 프로그래밍 등을 재귀를 호출은 당신이 마지막으로.

에 짧고,꼬리를 재귀는 재귀 같이 전화 마지막 문서 기능하지 않도록 해야를 기다리적화.

그래서 이것은 꼬리 재귀,즉N(x1,p*x)는 마지막 문에서 함수는 컴파일러를 영리를 파악할 수 있는 최적화를 위한 루프(factorial).두 번째 매개 변수 p 행 중간 제품의 값입니다.

function N(x, p) {
   return x == 1 ? p : N(x - 1, p * x);
}

이것은 비 꼬리-재귀적으로 작성하는 방법은 상기 요인 기능(지만 일부는 C++컴파일러를 할 수 있는 최다 어쨌든).

function N(x) {
   return x == 1 ? 1 : x * N(x - 1);
}

그러나 이것은 아닙니다:

function F(x) {
  if (x == 1) return 0;
  if (x == 2) return 1;
  return F(x - 1) + F(x - 2);
}

를 작성했 긴 라는 제목의 게시물"이해 꼬리 Recursion–Visual Studio C++–어셈블리보기"

enter image description here

여기에는 Perl5 전 tailrecsum 기능을 언급했다.

sub tail_rec_sum($;$){
  my( $x,$running_total ) = (@_,0);

  return $running_total unless $x;

  @_ = ($x-1,$running_total+$x);
  goto &tail_rec_sum; # throw away current stack frame
}

이것은에서 발췌 구조의 해석 컴퓨터 프로그램 에 대한 꼬리 재귀.

에 대조되는 반복과 재귀,우리가하지 않도록 주의해야한다 를 혼동의 개념을 재귀 프로세스의 개념 재귀적 절차입니다.때 우리는 설명하는 절차로 재귀,리 을 참조하여 구문한다는 사실 절차를 정의 참조 (직접 또는 간접적으로)절차 자체.하지만 우리는 설명 프로세스로 다음과 같은 패턴이 즉,말,선형 재귀,우리는 말하기하는 방법에 대한 과정은 진화에 관하여,아 구문의 절차를 작성합니다.보일 수 있는 것을 방해 우리가 참을 재귀 같은 절차 사 iter 로 생성 반복적인 프로세스입니다.그러나,프로세스는 정말로 반복적인:상태 캡쳐에 의해 완전히 세 개의 상태변수,그리고 통역이 필요 추적 세 개의 변수하기 위해 실행하는 과정입니다.

하나의 이유는 차이 프로세스 및 절차 수 이 혼동해서는 대부분의 구현은 일반적인 언어를 포함하여(Ada,Pascal C)는 방식으로 설계되는 해석의 모든 재귀 절차 사용량 메모리와 함께 성장하는의 수 절차에 통화도 할 때는 프로세스의 설명은 원칙적으로, 반복했다.결과적으로,이러한 언어로 설명 할 수있는 반복적 프로세스에 의지하여 특수한 목적을"루프" 과 같은행,반복될 때까지,예고하는 동안. 의 구현 식을 공유하지 않은 이 결함이다.그 를 실행할 반복적인 프로세스에서 일정한 공간,는 경우에도 반복적 과정을 설명하여 재귀적 절차입니다.인 구현으로 이 숙박 시설이라는 꼬리-재귀적입니다. 로 꼬리-재귀적인 구현을 반복할 수 있을 사용하여 표현 일반적인 절차를 호출 메커니즘,그래서는 특별한 반복 구조용으로만 구문 설탕이다.

꼬리 재귀입니다 삶을 살고 있는 지금.당신은 끊임없이 재생 같은 스택,프레임을 통해,기 때문에 아무 이유도 없는 것을 의미로 돌아가려면"앞"프레임입니다.과거 이상으로 수행할 수 있도록 폐기됩니다.당신을 얻을 하나의 프레임,영원으로 이동하고,미래까지의 과정은 필연적으로 죽습니다.

비유 분 고려할 때 몇 가지 프로세스를 활용할 수 있습 추가적인 프레임지만 여전히 고려 꼬리-재귀는 경우에 스택에 성장하지 않는 무.

꼬리를 재귀는 재귀적 기능 함수 호출 자체 끝에("꼬리")기능의에서는 계산 수행의 반환 후 재귀적이다.많은 컴파일러 최적화 변경적화하는 꼬리 재귀적 또는 반복적인다.

문제를 고려의 computing factorial 의 번호입니다.

간단한 방법이 될 것이다:

  factorial(n):

    if n==0 then 1

    else n*factorial(n-1)

당신 통화 요인(4).재귀 트리 될 것이다:

       factorial(4)
       /        \
      4      factorial(3)
     /             \
    3          factorial(2)
   /                  \
  2                factorial(1)
 /                       \
1                       factorial(0)
                            \
                             1    

최대 깊이에서 위의 경우 O(n).

그러나,다음 예제를 살펴보겠습니다.

factAux(m,n):
if n==0  then m;
else     factAux(m*n,n-1);

factTail(n):
   return factAux(1,n);

재귀 트리 factTail(4)이 될 것이다:

factTail(4)
   |
factAux(1,4)
   |
factAux(4,3)
   |
factAux(12,2)
   |
factAux(24,1)
   |
factAux(24,0)
   |
  24

여기에 또한,최대 깊이는 O(n)하지만 아무도의 통화를 추가합변수를 스택입니다.따라서 컴파일러 멀리 할 수 있는 스택과 함께.

을 이해하는 일부의 핵심 사이에 차이의 꼬리-call 재귀와 비 꼬리-call 재귀는 우리가 탐색 할 수 있습니다.NET 구현한 기술입니다.

여기에 문서와 함께 몇 가지 예제에서는 C#,F#,C++\CLI: 모 꼬리에서 재귀 C#,F#,C++\CLI.

C#최적화되지 않습니다 꼬리-call 재귀 반면,F#않습니다.

차이의 원칙을 포함 루프 대Lambda 생합니다.C#설계와 루프는 마음에 반 F#장에서의 원칙 Lambda 생합니다.에 대한 매우 좋은(무료)책의 원칙에 Lambda 미적분 참조하십시오 구조의 해석 컴퓨터 프로그램으로 아벨,서스 맨 및 서스 맨.

에 관한 꼬리에서 호출 F#에 대한 매우 좋은 입문 문서를 참조하십시오 자세한 소개를 꼬리에서 호출 F#.마지막으로,여기에 문서 커버하는 간의 차이를 비 꼬리 재귀와 꼬리-call 재귀(F#): 꼬리-재귀대비 꼬리에서 재귀 F.

를 읽고 싶은 경우에 당신의 일부에 대한 디자인의 차이의 꼬리-call 재귀 C#,F#참조하십시오 생성하는 꼬리-콜 Opcode C#에서는 F#.

당신은 충분히 알고 싶어 어떤 조건을 방지하는 C#컴파일러에서 수행하는 꼬리를 호출 최적화,이 문서를 참조하십시오: JIT CLR 꼬리 전화를 조건.

가 기본적으로 다음과 같은 두 가지 종류의 recursions: 머리 재귀꼬리를 재귀라고 합니다.

머리 재귀, 함수의적화하고 다음 일부 수행합 계산기,어쩌면 사용하는 결과 적화,예를 들어.

꼬리 recursive 기능 모든 계산을 먼저 일어날고 재귀화는 마지막 것은 발생합니다.

에서 가져온 슈퍼 멋진 게시합니다.을 고려하시기 바랍니다.

재귀를 의미한 자신을 호출하는 함수.예를 들어:

꼬리-재귀를 의미한 재귀는 결론을 내릴 기능:

보,마지막 것은 유엔 종단 기능(절차,계획에 전문 용어)지 자체를 호출.다른(더 유용한)예제:

에서 도우미 절차,마지막 한가지 않는 경우에 남아있는 전무는 자체를 호출 한 후(단점이 뭔가 및 cdr 뭔가).이것은 기본적으로 어떻게 당신이 지은 목록이다.

꼬리-재귀가 있다는 것이 큰 장점입 통역(또는 컴파일러에 의존하는 언어와 공급업체)최적화할 수 있습니다 그것은,변환로 뭔가에 해당하는 동안 반복입니다.으로 사실에 전통 방식,가장""와"하면서"루프에서 수행되는 꼬리-재귀한 방식으로(없다고 하는 동안,멀리로 나가 알고).

이 질문은 많은 훌륭한 답변...하지만 내가 도울 수 없어요 하지만 차임에 대한 대안을 정의하는 방법은"꼬리 재귀",또는 적어도"적절한 꼬리 재귀." 즉:해야 하나로 그것을 보는 시설의 특정 식 프로그램입니까?이나 해야 하나로 그것을 보는 시설의 의 구현한 프로그래밍 언어?

에 대한 자세한 내용 후기 보기있는 클래식 에 의 것 클린저,"적절한 꼬리 재귀고 공간에 효율"(PLDI1998),는 정의된"적절한 꼬리 재귀"의 속성으로 프로그래밍 언어를 구현합니다.정의 건설을 허용하나를 무시하는 구현 정보(는지 여부와 같은 호출 스택은 실제로 표현을 통해 런타임이나 스택을 통해 힙-할당된 연결 목록의 프레임).

이를 위해 사용 asymptotic 분석:지의 프로그램 실행 시간으로 하나는 일반적으로 보이지만,오히려 프로그램 공간 사용량.이 방법은 공간 사용량의 힙-할당된 연결 목록에 대한 런타임 호출 스택을 종료되고 점근에 해당;그래서 하나가 얻을 무시하는 프로그래밍 언어를 구현한 세부사항(사는 확실히 문제에서 아주 조금 하지만 실천할 수 있는 진흙 투성이의 물이 아주 조금 할 때 하나 여부를 확인하려고 시도 주어진 구현은 만족스러운 요구 사항을 수"성 꼬리 재귀")

종이가 가치가주의 연구를 위한 이유:

  • 그것은 유도의 정의 꼬리 식을꼬리를 통화 의 프로그램입니다.(그러한 정의,왜 이러한 호출은 중요한 것 같다는 주제의 대부분의 다른 답변 주어진 여기에.)

    여기에는 해당 정의를,그냥을 제공하는 맛의 텍스트:

    Definition1꼬리 식을 의 작성된 프로그램에서 핵심 구성표는 정의 유도 다음과 같습니다.

    1. 몸의 람다 식 꼬리 식을
    2. 는 경우 (if E0 E1 E2) 가 꼬리를 표현,다음 모두 E1E2 는 꼬리를 표현입니다.
    3. 아무것은 꼬리를 표현합니다.

    정의 2 A 꼬리 전화 가 꼬리를 표현하는 절차를 호출합니다.

(꼬리적화 또는 용지로 말한다,"self-리 호출은"특별한 경우의 꼬리를 호출입 절차를 호출합니다.)

  • 제공하는 공식에 대한 정의 여섯 가지"machines"평가를 위한 핵심 구성표는 각 시스템은 동일한 행동 관찰 를 제외하고asymptotic 공간 복잡도 클래스는 각각합니다.

    예를 들어,제공에 대한 정의를 컴퓨터와 각각 1.스 기반의 메모리 관리,2.가비지 컬렉션은 그러나 아무리 꼬리를 호출,3.가비지 컬렉션과 꼬리를 통화,종이 계속 이후부터 더 많은 고급 저장소 관리 전략 등 4."evlis 꼬리 재귀",는 환경이 필요하지 않습을 보존에서는 평가의 마지막 sub-식 인수에 꼬리를 호출,5.을 줄이는 환경의 폐쇄 무료의 변수는 폐쇄 6.그래서 소위"안전한"우주 체계에 의해 정의 Appel 와 샤오.

  • 을 증명하기 위해서는 기계 실제로에 속하는 여섯 별개의 공간 복잡도 클래스,종이,각 쌍에 대해 기계의 비교에서 제공한 구체적인 예는 프로그램의 노출됩니다 asymptotic 공간에 파열이 하나의 컴퓨터에만 다른하지 않습니다.


(읽기 내 응답을 지금,나는 확실하지 않다면 관리하는 실제로 캡쳐 중요한 포인트의 클린저 종이.하지만,슬프게도,나는 더 많은 시간을 할애 개발이 바로 대답니다.)

재귀적 기능은 기능 전화체

그것은 프로그래머 효율적인 프로그램을 사용하여 최소한의 코드.

단점은 그들이 할 수 원인이 무한 루프 과 기타 예상치 못한 결과는 경우 제대로 작성된.

나는 모두 설명 단순 재귀적 기능과 꼬리 재귀적 기능

하기 위해 쓰기 단순 재귀적 기능

  1. 첫 번째는 점을 고려해야 할 때 당신이 결정에 오 루프가 있는 경우 루프
  2. 두 번째가 무엇인 프로세스를 수행하는 경우 우리는 우리 자신의 기능

에서 주어진 예를 들어:

public static int fact(int n){
  if(n <=1)
     return 1;
  else 
     return n * fact(n-1);
}

위의 예제에서

if(n <=1)
     return 1;

은 결정적인 요인을 종료 하는 경우 루프

else 
     return n * fact(n-1);

실제로 처리를 할 수

나에게 휴식 작업을 하나 하나를 위해 쉽게 이해합니다.

우리가 어떻게 되는 내부적으로 실행하는 경우 fact(4)

  1. 대체 n=4
public static int fact(4){
  if(4 <=1)
     return 1;
  else 
     return 4 * fact(4-1);
}

If 루프가 실패하도록 그것은 간다 else 루프 그래서 그것을 반환 4 * fact(3)

  1. 에서 스택 메모리에,우리는 4 * fact(3)

    대체 n=3

public static int fact(3){
  if(3 <=1)
     return 1;
  else 
     return 3 * fact(3-1);
}

If 루프가 실패하도록 그것은 간다 else 루프

그래서 그것을 반환 3 * fact(2)

기억 우리가`라는`4*사실(3)`

에 대한 출력 fact(3) = 3 * fact(2)

지금까지 스택가 4 * fact(3) = 4 * 3 * fact(2)

  1. 에서 스택 메모리에,우리는 4 * 3 * fact(2)

    대체 n=2

public static int fact(2){
  if(2 <=1)
     return 1;
  else 
     return 2 * fact(2-1);
}

If 루프가 실패하도록 그것은 간다 else 루프

그래서 그것을 반환 2 * fact(1)

기억 우리가 소위 4 * 3 * fact(2)

에 대한 출력 fact(2) = 2 * fact(1)

지금까지 스택가 4 * 3 * fact(2) = 4 * 3 * 2 * fact(1)

  1. 에서 스택 메모리에,우리는 4 * 3 * 2 * fact(1)

    대체 n=1

public static int fact(1){
  if(1 <=1)
     return 1;
  else 
     return 1 * fact(1-1);
}

If 루프 진실

그래서 그것을 반환 1

기억 우리가 소위 4 * 3 * 2 * fact(1)

에 대한 출력 fact(1) = 1

지금까지 스택가 4 * 3 * 2 * fact(1) = 4 * 3 * 2 * 1

마지막으로 결과의 사(4) = 4 * 3 * 2 * 1 = 24

enter image description here

꼬리 재귀

public static int fact(x, running_total=1) {
    if (x==1) {
        return running_total;
    } else {
        return fact(x-1, running_total*x);
    }
}

  1. 대체 n=4
public static int fact(4, running_total=1) {
    if (x==1) {
        return running_total;
    } else {
        return fact(4-1, running_total*4);
    }
}

If 루프가 실패하도록 그것은 간다 else 루프 그래서 그것을 반환 fact(3, 4)

  1. 에서 스택 메모리에,우리는 fact(3, 4)

    대체 n=3

public static int fact(3, running_total=4) {
    if (x==1) {
        return running_total;
    } else {
        return fact(3-1, 4*3);
    }
}

If 루프가 실패하도록 그것은 간다 else 루프

그래서 그것을 반환 fact(2, 12)

  1. 에서 스택 메모리에,우리는 fact(2, 12)

    대체 n=2

public static int fact(2, running_total=12) {
    if (x==1) {
        return running_total;
    } else {
        return fact(2-1, 12*2);
    }
}

If 루프가 실패하도록 그것은 간다 else 루프

그래서 그것을 반환 fact(1, 24)

  1. 에서 스택 메모리에,우리는 fact(1, 24)

    대체 n=1

public static int fact(1, running_total=24) {
    if (x==1) {
        return running_total;
    } else {
        return fact(1-1, 24*1);
    }
}

If 루프 진실

그래서 그것을 반환 running_total

에 대한 출력 running_total = 24

마지막으로 결과의 사실(4,1)=24

enter image description here

많은 사람들이 이미 설명한 재귀 여기에.나 인용하려면 몇 가지 생각의 장점에 대해는 재귀에게 책에서"Concurrency 습니다.NET,현대적인 패턴의 동시고 병렬 프로그래밍"는 리카르도 Terrell:

"기능적 재귀는 자연적인 방법으로 반복하에 FP 기 때문에 그것이 피 돌연변이 있습니다.각 반복으로,새로운 값을 전달 반복으로 생성자를 대신하여 업데이트(변화).에 또한,재귀적 기능을 수 있습으로 구성하고,프로그램 더 많은 모듈 뿐만 아니라,도입 기회를 악용 병렬화."

또한 여기에 몇 가지 흥미로운 노트에서 같은 책에 대한 꼬리 재귀:

꼬리-call 재귀는 기술로 변환하는 일반 재귀 기능으로 최적화되어 버전 처리할 수 있는 대형 입력 없이 모든 위험과 부작용이 있습니다.

참고 주된 이유는 꼬리를 위해 호출을 최적화하는 것입 데이터 지역,메모리 사용량 및 캐시를 사용합니다.을 수행하여 꼬리 전화 호출 수신자 사용하여 동일한 스택에 공간으로 발신번호를 입력하실 수 있습니다.이 감소 메모리 부족이 있습니다.그것은 소폭 향상시킵 캐시기 때문에 동일한 메모리는 다시 사용에 대한 후속 호출자 및 수 있는 편에서 캐시 보다는 오히려 제거하는 오래된 캐시 라인을위한 공간을 만들기 위해 새로운 캐시 라인입니다.

A 꼬리 recursive 기능을 재귀적 함수는 마지막 작업 그것은 반환하기 전에 이들을 재귀적 함수 호출합니다.는 반환 값을 재귀적 함수 호출은 즉시 반환됩니다.예를 들어,당신의 코드는 다음과 같이 보일 것입니다.

def recursiveFunction(some_params):
    # some code here
    return recursiveFunction(some_args)
    # no code after the return statement

컴파일러 및 통역을 구현하는 리 호출 최적화리 호출 제거 을 최적화할 수 있습을 재귀 코드를 방지하 stack overflow.하는 경우 컴파일러 또는 인터프리터 구현하지 않는 꼬리 전화를 최적화(과 같은 CPython 통역)없음 추가적인 혜택을 쓰는 코드에 이 방법입니다.

예를 들어,이것은 표준을 재귀적 요인 함수에서 Python:

def factorial(number):
    if number == 1:
        # BASE CASE
        return 1
    else:
        # RECURSIVE CASE
        # Note that `number *` happens *after* the recursive call.
        # This means that this is *not* tail call recursion.
        return number * factorial(number - 1)

그리고 이것은 꼬리 call 재귀 버전의 계승 기능:

def factorial(number, accumulator=1):
    if number == 0:
        # BASE CASE
        return accumulator
    else:
        # RECURSIVE CASE
        # There's no code after the recursive call.
        # This is tail call recursion:
        return factorial(number - 1, number * accumulator)
print(factorial(5))

(주는지만 이것은 Python 코드 CPython 통역사를 하지 않는 꼬리를 호출 최적화,그래서 당신의 코드는 다음과 같이 부여하지 않 runtime 혜택입니다.)

당신이해야 할 필요가있을 수 있습니다 당신의 코드를 조금 더 읽을 사용하려면 꼬리의 전화를 최적화와 같 요인 예이다.(예를 들어,기본 경제는 직관적으로,그리고 accumulator 매개 변수를 효과적으로 사용되는 종류의 글로벌 변수가 있습니다.)

하지만 이익의 꼬리 전화를 최적화하는 방 stack overflow 오류가 있습니다.(I'll 을 얻을 수 있습니다 이 같은 혜택을 사용하여 반복적인 알고리즘 대신 재귀 하나입니다.)

스 오버플로우가 발생 할 때 호출 스택했다 너무 많은 프레임 개체에 밀.프레임 개체에 밀 경우 호출 스택하는 함수가 호출과 팝의 경우 호출 스택의 기능을 반환합니다.구조체 정보를 포함 같은 지역 변수하고 무엇을 줄의 코드를 반환하는 경우 기능을 반환합니다.

하는 경우 재귀적 기능에 의하여 너무 많은 재귀 호출이 반환하지 않고 호출 스택을 초과할 수 있습당 개체 제한.(수에 따라 다릅 플랫폼Python 에서 그것은 1000 프레임은 개체는 기본적으로 합니다.) 이 stack overflow 오류가 있습니다.(어이,하는 곳의 이름이 이 웹사이트에서 오!)

그러나,만약 마지막 것은 당신의 재귀적 기능하지 않적화하고 반환 값을 반환한 다음,거기에 아무 이유 없이 필요하지 현재 프레임을 개체를 유지해야에서 호출 스택입니다.후에는 모든이 없는 경우드 후 재귀,함수 호출이 없는 이유에 현재 프레임을 개체의 지역 변수.그래서 우리가 없앨 수 있는 현재 프레임의 개체는 대신 즉시 그것을 유지에 대한 호출 스택이 있습니다.의 최종 결과는 당신을 호출 스택하지 않는 크기가 증가하고,이에 따라할 수 없습 stack overflow.

컴파일러 또는 통역관이 있어야 합리 호출 최적화를 위한 기능을 인식할 수 있을 때 꼬리를 호출 최적화를 적용할 수 있습니다.그럼에도 있을 수 있습을 다시 정렬 코드에 당신의 재귀적 기능을 사용하의 꼬리를 호출 최적화,그리고 그것은 당신이하는 경우 이러한 잠재력 감소에서의 가독성은 가치가 최적화입니다.

꼬리 재귀가 빠르에 비해 정상적인 재귀.그것이기 때문에 빠르게 출력 조상들의 통화를 수 있는지에서 스택을 유지합니다.그러나 정상적인 재귀는 모든 조상 통화를 산출 기록에서 스택을 유지합니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top