평범한 영어로 재귀 란 무엇입니까?


74

재귀에 대한 아이디어는 실제 세계에서는 흔하지 않습니다. 따라서 초보자 프로그래머에게는 약간 혼란 스럽습니다. 비록 그것들이 점차 개념에 익숙해 졌다고 생각합니다. 그렇다면 아이디어를 쉽게 파악할 수있는 좋은 설명은 무엇입니까?




1
재귀는 함수가 자신을 호출 할 수있는 경우입니다. "네임 스페이스와 범위와 매개 변수가 함수에 전달되는 방법을 완전히 이해했다면 재귀를 이미 알고있다. 예제를 보여줄 수는 있지만 스스로 어떻게 작동하는지 알아낼 수있을 것이다." 학생들은 일반적으로 재귀로 인해 혼란스럽지 않지만 가변 범위 / 네임 스페이스를 제대로 파악하지 못하기 때문에 재귀로 어려움을 겪습니다. 재귀에 뛰어 들기 전에 학생들이 다른 범위의 변수에 같은 이름을 사용하여 혼란을 겪는 프로그램을 올바르게 추적 할 수 있는지 확인하십시오.
dspyz


1
재귀를 이해하려면 먼저 재귀를 이해해야합니다
Goerman

답변:


110

재귀 를 설명하기 위해 다른 설명을 조합하여 일반적으로 두 가지 모두 시도합니다.

  • 개념을 설명하고
  • 왜 중요한지 설명하고
  • 그것을 얻는 방법을 설명하십시오.

우선, Wolfram | AlphaWikipedia 보다 더 간단한 용어로 정의합니다 .

특정 수학 연산을 반복하여 각 항을 생성하는 표현입니다.


수학

학생 (또는 지금부터 설명하는 사람, 지금부터 학생이라고 말할 것입니다)이 적어도 수학적 배경을 가지고 있다면, 시리즈와 재귀재발 관계에 대한 개념을 연구함으로써 이미 재귀에 직면했습니다 .

시작하는 가장 좋은 방법은 시리즈로 시연하고 재귀가 무엇인지 간단히 말해주는 것입니다.

  • 수학 함수 ...
  • ... n 번째 요소에 해당하는 값을 계산하기 위해 자신을 호출합니다 ...
  • ... 어떤 경계를 정의합니다.

일반적으로, 그들은 여전히 ​​그것을 사용하지 않기 때문에 "허허, whatev '"를 얻거나, 아마도 아주 깊은 코를 likely 수도 있습니다.


코딩 예

나머지 부분은 실제로 포인터에 관해 지적한 질문에 대한 내 답변부록 에 나와있는 내용의 자세한 버전입니다 (나쁜 말장난).

이 단계에서 학생들은 일반적으로 화면에 무언가를 인쇄하는 방법을 알고 있습니다. 우리는 C를 사용하는 가정, 그들은 사용하여 하나의 문자를 인쇄하는 방법을 알고 writeprintf. 또한 제어 루프에 대해서도 알고 있습니다.

나는 보통 그들이 얻을 때까지 몇 가지 반복적이고 간단한 프로그래밍 문제에 의지한다.

계승

계승은 이해하기 매우 간단한 수학 개념이며 구현은 수학적 표현에 매우 가깝습니다. 그러나 그들은 처음에는 그것을 얻지 못할 수 있습니다.

계승 연산의 재귀 적 정의

알파벳

알파벳 버전은 재귀 진술의 순서에 대해 생각하도록 가르치는 것이 흥미 롭습니다. 포인터와 마찬가지로, 그들은 당신에게 무작위로 줄을 던질 것입니다. 요점은 루프는 조건을 수정하거나 반전 할 수있는 실현에 그들을 데리고하는 것입니다 또는 당신의 함수에서 문장의 순서를 반전. 그것이 시각적으로 보여주기 때문에 알파벳 인쇄가 도움이되는 곳입니다. 각 호출마다 하나의 문자를 인쇄하고 다음 (또는 이전) 문자를 작성하기 위해 재귀 적으로 호출하는 함수를 작성하게하십시오.

FP 팬 여러분, 출력 스트림에 물건을 인쇄하는 것이 현재 부작용이라는 사실을 건너 뛰십시오. FP-front에서 너무 성가 시게하지 마십시오. (하지만리스트 지원 언어를 사용하는 경우, 각 반복마다리스트에 연결하여 최종 결과를 인쇄하십시오. 그러나 일반적으로 C로 시작합니다. 불행히도 이런 종류의 문제와 개념에는 적합하지 않습니다.) .

지수화

지수화 문제는 약간 더 어렵다 ( 학습 단계 에서). 분명히 개념은 계승에 대한 개념과 동일하며 여러 매개 변수가 있다는 점을 제외하고는 복잡성이 추가되지 않습니다. 그리고 그것은 보통 사람들을 혼동하고 처음에 그들을 버릴 정도로 충분합니다.

간단한 형태 :

지수 연산의 간단한 형태

반복해서 다음과 같이 표현할 수 있습니다.

지수 연산에 대한 반복 관계

더 세게

이 간단한 문제가 튜토리얼에서 보여지고 다시 구현되면 약간 더 어려운 (그러나 매우 고전적인) 연습을 할 수 있습니다.

