모든 재귀를 반복으로 변환 할 수 있습니까?


181

레딧 스레드는 분명히 흥미로운 질문을 제기 :

테일 재귀 함수는 사소하게 반복 함수로 변환 될 수 있습니다. 다른 것은 명시 적 스택을 사용하여 변환 할 수 있습니다. 모든 재귀를 반복으로 변환 할 수 있습니까 ?

게시물의 (카운터?) 예는 다음과 같습니다.

(define (num-ways x y)
  (case ((= x 0) 1)
        ((= y 0) 1)
        (num-ways2 x y) ))

(define (num-ways2 x y)
  (+ (num-ways (- x 1) y)
     (num-ways x (- y 1))

3
이것이 어떻게 반례인지 알 수 없습니다. 스택 기술이 작동합니다. 그것은 예쁘지 않을 것이고 나는 그것을 쓰지 않을 것이지만 그것은 가능합니다. akdas는 귀하의 링크에서이를 인정합니다.
Matthew Flaschen

귀하의 (num-ways xy)는 단지 (x + y) choosex = (x + y)! / (x! y!)이며 재귀가 필요하지 않습니다.
ShreevatsaR


재귀는 단지 편의라고 말할 수 있습니다.
e2-e4

답변:


181

항상 재귀 함수를 반복 함수로 바꿀 수 있습니까? 물론 그렇습니다. 그리고 교회 튜링 논문은 기억이 도움이된다면 그것을 증명합니다. 즉, 재귀 함수로 계산할 수있는 것은 반복 모델 (예 : Turing machine)로 계산할 수 있으며 그 반대도 마찬가지입니다. 논문은 변환 방법을 정확하게 알려주지는 않지만 확실히 가능하다고 말합니다.

많은 경우 재귀 함수를 쉽게 변환 할 수 있습니다. Knuth는 "컴퓨터 프로그래밍 기술"에서 몇 가지 기술을 제공합니다. 그리고 종종 재귀 적으로 계산 된 것을 적은 시간과 공간에서 완전히 다른 접근법으로 계산할 수 있습니다. 이것의 전형적인 예는 피보나치 수 또는 이의 서열이다. 학위 과정에서 반드시이 문제를 해결했습니다.

이 동전의 반대편에서, 우리는 공식의 재귀 적 정의를 이전 결과를 기억하기위한 초대로 취급 할 정도로 진보 된 프로그래밍 시스템을 상상할 수 있습니다. 재귀 적 정의를 가진 공식의 계산을 따르십시오. Dijkstra는 거의 확실히 그러한 시스템을 상상했습니다. 그는 구현을 프로그래밍 언어의 의미와 분리하는 데 오랜 시간을 보냈습니다. 그런 다음 그의 비결정적이고 다중 처리 프로그래밍 언어는 실무 전문 프로그래머보다 우위에 있습니다.

최종 분석에서 많은 함수는 재귀적인 형태로 이해하고 읽고 쓰는 것이 더 쉬워졌습니다. 설득력있는 이유가 없다면,이 함수들을 명시 적으로 반복적 인 알고리즘으로 (수동으로) 변환해서는 안됩니다. 컴퓨터가 해당 작업을 올바르게 처리합니다.

한 가지 설득력있는 이유가 있습니다. [ 석면 속옷 착용 ] 구성표, Lisp, Haskell, OCaml, Perl 또는 Pascal 과 같은 최고급 언어로 프로토 타입 시스템을 구축했다고 가정합니다 . 조건이 C 또는 Java로 구현되어야한다고 가정하십시오. (아마도 정치 일 것입니다.) 그렇다면 재귀 적으로 작성된 일부 함수를 가질 수 있지만 문자 그대로 번역되어 런타임 시스템을 폭발시킬 수 있습니다. 예를 들어, Scheme에서는 무한 꼬리 재귀가 가능하지만 동일한 관용구가 기존 C 환경에 문제를 일으 킵니다. 또 다른 예는 파스칼은 지원하지만 C는 지원하지 않는 어휘 중첩 함수와 정적 범위를 사용하는 것입니다.

이러한 상황에서는 원래 언어에 대한 정치적 저항을 극복하려고 시도 할 수 있습니다. Greenspun의 (뺨에 혀로) 10 번째 법칙에서와 같이 Lisp를 잘못 구현 한 것을 알게 될 것입니다. 또는 완전히 다른 솔루션 접근 방식을 찾을 수 있습니다. 그러나 어쨌든 확실한 방법이 있습니다.


10
교회 튜링이 아직 입증되지 않았습니까?
Liran Orevi

15
@eyelidlessness : B에서 A를 구현할 수 있다면 B는 최소한 A만큼의 힘을 가짐을 의미합니다. B의 A 구현에서 A의 일부 문을 실행할 수 없으면 구현이 아닙니다. A가 B로 구현 될 수 있고 B가 A로 구현 될 수 있으면 power (A)> = power (B) 및 power (B)> = power (A)입니다. 유일한 해결책은 power (A) == power (B)입니다.
Tordek

6
다시 : 첫 번째 단락 : 당신은 교회 튜링 논문이 아니라 계산 모델의 동등성에 대해 이야기하고 있습니다. 동등성은 교회 및 / 또는 Turing에 의해 입증 된 AFAIR이지만, 논문이 아닙니다. 이 논문은 직관적으로 계산 가능한 모든 것이 엄격한 수학적 의미로 계산 가능하다는 실험적 사실입니다 (Turing 기계 / 재귀 함수 등). 물리 법칙을 사용하여 튜링 머신이 할 수없는 것 (예 : 정지 문제)을 계산하는 비 클래식 컴퓨터를 구축 할 수 있다면 그것은 반증 될 수 있습니다. 동등성은 수학적 정리 인 반면, 반증되지는 않을 것입니다.
sdcvvc

7
이 답변이 어떻게 긍정적 인 투표를 얻었습니까? 먼저 튜링 완성도와 교회 튜링 논문을 섞은 다음, "고급"시스템을 언급하고 게으른 무한 꼬리 재귀 (C 또는 다른 튜링 전체 언어로 할 수있는)를 떨어 뜨리면서 잘못된 핸드 웨이브를 만듭니다 .. 어. Turing complete가 무엇을 의미하는지 아는 사람이 있습니까?). 그렇다면 Oprah에 대한 질문이었던 희망찬 손끝의 결론은 긍정적이고 고양되는 것입니다. 끔찍한 대답!
ex0du5

8
그리고 의미론에 대한 BS ??? 정말? 이것은 구문 변환에 대한 질문이며 어떻게 든 Dijkstra를 삭제하는 좋은 방법이되었으며 pi-calculus에 대해 알고 있습니까? 언어의 의미 론적 의미를 보든 다른 모델을 보든이 질문에 대한 답과 관련이 없을 것입니다. 언어가 어셈블리인지 생성 도메인 모델링 언어인지는 아무 의미가 없습니다. Turing 완전성과 "스택 변수"를 "변수 스택"으로 변환하는 것에 관한 것입니다.
ex0du5

43

모든 재귀 함수에 대해 비 재귀 양식을 항상 작성할 수 있습니까?

예. 간단한 공식 증거는 µ 재귀 와 GOTO와 같은 비 재귀 미적분이 모두 Turing을 완료 함을 보여줍니다. 모든 Turing 완전 미적분은 표현력이 완전히 동일하기 때문에 모든 재귀 함수는 비재 귀적 Turing- 미완 적 미적분에 의해 구현 될 수 있습니다.

불행히도 온라인에서 GOTO에 대한 좋은 공식 정의를 찾을 수 없으므로 다음과 같습니다.

GOTO 프로그램은 P 가 다음 중 하나가 되도록 레지스터 머신 에서 실행 되는 일련의 명령 P 입니다 .

  • HALT실행이 중단되는
  • r = r + 1r레지스터는 어디에 있습니까
  • r = r – 1r레지스터는 어디에 있습니까
  • GOTO xx라벨이 어디 있어요
  • IF r ≠ 0 GOTO xr레지스터는 어디에 x있고 레이블입니다
  • 위의 명령 뒤에 레이블이 붙습니다.

그러나 재귀 함수와 비 재귀 함수 간의 변환이 항상 사소한 것은 아닙니다 (호출 스택을 수동으로 다시 구현하는 경우 제외).

자세한 내용은 이 답변을 참조하십시오 .


좋은 답변입니다! 그러나 실제로 재귀 적 알고리즘을 반복적 인 알고리즘으로 변환하는 데 큰 어려움이 있습니다. 예를 들어, 여기 community.topcoder.com/… 에 제시된 단형 입력기를 반복 알고리즘으로 바꿀 수 없었습니다
Nils

31

재귀는 실제 인터프리터 또는 컴파일러에서 스택 또는 유사한 구조로 구현됩니다. 따라서 재귀 함수를 반복 대응 함수로 변환 할 수 있습니다. 왜냐하면 그것이 항상 자동으로 수행되기 때문입니다 . 컴파일러의 작업을 임시로, 아마도 매우 추악하고 비효율적 인 방식으로 복제하게 될 것입니다.


13

기본적으로 그렇습니다. 결국 당신이해야 할 일은 '암시 적으로 스택에 상태를 푸시하는 메소드 호출을 명시 적 스택 푸시로 대체하여'이전 호출 '이 어디서 왔는지 기억하고'호출 된 메소드 '를 실행하는 것입니다 대신에.

루프, 스택 및 상태 머신의 조합을 기본적으로 메소드 호출을 시뮬레이션하여 모든 시나리오에 사용할 수 있다고 생각합니다. 이것이 더 나은지 (더 빠른지 또는 어떤 의미에서는 더 효율적 일지) 여부는 일반적으로 말할 수 없습니다.


9
  • 재귀 함수 실행 흐름은 트리로 표현 될 수 있습니다.

  • 데이터 구조를 사용하여 해당 트리를 통과하는 루프로 동일한 논리를 수행 할 수 있습니다.

  • 깊이 우선 순회는 스택을 사용하여 수행 할 수 있으며, 너비 우선 순회는 대기열을 사용하여 수행 할 수 있습니다.

대답은 '그렇다'입니다. 이유 : https://stackoverflow.com/a/531721/2128327 .

단일 루프에서 재귀를 수행 할 수 있습니까? 예, 왜냐하면

튜링 머신은 단일 루프를 실행하여 모든 작업을 수행합니다.

  1. 명령을 가져와
  2. 그것을 평가
  3. 고토 1.

7

예, 스택을 명시 적으로 사용하십시오 (그러나 재귀는 훨씬 더 읽기 쉽습니다, IMHO).


17
나는 항상 읽는 것이 더 즐겁다 고 말하지 않을 것입니다. 반복과 재귀는 모두 그 자리에 있습니다.
Matthew Flaschen

6

예, 항상 비 재귀 버전을 작성할 수 있습니다. 사소한 해결책은 스택 데이터 구조를 사용하고 재귀 실행을 시뮬레이션하는 것입니다.


스택 데이터 구조가 스택에 할당되면 목적을 상실하거나 힙에 할당되면 시간이 더 걸리는 것입니다. 그것은 사소하지만 비효율적입니다.
conradkleinespel

1
@conradk 경우에 따라 호출 스택을 소진하기에 충분히 큰 문제에 대해 트리 재귀 작업을 수행해야하는 경우 실제적인 방법입니다. 힙 메모리는 일반적으로 훨씬 더 많습니다.
jamesdlin

4

원칙적으로 재귀를 제거하고 데이터 구조와 호출 스택 모두에 대해 무한 상태의 언어로 반복을 대체 할 수 있습니다. 이것이 교회 튜링 논문의 기본 결과입니다.

실제 프로그래밍 언어가 주어지면 그 대답은 분명하지 않습니다. 문제는 프로그램에 할당 할 수있는 메모리의 양이 제한되어 있지만 사용할 수있는 호출 스택의 양이 제한되지 않은 언어를 가질 수 있다는 것입니다 (스택 변수의 주소가 32 비트 C 인 경우) 접근 불가 한). 이 경우 재귀는 더 많은 메모리를 사용할 수 있기 때문에 더욱 강력합니다. 호출 스택을 에뮬레이트하기 위해 명시 적으로 할당 가능한 메모리가 충분하지 않습니다. 이에 대한 자세한 설명은 이 토론을 참조하십시오 .


