이중 재귀 함수의 런타임을 어떻게 결정합니까?


15

임의로 이중 재귀 함수가 주어지면 런타임을 어떻게 계산합니까?

예 (의사 코드) :

int a(int x){
  if (x < = 0)
    return 1010;
  else
    return b(x-1) + a(x-1);
}
int b(int y){
  if (y <= -5)
    return -2;
  else
    return b(a(y-1));
}

아니면 그 라인을 따라 뭔가.

이와 같은 것을 결정하기 위해 어떤 방법을 사용해야합니까?


2
숙제입니까?
Bernard

5
아니, 여름 시간이고 배우고 싶습니다. 나는 뇌가 흐릿 해 지도록하는 대신에 앞서 나간다.
if_zero_equals_one

11
알았어 이것을 스택 오버플로로 마이그레이션하기 위해 투표하는 사람들에게 : 여기는 주제에 관한 것이고 주제는 스택 오버플로에 관한 것입니다. Programmers.SE는 개념적 화이트 보드 질문입니다. 스택 오버플로는 구현 중이며 문제가있는 코딩 문제입니다.

3
고마워 내가 처음 여기에서 한 이유입니다. 게다가 물고기를받는 것보다 낚시하는 법을 아는 것이 좋습니다.
if_zero_equals_one

1
이 특별한 경우에, b (a (0))가 무한히 많은 다른 b (a (0)) 항을 호출하기 때문에 여전히 일반적으로 무한 재귀입니다. 수학 공식이라면 달라졌을 것입니다. 설정이 다르면 다르게 작동했을 것입니다. 수학에서와 마찬가지로 cs에서 일부 문제에는 해결책이 있지만, 일부는 쉽지 않고, 일부는 쉽지 않습니다. 솔루션이 존재하는 상호 재귀적인 경우가 많이 있습니다. 때때로, 스택을 날려 버리지 않기 위해 트램펄린 패턴을 사용해야 할 때가 있습니다.
Job

답변:


11

계속해서 기능을 변경하십시오. 그러나 개종하지 않고 영원히 실행될 것을 계속 선택하십시오.

재귀는 복잡하고 빠릅니다. 제안 된 이중 재귀 함수를 분석하는 첫 번째 단계는 일부 샘플 값에서이를 추적하여 그 기능을 확인하는 것입니다. 계산이 무한 루프 상태가되면 함수가 제대로 정의되지 않은 것입니다. 계산이 더 큰 숫자 (매우 쉽게 발생 함)를 계속 유지하는 나선 모양으로 바뀌면 제대로 정의되지 않은 것입니다.

그것을 통해 답을 얻으면 대답 사이에 어떤 패턴이나 재발 관계를 생각해보십시오. 일단 가지고 나면 런타임을 알아낼 수 있습니다. 그것을 이해하는 것은 매우 복잡 할 수 있지만, 우리는 많은 경우에 답을 알아낼 수 있는 마스터 정리 와 같은 결과를 가지고 있습니다.

단일 재귀로도 계산 방법을 모르는 런타임 함수를 쉽게 만들 수 있습니다. 예를 들어 다음을 고려하십시오.

def recursive (n):
    if 0 == n%2:
        return 1 + recursive(n/2)
    elif 1 == n:
        return 0
    else:
        return recursive(3*n + 1)

현재 알 수없는 이 기능이 항상 실행이 무엇인지 말할 것도없고, 잘 정의되어 있는지 여부를 확인합니다.


5

특정 함수 쌍의 런타임은 다른 함수를 호출하지 않고 반환되지 않기 때문에 무한합니다. 의 반환 값 a입니다 항상 호출의 반환 값에 의존 b하는 항상 호출 a... 그리고로 알려진 무슨의 그 무한 재귀 .


여기서 특정 기능을 찾지 않습니다. 서로 호출하는 재귀 함수의 런타임을 찾는 일반적인 방법을 찾고 있습니다.
if_zero_equals_one

1
일반적인 경우 해결책이 확실하지 않습니다. Big-O가 의미를 갖기 위해서는 알고리즘이 중단되는지 알아야합니다. 시간이 얼마나 걸리는지 알기 전에 계산을 실행해야하는 재귀 알고리즘이 있습니다 (예 : 점이 Mandlebrot 세트에 속하는지 여부 결정).
jimreed