참고 : 다시 말하지만,이 중 일부는 더 이상 어렵지 않습니다 ... 그들은 정확히 같은 각도 또는 약간 다른 각도에서 문제에 접근합니다. 그러나 연습은 완벽합니다.


헬퍼

참조

일부 독서는 결코 아프지 않습니다. 글쎄요, 처음에는 더 잃어 버릴 것입니다. 그것은 당신에게서 자라며 언젠가 당신이 마침내 그것을 얻는다는 것을 깨달을 때까지 머리 뒤에 앉아있는 것입니다. 그리고 당신은 당신이 읽은 것들을 다시 생각합니다. 재귀 , 컴퓨터 과학 재귀재발 관계 위키 백과의 페이지가 지금 할 것입니다.

레벨 / 깊이

학생들이 코딩 경험이 많지 않다고 가정하면 코드 스텁을 제공하십시오. 처음 시도한 후에 재귀 수준을 표시 할 수있는 인쇄 기능을 제공하십시오. 레벨의 숫자 값을 인쇄하면 도움이됩니다.

서랍으로 쌓기 다이어그램

인쇄 된 결과 (또는 레벨의 출력)를 들여 쓰면 프로그램이 수행하는 작업, 서랍이나 파일 시스템 탐색기의 폴더와 같은 스택 컨텍스트를 열고 닫는 다른 시각적 표현을 제공하므로 도움이됩니다.

재귀 약어

학생이 이미 컴퓨터 문화에 정통한 경우 재귀 약어를 사용하여 이름이있는 일부 프로젝트 / 소프트웨어를 이미 사용하고있을 수 있습니다 . 오랜 시간 동안, 특히 GNU 프로젝트에서 전통이되었습니다. 몇 가지 예는 다음과 같습니다.

재귀 :

  • GNU- "GNU 's Not Unix"
  • Nagios- "Nagios는 성도를 주장하지 않습니다"
  • PHP- "PHP 하이퍼 텍스트 전 처리기"및 원본 "개인 홈 페이지"
  • 와인- "와인은 에뮬레이터가 아니다"
  • Zile- "Zile Is Lossy Emacs"

상호 재귀 :

  • HURD- "UNIX 대체 데몬의 HIRD"(여기서 HIRD는 "깊이를 나타내는 인터페이스의 hurd")

그들에게 스스로 생각해 보라고한다.

마찬가지로 Google의 재귀 검색 수정 과 같이 재귀 유머가 많이 발생 합니다. 재귀에 대한 자세한 내용은이 답변을 읽으십시오 .


함정과 추가 학습

사람들이 일반적으로 어려움을 겪고 답변을 알아야하는 일부 문제.

왜, 오 하나님 왜 ???

왜 그렇게 하시겠습니까? 좋고 분명하지 않은 이유는 종종 그런 식으로 문제를 표현하는 것이 더 간단하다는 것입니다. 좋지는 않지만 명백한 이유는 타이핑이 덜 필요하다는 것입니다 (재귀를 사용한다고해서 soooo l33t를 느끼게하지 마십시오 ...).

재귀 접근 방식을 사용할 때 일부 문제를 해결하기가 훨씬 쉽습니다. 일반적으로 Divide and Conquer 패러다임을 사용하여 해결할 수있는 모든 문제 는 다중 분기 재귀 알고리즘에 적합합니다.

N은 또 뭐지 ??

n매번 왜 또는 (변수 이름이 다름) 다른가요? 초보자는 일반적으로 변수와 매개 변수가 무엇인지 이해하는 데 문제가 있으며 n프로그램에서 이름이 지정된 항목 이 다른 값을 가질 수 있습니다. 이제이 값이 제어 루프 나 재귀에 있으면 훨씬 더 나빠집니다! 훌륭하고 어디에서나 동일한 변수 이름을 사용하지 말고 매개 변수가 변수 라는 것을 분명히 하십시오 .

최종 조건

종료 조건을 어떻게 확인합니까? 간단합니다. 발걸음을 크게 내딛게하십시오. 예를 들어 계승 시작의 경우 5, 4, 그 다음에 0까지.

악마는 세부 사항에 있습니다

테일 콜 최적화 와 같은 초기 접견에 대해서는 이야기하지 마십시오 . TCO는 훌륭하지만 처음에는 신경 쓰지 않습니다. 그들에게 효과가있는 방식으로 머리를 감쌀 시간을 준다. 나중에 다시 세상을 산산조각 낸 다음 휴식을 취하십시오.

마찬가지로, 첫 번째 강의에서 콜 스택 과 메모리 소비 에 대해 이야기하지 마십시오 . 스택 오버플로 . 나는 종종 이 단계에서 정확하게 루프를 거의 쓸 수 없을 때 재귀에 관해 알아야 할 모든 것에 대해 50 개의 슬라이드가있는 강의를 보여주는 사적으로 학생들을지도합니다 . 이것은 참조가 나중에 도움이 될 수있는 좋은 예 이지만 지금 당장 당신을 깊이 혼란스럽게 합니다.

그러나 적절한시기에 반복 또는 재귀 경로를 사용해야 할 이유 가 있음을 분명히 하십시오 .

상호 재귀

우리는 함수가 재귀적일 수 있고 심지어 여러 호출 포인트 (8- 여왕, 하노이, 피보나치 또는 심지어 지뢰 찾기를위한 탐색 알고리즘)를 가질 수 있음을 보았습니다. 그러나 상호 재귀 호출은 어떻습니까? 여기에서 수학부터 시작하십시오. f(x) = g(x) + h(x)어디 g(x) = f(x) + l(x)에서 h그리고 l그냥 물건을하십시오.