2

모든 계산 가능한 기능은 Turing Machines에서 계산할 수 있으므로 재귀 시스템과 Turing 시스템 (반복 시스템)은 동일합니다.


1

때로는 재귀를 교체하는 것이 그보다 훨씬 쉽습니다. 재귀는 1990 년대에 CS에서 가르쳤던 유행이었으며, 당시의 많은 개발자들이 재귀로 무언가를 해결하면 더 나은 해결책이라고 생각했습니다. 따라서 그들은 역순으로 거꾸로 반복하는 대신 재귀를 사용하거나 그런 어리석은 것들을 사용합니다. 따라서 때때로 재귀를 제거하는 것은 단순한 "duh, 명백했다"유형의 운동입니다.

패션이 다른 기술로 옮겨 감에 따라 이것은 현재 문제가되지 않습니다.



0

명시 적 스택의 Appart, 재귀를 반복으로 변환하는 또 다른 패턴은 트램폴린을 사용하는 것입니다.

여기서 함수는 최종 결과를 반환하거나 그렇지 않은 경우 함수 호출을 닫습니다. 그런 다음 시작 (트램 폴링) 기능은 최종 결과에 도달 할 때까지 반환 된 클로저를 계속 호출합니다.

이 방법은 상호 재귀 함수에 효과적이지만 꼬리 호출에만 효과가 있습니다.