항상 그렇지는 않지만, 전달 된 숫자가> = 0 인 경우 a에만 호출 b합니다. 그러나 예, 무한 루프가 있습니다.
btilly

1
@btilly 내 대답을 게시 한 후 예제가 변경되었습니다.
jimreed

1
@ jimreed : 그리고 그것은 다시 변경되었습니다. 가능한 경우 내 의견을 삭제합니다.
btilly

4

확실한 방법은 함수를 실행하고 시간이 얼마나 걸리는지 측정하는 것입니다. 그래도 특정 입력에 걸리는 시간 만 알려줍니다. 그리고 함수가 종료되는지 미리 알지 못하면 터프합니다. 함수가 종료되는지 여부를 알아낼 수있는 기계적인 방법 은 없습니다. 이것이 정지 문제 이며 결정 불가능합니다.

함수의 실행 시간을 찾는 것은 Rice의 정리에 의해 결정될 수 없습니다 . 실제로 라이스 정리는 함수가 제 O(f(n))시간에 실행되는지 여부를 결정하는 것조차도 결정 불가능 하다는 것을 보여줍니다 .

따라서 일반적으로 할 수있는 최선의 방법은 인간의 지능 (우리가 아는 한 튜링 머신의 한계에 구속되지 않음)을 사용하여 패턴을 인식하거나 패턴을 고안하는 것입니다. 함수의 런타임을 분석하는 일반적인 방법은 함수의 재귀 정의를 런타임에 재귀 방정식 (또는 상호 재귀 함수에 대한 일련의 방정식)으로 바꾸는 것입니다.

T_a(x) = if x ≤ 0 then 1 else T_b(x-1) + T_a(x-1)
T_b(x) = if x ≤ -5 then 1 else T_b(T_a(x-1))

다음은? 이제 수학 문제가 있습니다.이 함수 방정식을 풀어야합니다. 종종 작동하는 방법은 함수를 해석, 이것들을 해결하기 위해 분석 기능과 사용 미적분에 대한 방정식에 정수 기능에 대한 이러한 방정식을 설정하는 것입니다 T_aT_b같이 생성 기능 .

함수와 다른 이산 수학 주제를 생성 할 때 Ronald Graham, Donald Knuth 및 Oren Patashnik의 Concrete Mathematics 책을 추천합니다 .


1

다른 사람들이 지적했듯이 재귀 분석은 매우 빠릅니다. : 여기에 같은 것은 또 다른 예입니다 http://rosettacode.org/wiki/Mutual_recursion http://en.wikipedia.org/wiki/Hofstadter_sequence#Hofstadter_Female_and_Male_sequences 는 답변 이들에 대한 실행 시간을 계산하기 어렵다은. 이것은 "복잡한 형태"를 갖는 이러한 상호 재귀 함수 때문이다.

어쨌든,이 쉬운 예를 보자 :

http://pramode.net/clojure/2010/05/08/clojure-trampoline/

(declare funa funb)
(defn funa [n]
  (if (= n 0)
    0
    (funb (dec n))))
(defn funb [n]
  (if (= n 0)
    0
    (funa (dec n))))

다음과 같이 계산해 봅시다 funa(m), m > 0:

funa(m) = funb(m - 1) = funa(m - 2) = ... funa(0) or funb(0) = 0 either way.

런타임은 다음과 같습니다.

R(funa(m)) = 1 + R(funb(m - 1)) = 2 + R(funa(m - 2)) = ... m + R(funa(0)) or m + R(funb(0)) = m + 1 steps either way

이제 좀 더 복잡한 예제를 하나 골라 보자.

http://planetmath.org/encyclopedia/MutualRecursion.html 에서 영감을 얻었 습니다. "" "피보나치 수는 상호 재귀를 통해 해석 될 수 있습니다. F (0) = 1 및 G (0 ) = 1이고 F (n + 1) = F (n) + G (n) 및 G (n + 1) = F (n)입니다. "" "