수학 시리즈만으로 시작하면 계약이 표현식으로 명확하게 정의되므로 작성 및 구현이 더 쉬워집니다. 예를 들어, Hofstadter 여성 및 남성 서열 :

Hofstadter의 남성과 여성의 순서

그러나 코드 측면에서, 상호 재귀 솔루션의 구현은 종종 코드 복제로 이어지고 단일 재귀 양식으로 간소화되어야합니다 ( Peter Norvig모든 스도쿠 퍼즐 풀기 참조) .


5
거의 5 ~ 6 회 후에 답변을 읽었습니다. 내가 생각하는 다른 사용자를 유치하기에는 좋지만 너무 길었습니다. 나는 여기서 재귀를 가르치는 것에 관해 많은 것을 배웠다. 교사로서, 재귀 프로그래머
Gulshan

9
@Gulshan, 나는이 답변이 어느 것이 든 포괄적이며 캐주얼 독자들에 의해 쉽게 '탈지'된다고 생각한다. 따라서, 그것은 static unsigned int vote = 1;나에게서 얻는다 . 만약 당신이 원한다면 정적 유머를 용서하십시오 :) 이것은 지금까지 가장 좋은 대답입니다.
Tim Post

1
@Gulsan : 배우고 자하는 사람 만이 시간을내어 기꺼이 할 것입니다. :) 정말 상관 없습니다. 때로는 짧은 대답이 우아하고 일반적인 개념을 시작하거나 설명하기 위해 많은 유용하고 필요한 정보를 전달합니다. 나는 그 질문에 대해 더 긴 대답을 원했고 OP가 "올바른"답변을 얻은 질문에 대해 언급하고 비슷한 질문을하는 것을 고려할 때 동일한 종류의 대답을 전달하는 것이 적절합니다. 당신이 뭔가를 배웠습니다.
haylem

@Gulshan : 이제 당신의 대답에 관해 : 첫 번째 요점은 저를 많이 혼동했습니다. 나는 당신이 재귀 함수의 개념을 시간이 지남에 따라 점차적으로 상태를 변화시키는 것으로 묘사하고 싶지만, 당신의 발표 방식이 조금 이상하다고 생각합니다. 3 점을 읽은 후에는 많은 학생들이 갑자기 하노이를 풀지 못할 것으로 기대합니다. 그러나 그것은 단지 문구 문제 일 수 있습니다.
haylem

나는 동일한 변수가 다른 재귀 깊이에서 다른 값을 가질 수 있음을 보여주는 좋은 방법을 발견했습니다. 재귀 적 코드에 따라 학생들이 수행하는 단계와 변수의 값을 기록하도록 고려하십시오. 그들이 재귀에 도달하면, 새로운 조각으로 다시 시작하게하십시오. 종료 조건에 도달하면 이전 조각으로 다시 이동합니다. 이것은 본질적으로 호출 스택을 에뮬레이트하지만 이러한 차이점을 보여주는 좋은 방법입니다.
Andy Hunt

58

동일한 함수 내에서 함수를 호출합니다.


2
이것은 실제로 시작하는 가장 좋은 설명입니다. 간단하고 요점; 이 요약을 설정 한 후에는 모든 헤드 트립 세부 사항으로 이동하십시오.
jhocking

27

재귀는 자신을 호출하는 함수입니다.

사용 방법, 사용시기 및 나쁜 디자인을 피하는 방법을 알아야합니다.

당신이 알아야 할 가장 중요한 것은 결코 끝나지 않는 루프를 얻지 않도록 매우 조심하는 것입니다. pramodc84 에서 귀하의 질문에 대한 답변은 다음과 같은 결함이 있습니다. 결코 끝나지 않습니다 ...
재귀 함수는 항상 자신을 다시 호출 해야하는지 여부를 결정하는 조건을 확인해야합니다.

재귀를 사용하는 가장 전형적인 예는 깊이가 정적 인 제한이없는 트리를 사용하는 것입니다. 이것은 재귀를 사용해야하는 작업입니다.


재귀 적으로 분명히 더 좋을지라도 반복적으로 마지막 단락을 구현할 수 있습니다. "사용 방법, 사용시기 및 나쁜 디자인을 피하는 방법을 아는 것이 중요합니다.이를 위해서는 직접 사용해보고 어떤 일이 발생하는지 이해해야합니다." 그런 것들.
haylem

@haylem : "사용 방법, 사용시기 및 나쁜 디자인을 피하는 방법"에 대한 답변은 OP가 요청한 내용에 더 적합 할 것입니다. "라고 말했듯이) 여기에는 질문에 대한 빠른 답변보다는 더 많은 교수법에 대한 광범위한 강의가 필요합니다. 그래도 대답을 잘했습니다 . +1 ... 개념을 더 잘 이해해야하는 사람은 답을 읽으면 도움이됩니다.
awe

서로를 호출하는 한 쌍의 함수는 어떻습니까? A는 어떤 조건에 도달 할 때까지 A를 다시 호출하는 B를 호출합니다. 이것은 여전히 ​​재귀로 간주됩니까?
santiagozky 2016 년

예, 함수는 a여전히을 호출 하여 간접적으로 호출 b합니다.
kindall