http://en.wikipedia.org/wiki/Trampoline_ (컴퓨터)


0

예라고 말하고 싶습니다-함수 호출은 goto 및 스택 작업 (대략 말하면)입니다. 함수를 호출하는 동안 빌드 된 스택을 모방하고 goto와 비슷한 것을 수행하기 만하면됩니다 (이 키워드가 명시 적으로없는 언어로 고토를 모방 할 수도 있습니다).


1
OP가 증거 나 다른 실질적인 것을 찾고 있다고 생각합니다.
Tim



-1

tazzego, 재귀는 함수가 원하는지 여부에 관계없이 함수를 호출한다는 것을 의미합니다. 사람들이 재귀없이 일을 할 수 있는지 여부에 대해 이야기 할 때, 그들은 이것을 의미하며 "재귀 정의에 동의하지 않기 때문에 사실이 아닙니다."라고 말할 수는 없습니다.

그것을 염두에두고, 당신이 말하는 다른 모든 것은 말도 안됩니다. 말도 안되는 말은 콜 스택없이 프로그래밍을 상상할 수 없다는 것입니다. 그것은 콜 스택을 사용하는 것이 대중화 될 때까지 수십 년 동안해온 일입니다. 이전 버전의 FORTRAN에는 호출 스택이 없었으며 제대로 작동했습니다.

그런데 반복 수단으로 재귀 (예 : SML) 만 구현하는 Turing-complete 언어가 있습니다. 루핑 수단으로 만 반복을 구현하는 Turing-complete 언어도 있습니다 (예 : FORTRAN IV). Church-Turing 논문은 재귀 전용 언어로 가능한 모든 것이 비 재귀 언어로 수행 될 수 있으며, 둘 다 튜링 완전성의 속성을 가지고 있다는 사실에 의해 가능합니다.


-3

반복 알고리즘은 다음과 같습니다.

def howmany(x,y)
  a = {}
  for n in (0..x+y)
    for m in (0..n)
      a[[m,n-m]] = if m==0 or n-m==0 then 1 else a[[m-1,n-m]] + a[[m,n-m-1]] end
    end
  end
  return a[[x,y]]
end
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.