그렇다면 F의 런타임은 무엇입니까? 우리는 다른 길로 갈 것입니다.
음, R (F (0)) = 1 = F (0); R (G (0)) = 1 = G (0)
이제 R (F (1)) = R (F (0)) + R (G (0)) = F (0) + G (0) = F (1)
...
R (F (m)) = F (m)임을 알기가 어렵지 않습니다. 예를 들어 인덱스 i에서 피보나치 수를 계산하는 데 필요한 함수 호출 수는 피보나치 수 값과 같습니다. 색인에서 i. 이것은 두 개의 숫자를 더하는 것이 함수 호출보다 훨씬 빠르다고 가정했습니다. 그렇지 않은 경우, 이는 사실입니다 : R (F (1)) = R (F (0)) + 1 + R (G (0)) 그리고 이것에 대한 분석은 더 복잡했을 것입니다. 쉬운 폐쇄 형 솔루션이 없을 수도 있습니다.

피보나치 수열의 닫힌 형태는 좀 더 복잡한 예는 말할 것도없이 재창조하기 쉽지는 않습니다.


0

가장 먼저해야 할 일은 정의한 함수가 종료되고 어떤 값이 정확하게 작동하는지 보여주는 것입니다. 이 예에서는

int a(int x){
  if (x < = 0)
    return 1010;
  else
    return b(x-1) + a(x-1);
}
int b(int y){
  if (y <= -5)
    return -2;
  else
    return b(a(y-1));
}

b에만 종료 y <= -5하면 다른 값에 연결하면 다음과 같은 형식의 기간을 가지고 있기 때문에 b(a(y-1)). 좀 더 확장하면 양식의 용어가 b(a(y-1))결국 용어 b(1010)로 이어지고 b(a(1009))다시 용어 로 이어진다는 것을 알 수 b(1010)있습니다. 즉 , 계산할 값이 계산되는 값에 따라 무한 루프로 끝나기 때문에 a만족하지 않는 값을 연결할 수 없습니다 x <= -4. 따라서 본질적으로이 예제는 런타임이 일정합니다.

따라서 간단한 대답은 재귀 함수의 런타임을 결정하는 일반적인 방법이 없다는 것입니다. 재귀 적으로 정의 된 함수의 종료 여부를 결정하는 일반적인 절차가 없기 때문입니다.


-5

Big-O에서와 같은 런타임?

즉 쉽게 : O (N) - 종료 조건이 있음을 가정.

재귀는 단지 루핑이며 간단한 루프는 루프 에서 수행하는 작업 수에 관계없이 O (N) 입니다 (다른 메소드를 호출하는 것은 루프의 또 다른 단계 일뿐입니다).

흥미로운 부분은 하나 이상의 재귀 메서드 내에 루프가있는 경우입니다. 이 경우 일종의 지수 성능 ( 메소드를 통과 할 때마다 O (N) 을 곱함)으로 끝납니다 .


2
호출 된 메소드의 최상위 순서를 호출 메소드의 순서로 곱하여 Big-O 성능을 판별합니다. 그러나 지수 및 계승 성능에 대해 이야기하기 시작하면 다항식 성능을 무시할 수 있습니다. 나는 생각 계승 승리 : 지수와 계승을 비교할 때 동일 보유하고있다. I이었다 시스템 분석했다 적이 모두 지수와 계승을.
Anon

5
이것은 올바르지 않습니다. n 번째 피보나치 수와 퀵 정렬을 계산하는 재귀 양식은 각각 O(2^n)O(n*log(n))입니다.
unpythonic

1
멋진 증거를하지 않으면 서 amazon.com/Introduction-Algorithms-Second-Thomas-Cormen/dp/… 로 안내하고이 SE 사이트 cstheory.stackexchange.com을 살펴보십시오 .
Bryan Harrington

4
사람들이 왜이 끔찍한 대답에 투표 했습니까? 메서드를 호출하면 메서드에 걸리는 시간에 비례하여 시간이 걸립니다. 이 경우 메소드 a호출 bb호출 a이므로 두 메소드 중 하나에 시간이 걸리는 것으로 가정 할 수 없습니다O(1) .
btilly

2
@Anon-포스터는 위에 표시된 기능뿐만 아니라 임의로 이중 재귀 기능을 요청했습니다. 나는 당신의 설명에 맞지 않는 간단한 재귀의 두 가지 예를 들었습니다. 이전 표준을 "이중 재귀"형식으로 변환하는 것은 쉽지 않습니다.
unpythonic
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.