1
@ santiagozky : kindall이 말했듯이 여전히 재귀입니다. 그러나 나는 그것을하지 않는 것이 좋습니다. 재귀를 사용할 때 코드에서 재귀가 있음을 분명히해야합니다. 다른 함수를 통해 간접적으로 자신을 호출하면 현재 상황을 파악하기가 훨씬 어려워집니다. 함수가 효과적으로 자신을 호출한다는 것을 모른다면, (또는이 함수를 만들지 않은 다른 사람이) 재귀에 대한 조건을 깰 수있는 상황에 처할 수 있습니다 (코드의 기능을 변경하는 동안) 끝없는 루프와 교착 상태에.
awe

21

재귀 프로그래밍은 자체 버전을보다 쉽게 ​​해결하기 위해 점진적으로 문제를 줄이는 프로세스입니다.

모든 재귀 함수는 다음과 같은 경향이 있습니다.

  1. 처리 할 목록 또는 다른 구조 또는 문제 도메인
  2. 현재 지점 / 단계 처리
  3. 나머지 / 하위 도메인에서 자체 호출
  4. 하위 도메인 작업의 결과를 결합하거나 사용하십시오

2 단계가 3 이전이고 4 단계가 사소한 경우 (연결, 합 또는 기타)에는 꼬리 재귀가 가능 합니다. 현재 단계를 완료하기 위해 문제의 서브 도메인 (들)의 결과가 필요할 수 있으므로 2 단계는 종종 3 단계 이후에 와야합니다.

이진 트리를 순회합니다. 순회는 필요한 항목에 따라 선주문, 주문 또는 주문으로 이루어질 수 있습니다.

   B
A     C

선주문 : BAC

traverse(tree):
    visit the node
    traverse(left)
    traverse(right)

순서 : ABC

traverse(tree):
    traverse(left)
    visit the node
    traverse(right)

주문 후 : ACB

traverse(tree):
    traverse(left)
    traverse(right)
    visit the node

매우 많은 재귀 문제는 작업 의 특정 사례 또는 접힘입니다. 이 두 작업 만 이해하면 재귀에 대한 유용한 사용 사례를 크게 이해할 수 있습니다.


실제 재귀의 주요 구성 요소는 솔루션을 약간 더 작은 문제에 사용하여 더 큰 문제를 해결하는 것입니다. 그렇지 않으면 무한 재귀가 있습니다.
Barry Brown

@ 배리 브라운 : 그렇습니다. 따라서 내 진술은 "... 자체 버전을 쉽게 해결하기 위해 문제를 줄이는 것"
Orbling

나는 반드시 그렇게 말하지 않을 것입니다 ... 종종 분할, 정복 문제 또는 간단한 경우에 이르는 재발 관계를 정의하는 상황에서 종종 그렇습니다. 그러나 문제의 각 반복 N에 대해 계산 가능한 사례 N + 1이 있음을 증명하는 것이 더 중요하다고 말하고 싶습니다.
haylem

1
@Sean McMillan : 재귀는 적합한 도메인에서 사용될 때 강력한 도구입니다. 너무 자주 나는 그것이 사소한 문제를 처리 하는 영리한 방법으로 사용되는 것을 보았습니다 .
Orbling

20

OP는 재귀가 현실 세계에 존재하지 않는다고 말했지만 나는달라고 간청합니다.

피자를 자르는 실제 '동작'을 보자. 피자를 오븐에서 꺼낸 후 서빙하려면 반으로 자른 다음 반을 자른 다음 다시 반으로 자르십시오.

원하는 결과 (슬라이스 수)를 얻을 때까지 반복해서 수행하는 피자를 자르는 작업입니다. 그리고 논쟁을 위해 자르지 않은 피자는 조각 자체라고 가정 해 봅시다.

다음은 Ruby의 예입니다.

데프 컷 _ 피자 (기존 _ 슬라이스, 원하는 _ 슬라이스)
  existing_slices! = desired_slices 인 경우
    # 우리는 아직 모든 사람에게 먹이를 줄 수있는 슬라이스가 충분하지 않습니다.
    # 피자 조각을 자르고있어 그 수가 두배가되었습니다
    new_slices = 기존 _ 슬라이스 * 2 
    # 그리고 이것은 재귀 호출입니다.
    cut_pizza (새 슬라이스, 원하는 슬라이스)
  그밖에
    # 원하는 슬라이스 수를 가지고 있으므로
    # 계속 재귀하는 대신 여기에
    existing_slices를 반환
  종료
종료

피자 = 1 # 전체 피자, '한 조각'
cut_pizza (pizza, 8) # => 8을 얻습니다

따라서 실제 작업은 피자를 자르는 것이며 재귀는 원하는 것을 가질 때까지 반복해서 똑같은 일을하고 있습니다.

재귀 함수로 구현할 수있는 정리는 다음과 같습니다.

  • 여러 달에 걸친 복리 계산.
  • 파일 시스템은 파일 때문에 디렉토리 때문에 파일 시스템에서 파일을 찾습니다.
  • 일반적으로 나무로 작업하는 것과 관련된 모든 것이 있다고 생각합니다.

파일 이름을 기준으로 파일을 찾도록 프로그램을 작성하고 파일이 발견 될 때까지 자신을 호출하는 함수를 작성하는 것이 좋습니다. 서명은 다음과 같습니다.

find_file_by_name(file_name_we_are_looking_for, path_to_look_in)

따라서 다음과 같이 호출 할 수 있습니다.

find_file_by_name('httpd.conf', '/etc') # damn it i can never find apache's conf

내 의견으로는 단순히 기계 역학을 프로그래밍하는 것입니다. 변수를 사용하여 이것을 다시 작성할 수 있지만 이것은 '더 나은'솔루션입니다. 그것에 대해 신비하거나 어려운 것은 없습니다. 몇 가지 재귀 함수를 작성하면 프로그래밍 도구 상자에서 또 다른 기계적 트릭을 클릭하고 huzzah 합니다.

추가 크레디트cut_pizza 위 의 예는 2의 거듭 제곱 (예 : 2 또는 4 또는 8 또는 16)이 아닌 여러 조각을 요청하면 스택 수준에 너무 큰 오류가 발생합니다. 누군가 10 조각을 요청하면 영원히 실행되지 않도록 수정할 수 있습니까?


16

좋아, 나는 이것을 간단하고 간결하게 유지하려고 노력할 것이다.

재귀 함수는 자신을 호출하는 함수입니다. 재귀 함수는 다음 세 가지로 구성됩니다.

  1. 논리
  2. 자신을 부르다
  3. 종료시기

재귀 메서드를 작성하는 가장 좋은 방법은 반복하려는 프로세스의 한 루프 만 처리하는 간단한 예제로 작성하려는 메서드를 생각한 다음 메서드 자체에 호출을 추가하고 원하는 때 추가하는 것입니다. 끝내다. 배우는 가장 좋은 방법은 모든 것처럼 연습하는 것입니다.

이것은 프로그래머 웹 사이트이므로 코드 작성을 자제하지만 여기에 좋은 링크가 있습니다.

농담을하면 재귀의 의미를 알 수 있습니다.


이 답변을 참조하십시오 : programmers.stackexchange.com/questions/25052/… (-:
Murph

4
엄격하게 재귀에는 종료 조건이 필요하지 않습니다. 재귀 종료를 보장하면 재귀 함수가 해결할 수있는 문제 유형이 제한되며, 일부 유형의 의미 론적 프리젠 테이션은 전혀 종료가 필요하지 않습니다.
Donal Fellows

6

재귀는 프로그래머가 자체적으로 함수 호출을 호출하는 데 사용할 수있는 도구입니다. 피보나치 수열은 재귀 사용 방법의 교과서 예입니다.

대부분의 재귀 코드는 모두 반복 함수로 표현할 수는 없지만 일반적으로 지저분합니다. 다른 재귀 프로그램의 좋은 예는 트리, 이진 검색 트리 및 퀵 정렬과 같은 데이터 구조입니다.

재귀는 코드를 느슨하게 만드는 데 사용되며 일반적으로 속도가 느리고 더 많은 메모리가 필요합니다.


속도가 더 느리거나 더 많은 메모리를 필요로하는지 여부는 사용에 따라 다릅니다.
Orbling

5
피보나치 수열 계산은 재귀 적으로 끔찍한 일입니다. 나무 순회는 재귀를 훨씬 더 자연스럽게 사용합니다. 일반적으로 재귀가 잘 사용되면 호출 스택 대신 자체 스택을 유지 관리해야하기 때문에 속도가 느려지지 않고 더 많은 메모리가 필요하지 않습니다.
David Thornley

1
@Dave : 논쟁의 여지가 없지만, 피보나치가 좋은 예라고 생각합니다.
Bryan Harrington

5

나는 이것을 사용하고 싶다 :

가게에 어떻게 걸어가요?

상점 입구에 있다면 간단히 살펴보십시오. 그렇지 않으면 한 걸음 내딛고 나머지 길을 가게로 걸어가십시오.

세 가지 측면을 포함하는 것이 중요합니다.

  • 사소한 기본 사례
  • 문제의 작은 조각을 해결
  • 나머지 문제를 재귀 적으로 해결

우리는 실제로 일상 생활에서 재귀를 많이 사용합니다. 우리는 그렇게 생각하지 않습니다.


THat은 재귀가 아닙니다. 두 개로 나누면됩니다. 가게까지 반은 걸어 가고 나머지 반은 걸어보세요. 재귀.

2
재귀입니다. 이것은 나누고 정복하는 것이 아니라 한 가지 유형의 재귀입니다. 경로 찾기와 같은 그래프 알고리즘은 재귀 개념으로 가득합니다.
deadalnix

2
이것은 재귀이지만, "한 단계를 밟은 다음 나머지 단계를 저장소로 이동"하는 것을 반복 알고리즘으로 정신적으로 변환하기가 쉽지 않기 때문에 나쁜 예라고 생각합니다. 멋지게 작성된 for루프를 무의미한 재귀 함수로 변환하는 것과 같습니다 .
Brian

3

내가 당신에게 지적하는 가장 좋은 예는 K & R의 C Programming Language입니다. 그 책에서 (메모리에서 인용하고 있습니다), 재귀 색인 페이지의 항목 (단독)은 재귀에 대해 이야기하는 실제 페이지와 색인 페이지도 마찬가지입니다.


2

Josh K는 이미 Matroshka 인형을 언급했습니다 . 가장 짧은 인형 만이 아는 것을 배우고 싶다고 가정하십시오. 문제는 그녀가 원래 첫 번째 그림의 왼쪽에있는 키가 큰 인형 안에 살고 있기 때문에 직접 대화 할 수 없다는 것 입니다. 이 구조는 가장 큰 인형으로 끝날 때까지 (인형은 더 큰 인형 안에 살고 있습니다).

그래서 당신이 할 수있는 유일한 것은 가장 큰 인형에게 질문을하는 것입니다. 가장 큰 인형 (답변을 모르는 사람)은 질문을 더 짧은 인형 (첫 번째 그림의 오른쪽에 있음)으로 전달해야합니다. 그녀는 또한 대답이 없기 때문에 다음으로 짧은 인형에게 물어볼 필요가 있습니다. 메시지가 가장 짧은 인형에 도달 할 때까지 이렇게됩니다. 가장 짧은 인형 (비밀 답변을 아는 유일한 사람)은 다음 키 큰 인형 (왼쪽에 있음)으로 답을 전달하고 다음 키 큰 인형에게 전달합니다. 가장 큰 인형 인 최종 목적지에 도달하고 마침내 ... you :)

이것이 재귀가 실제로하는 일입니다. 함수 / 메소드는 예상되는 답변을 얻을 때까지 자신을 호출합니다. 따라서 재귀 코드를 작성할 때 재귀 종료 시점을 결정하는 것이 매우 중요합니다.

가장 좋은 설명은 아니지만 희망적으로 도움이됩니다.


2

재귀 n. -연산이 자체적으로 정의되는 알고리즘 설계 패턴.

전형적인 예는 숫자 n의 계승을 찾는 것입니다. 0! = 1이고 다른 자연수 N의 경우 N의 계승은 N보다 작거나 같은 모든 자연수의 곱입니다. 따라서 6! = 6 * 5 * 4 * 3 * 2 * 1 = 720.이 기본 정의를 사용하면 간단한 반복 솔루션을 만들 수 있습니다.

int Fact(int degree)
{
    int result = 1;
    for(int i=degree; i>1; i--)
       result *= i;

    return result;
}

그러나 작업을 다시 점검하십시오. 6! = 6 * 5 * 4 * 3 * 2 * 1. 같은 정의로 5! = 5 * 4 * 3 * 2 * 1, 6이라고 말할 수 있습니다! = 6 * (5!). 차례로 5! = 5 * (4!) 등. 이렇게하면 모든 이전 작업의 결과에 대해 수행 된 작업으로 문제가 줄어 듭니다. 결국 결과는 정의에 의해 알려진 기본 사례라고하는 지점으로 줄어 듭니다. 이 경우 0! = 1 (대부분의 경우 1! = 1이라고도 함). 컴퓨팅에서 우리는 종종 메소드 호출 자체를 통해 더 작은 입력을 전달함으로써 매우 유사한 방식으로 알고리즘을 정의 할 수 있습니다. 따라서 많은 재귀를 통한 문제를 기본 사례로 줄입니다.

int Fact(int degree)
{
    if(degree==0) return 1; //the base case; 0! = 1 by definition
    else return degree * Fact(degree -1); //the recursive case; N! = N*(N-1)!
}

많은 언어에서 삼항 연산자를 사용하여 더 단순화 할 수 있습니다 (때로는 연산자를 제공하지 않는 언어에서는 Iif 함수로 표시됨).

int Fact(int degree)
{
    //reads equivalently to the above, but is concise and often optimizable
    return degree==0 ? 1: degree * Fact(degree -1);
}

장점 :

  • 자연스러운 표현-많은 유형의 알고리즘에서 이것은 함수를 표현하는 매우 자연스러운 방법입니다.
  • LOC 감소-함수를 재귀 적으로 정의하는 것이 훨씬 간결합니다.
  • 속도-특정 경우 언어 및 컴퓨터 아키텍처에 따라 알고리즘의 재귀가 동등한 반복 솔루션보다 빠릅니다. 일반적으로 함수 호출은 반복적으로 반복하는 데 필요한 작업 및 메모리 액세스보다 하드웨어 수준에서 더 빠릅니다.
  • 분 산성-많은 재귀 알고리즘은 "분할 및 정복"사고 방식입니다. 연산 결과는 입력의 두 반쪽 각각에서 수행 된 동일한 연산 결과의 함수입니다. 이를 통해 각 레벨에서 작업을 2 개로 분할 할 수 있으며 가능한 경우 나머지 절반을 다른 "실행 단위"에 처리 할 수 ​​있습니다. 이것은 반복 알고리즘으로 일반적으로 어렵거나 불가능합니다.

단점 :

  • 이해가 필요합니다. 진행 상황을 이해하려면 효과적인 재귀 알고리즘을 작성하고 유지하기 위해 단순히 재귀 개념을 파악해야합니다. 그렇지 않으면 그냥 흑 마법처럼 보입니다.
  • 상황에 따라 다름-재귀가 좋은 아이디어인지 아닌지는 알고리즘 자체가 얼마나 우아하게 정의 될 수 있는지에 달려 있습니다. 예를 들어 재귀 적 SelectionSort를 작성할 수 있지만 반복 알고리즘은 일반적으로 더 이해하기 쉽습니다.
  • 호출 스택을위한 RAM 액세스 교환-일반적으로 함수 호출은 캐시 액세스보다 저렴하여 반복보다 재귀를 더 빠르게 할 수 있습니다. 그러나 일반적으로 반복 스택 알고리즘이 작동하는 곳에서 재귀 오류가 발생할 수있는 호출 스택의 깊이에는 제한이 있습니다.
  • 무한 재귀-언제 중지해야하는지 알아야합니다. 무한 반복도 가능하지만 관련된 루핑 구문은 일반적으로 이해하기 쉽고 디버그하기 쉽습니다.

1

내가 사용하는 예는 실생활에서 직면 한 문제입니다. 컨테이너 (여행용 대형 배낭 등)가 있으며 총 무게를 알고 싶습니다. 용기에 2 ~ 3 개의 느슨한 품목과 다른 용기 (예 : 재료 자루)가 있습니다. 총 용기의 무게는 분명히 빈 용기의 무게와 그 안에 들어있는 모든 것의 무게를 합한 것입니다. 느슨한 품목의 경우 무게를 측정 할 수 있으며, 재료 자루의 경우 무게를 측정하거나 "각 포장의 무게는 빈 용기의 무게와 그 안에 들어있는 모든 것의 무게입니다"라고 말할 수 있습니다. 그런 다음 컨테이너에 항목이 느슨한 지점에 도달 할 때까지 컨테이너에 컨테이너 등을 계속 넣습니다. 재귀입니다.

실생활에서는 결코 일어나지 않는다고 생각할 수도 있지만 특정 회사 나 부서의 직원 수를 계산하거나 합산하면 회사를 위해 일하는 사람들과 부서의 사람들이 혼합되어 있습니다. 부서 등이 있습니다. 또는 지역이 있고 일부 지역에 지역 등이있는 국가에서의 판매. 이런 종류의 문제는 비즈니스에서 항상 발생합니다.


0

재귀는 많은 계산 문제를 해결하는 데 사용될 수 있습니다. 예를 들어, 파티에 n 명 그룹이 있고 (n> 1) 모든 사람이 다른 사람의 손을 정확히 한 번 흔든다 고 가정합니다. 악수는 몇 번이나됩니까? 솔루션이 C (n, 2) = n (n-1) / 2임을 알 수 있지만 다음과 같이 재귀 적으로 해결할 수 있습니다.

두 사람 만 있다고 가정합니다. 그런 다음 (검사하여) 대답은 분명히 1입니다.

세 사람이 있다고 가정합니다. 한 사람을 골라 내고 다른 두 사람과 악수한다는 점에 유의하십시오. 그 후에는 다른 두 사람 간의 핸드 셰이크 만 계산해야합니다. 우리는 이미 그것을 지금 당장했고 그것은 1입니다. 그래서 답은 2 + 1 = 3입니다.

n 명이 있다고 가정하십시오. 이전과 동일한 논리에 따라 (n-1) + (n-1 명의 핸드 셰이크 수)입니다. 확장하면 (n-1) + (n-2) + ... + 1이됩니다.

재귀 함수로 표현

f (2) = 1
f (n) = n-1 + f (n-1), n> 2


0

인생에서 (컴퓨터 프로그램과는 달리) 재귀는 우리의 직접적인 통제하에 거의 일어나지 않습니다. 왜냐하면 혼란 스럽기 때문입니다. 또한 인식은 기능적으로 순수하지 않고 부작용에 관한 경향이 있으므로 재귀가 발생하면 인식하지 못할 수 있습니다.

그러나 세계에서는 재귀가 발생합니다. 많이.

좋은 예는 물 순환의 단순화 된 버전입니다.

  • 태양은 호수를 데 웁니다
  • 물이 하늘로 올라가 구름을 만듭니다
  • 구름이 산으로 표류
  • 산에서 공기가 너무 차가워 져 습기를 유지할 수 없습니다
  • 비가 내린다
  • 강이 형성됩니다
  • 강물이 호수로 흘러

이것은 다시 발생하게하는주기입니다. 재귀 적입니다.

재귀를 얻을 수있는 또 다른 장소는 영어 (일반적으로 사람의 언어)입니다. 처음에는 그것을 인식하지 못할 수도 있지만, 문장을 생성 할 수있는 방법은 재귀 적입니다. 규칙을 통해 하나의 심볼 인스턴스를 동일한 심볼의 다른 인스턴스와 나란히 포함 할 수 있기 때문입니다.

Steven Pinker의 언어 본능 :

소녀가 아이스크림을 먹거나 소녀가 사탕을 먹는다면 소년은 핫도그를 먹는다

그것은 다른 전체 문장을 포함하는 전체 문장입니다.

여자 아이가 아이스크림을 먹는다

여자 아이가 사탕을 먹는다

소년은 핫도그를 먹는다

완전한 문장을 이해하는 행위에는 작은 문장을 이해하는 것이 포함되는데, 작은 문장은 동일한 문장을 사용하여 완전한 문장으로 이해됩니다.

프로그래밍 관점에서 재귀를 이해하려면 재귀로 해결할 수있는 문제를 살펴보고 그 이유와 그 의미를 이해하는 것이 가장 쉽습니다.

예를 들어, 나는 가장 큰 공통 제수 함수 또는 짧게는 gcd를 사용합니다.

당신은 당신의 두 숫자를 가지고 ab. 그들의 gcd를 찾으려면 (0이 아닌 것으로 가정) a로 균등하게 나눌 수 있는지 확인해야합니다 b. 그것이 bgcd 인 경우, 그렇지 않으면의 gcd b및 나머지 를 확인해야합니다 a/b.

gcd 함수가 gcd 함수를 호출하므로이 함수가 재귀 함수임을 이미 알 수 있습니다. 집으로 망치기 위해 여기에 c #이 있습니다 (다시 말하면 0이 매개 변수로 전달되지 않는다고 가정).

int gcd(int a, int b)
{   
    if (a % b == 0) //this is a stopping condition
    {
        return b;
    }

    return (gcd(b, a % b)); //the call to gcd here makes this function recursive
}

프로그램에서 중지 조건을 갖는 것이 중요합니다. 그렇지 않으면 함수가 영원히 반복되어 결국 스택 오버플로가 발생합니다!

while 루프 나 다른 반복 구조가 아닌 재귀를 사용하는 이유는 코드를 읽을 때 수행중인 작업과 수행 할 작업을 알려주기 때문에 올바르게 작동하는지 파악하기가 더 쉽기 때문입니다. .


1
물 순환 예제 반복을 발견했습니다. 두 번째 언어 예제는 재귀보다 분할 및 정복보다 더 많은 것처럼 보입니다.
굴샨

@ 굴샨 : 물 순환은 재귀 함수처럼 자체 반복되기 때문에 재귀 적이라고 말하고 싶습니다. for 루프처럼 여러 객체 (벽, 천장 등)에 대해 동일한 단계를 수행하는 방을 그리는 것과는 다릅니다. 이 언어 예제는 나누기와 정복을 사용하지만 중첩 된 문장을 처리하기 위해 자체를 호출하는 "함수"도 사용하므로 재귀 적입니다.
Matt Ellen

물 순환에서주기는 태양에 의해 시작되며주기의 다른 요소는 태양을 다시 시작하게하지 않습니다. 재귀 호출은 어디에 있습니까?
굴샨

재귀 호출이 없습니다! 기능이 아닙니다. : D 재귀는 자체 재귀를 유발하므로 재귀 적입니다. 호수에서 나온 물이 호수로 돌아와서주기가 다시 시작됩니다. 다른 시스템이 호수에 물을 넣는다면 반복 될 것입니다.
매트 엘렌

1
물 순환은 while 루프입니다. 물론 while 루프는 재귀를 사용하여 표현할 수 있지만 그렇게하면 스택이 손상 될 수 있습니다. 제발 하지마
Brian

0

다음은 재귀에 대한 실제 예입니다.

그들이 코믹한 컬렉션을 가지고 있다고 상상해 보도록하자. 조심하십시오-실제로 컬렉션이 있으면 아이디어를 언급했을 때 즉시 죽일 수 있습니다.

이제이 매뉴얼의 도움을 받아이 분류되지 않은 큰 만화 더미를 정렬 해 보자.

Manual: How to sort a pile of comics

Check the pile if it is already sorted. If it is, then done.

As long as there are comics in the pile, put each one on another pile, 
ordered from left to right in ascending order:

    If your current pile contains different comics, pile them by comic.
    If not and your current pile contains different years, pile them by year.
    If not and your current pile contains different tenth digits, pile them 
    by this digit: Issue 1 to 9, 10 to 19, and so on.
    If not then "pile" them by issue number.

Refer to the "Manual: How to sort a pile of comics" to separately sort each
of the new piles.

Collect the piles back to a big pile from left to right.

Done.

여기서 가장 좋은 점은 단일 이슈에 해당 할 때 로컬 더미가 바닥에 보이도록 전체 "스택 프레임"을 갖습니다. 그들에게 매뉴얼의 여러 인쇄물을 제공하고 현재이 레벨에있는 마크 (예 : 지역 변수의 상태)와 함께 각 더미 레벨을 하나씩 옆에 두십시오. 따라서 각 완료에서 계속할 수 있습니다.

그것은 재귀가 기본적으로하는 것입니다. 아주 세부적인 수준에서 더 똑같은 과정을 수행하는 것입니다.


-1
  • 종료 조건에 도달하면 종료
  • 사물의 상태를 바꿀 무언가를하다
  • 현재 상태로 시작하여 모든 일을한다

재귀는 무언가에 도달 할 때까지 반복해야하는 것을 표현하는 매우 간결한 방법입니다.



-2

재귀에 대한 훌륭한 설명은 말 그대로 '자체 내에서 재발하는 행동'입니다.

벽을 그리는 화가를 생각해보십시오. 행동은 "오른쪽으로 스쿠 트하는 것보다 천장에서 바닥으로 스트립을 페인트하고 (오른쪽으로 스쿠 트하는 것보다 천장에서 바닥으로 스트립을 페인트하고) 오른쪽에서 조금 튀기지 말고 천장에서 바닥으로 스트리핑하십시오.

그의 paint () 함수는 자신의 더 큰 paint_wall () 함수를 구성하기 위해 계속해서 자신을 호출합니다.

잘만되면이 가난한 화가는 어떤 종류의 정지 조건이 있습니다 :)


6
나 에게이 예제는 재귀가 아닌 반복 절차처럼 보입니다.
굴샨

@Gulshan : 재귀와 반복은 비슷한 일을하며 종종 일과 잘 어울립니다. 기능적 언어는 일반적으로 반복 대신 재귀를 사용합니다. 같은 것을 반복해서 쓰는 것이 어색한 재귀의 더 좋은 예가 있습니다.
David Thornley
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.