계승, 피보나치 수 등이없는 재귀


47

재귀에 대해 찾을 수있는 거의 모든 기사에는 다음과 같은 계승 또는 피보나치 수의 예가 포함됩니다.

  1. 수학
  2. 실생활에서 쓸모없는

재귀를 가르치는 흥미로운 수학 이외의 코드 예제가 있습니까?

분할 및 정복 알고리즘을 생각하고 있지만 일반적으로 복잡한 데이터 구조가 필요합니다.


26
귀하의 질문이 완전히 유효하지만 피보나치 수 를 실생활에서 쓸모없는 것으로 부르는 것을 망설이고 있습니다. factorial도 마찬가지입니다 .
Zach L

2
Little Schemer는 Fact 또는 Fib를 사용하지 않는 재귀에 대한 전체 책입니다. junix-linux-config.googlecode.com/files/…
Eric Wilson

5
@Zach : 그럼에도 불구하고 재귀는 지수 실행 시간 때문에 피보나치 수 를 구현 하는 끔찍한 방법 입니다.
dan04

2
@ dan04 : 재귀는 대부분의 언어로 스택 오버플로 가능성으로 인해 거의 모든 것을 구현하는 끔찍한 방법입니다.
R ..

5
@ dan04 당신의 언어가 대부분의 기능적인 언어들처럼 테일 콜 최적화를 구현할만큼 똑똑하지 않다면
Zachary K

답변:


107

디렉토리 / 파일 구조는 재귀 사용의 가장 좋은 예입니다. 시작하기 전에 모든 사람들이 이해하지만 트리와 같은 구조와 관련된 모든 것이 가능하기 때문에.

void GetAllFilePaths(Directory dir, List<string> paths)
{
    foreach(File file in dir.Files)
    {
        paths.Add(file.Path);
    }

    foreach(Directory subdir in dir.Directories)
    {
        GetAllFilePaths(subdir, paths)
    }
}

List<string> GetAllFilePaths(Directory dir)
{
    List<string> paths = new List<string>();
    GetAllFilePaths(dir, paths);
    return paths;
}

1
고마워, 나는 파일 시스템을 가지고 갈 것이라고 생각한다. 구체적이고 많은 실제 예제에 사용할 수 있습니다.
시냅스

9
참고 : unix 명령은 종종 -r 옵션 (예 : cp 또는 rm)을 제외시킵니다. -r 재귀를 나타냅니다.
deadalnix

7
실제 파일 시스템에서 실제로는 반드시 트리가 아닌 직접 그래프이므로 파일 시스템에서 지원하는 경우 하드 링크 등이 조인 및 사이클
jk를

1
@ jk : 디렉토리는 하드 링크 될 수 없으므로 둘 이상의 위치에 나타날 수있는 일부 잎이 있으며 심볼릭 링크를 제외하면 실제 파일 시스템은 트리입니다.
R ..

1
일부 파일 시스템에는 디렉토리에 대한 다른 특성 (예 : NTFS 재분석 지점)이 있습니다. 내 포인트는 실제 파일 시스템에 예기치 않은 결과가 발생할 수 있습니다이를 구체적으로 인식하지 그 코드
JK합니다.

51

나무 구조와 관련된 것을 찾으십시오. 트리는 비교적 이해하기 쉽고 목록과 같은 선형 데이터 구조보다 재귀 솔루션의 아름다움이 훨씬 빨리 나타납니다.

고려해야 할 사항 :

  • 파일 시스템-기본적으로 나무입니다. 좋은 프로그래밍 작업은 특정 디렉토리와 모든 하위 디렉토리에서 모든 .jpg 이미지를 가져 오는 것입니다.
  • 조상-가계도가 주어지면 공통 조상을 찾기 위해 걸어야하는 세대 수를 찾으십시오. 또는 나무의 두 사람이 같은 세대에 속하는지 확인하십시오. 또는 나무에있는 두 사람이 합법적으로 결혼 할 수 있는지 확인하십시오 (관할 지역에 따라 다름)
  • HTML과 유사한 문서-문서의 직렬 (텍스트) 표현과 DOM 트리 사이를 변환합니다. DOM의 하위 집합에 대한 작업을 수행합니다 (xpath의 하위 집합을 구현할 수도 있습니까?). ...

이들은 모두 실제 실제 시나리오와 관련이 있으며 실제 중요 응용 프로그램에서 모두 사용할 수 있습니다.


물론 자신 만의 트리 구조를 디자인 할 자유가있을 때마다 부모 / 다음 형제 / 등에 대한 포인터 / 참조를 유지하는 것이 거의 항상 좋습니다. 재귀없이 트리를 반복 할 수 있도록 노드에서.
R ..

1
포인터는 그것과 어떤 관련이 있습니까? 첫 번째 자녀, 다음 형제 자매 및 부모에 대한 포인터가 있더라도 어떻게 든 나무를 걸어야하며 경우에 따라 재귀가 가장 실현 가능한 방법입니다.
tdammers

40

https://stackoverflow.com/questions/105838/real-world-examples-of-recursion

  • 전염성 감염 모델링
  • 지오메트리 생성
  • 디렉토리 관리
  • 정렬

https://stackoverflow.com/questions/2085834/how-did-you-practically-use-recursion

  • 광선 추적
  • 체스
  • 소스 코드 구문 분석 (언어 문법)

https://stackoverflow.com/questions/4945128/what-is-a-good-example-of-recursion-other-than-generating-a-fibonacci-sequence

  • BST 검색
  • 하노이 타워
  • 회문 검색

https://stackoverflow.com/questions/126756/examples-of-recursive-functions

  • 취침 시간 이야기로 재귀를 보여주는 멋진 영어 이야기를 제공합니다.

10
이 이론적으로 질문에 대답 할 수 있습니다 동안, 바람직 것 여기 질문과 답변의 본질적인 부분을 포함하고 참조 할 수 있도록 링크를 제공합니다. 질문이 SO에서 제거 된 경우 귀하의 답변은 완전히 쓸모가 없습니다.
Adam Lear

@Anna 글쎄, 사용자는 자신의 질문을 삭제할 수 없으므로 어떻게 될까요?
vemv

4
@vemv 투표 변경, 중재자, 주제 변경에 관한 규칙 삭제 ... 발생할 수 있습니다. 어느 쪽이든, 여기에 더 완전한 답을 얻는 것이 방문자를 박쥐에서 4 개의 다른 페이지로 보내는 것보다 바람직합니다.
Adam Lear

@Anna : 이러한 사고 방식에 따라 "정확한 복제본"으로 닫힌 모든 질문에는 원래 붙여 넣은 답변이 포함되어야합니다. 프로그래머에 대한 중복 질문?
SF.

1
@SF 복제본으로 닫을 수 있다면 교차 사이트 복제본은 지원되지 않습니다. 프로그래머는 별도의 사이트이므로 여기에 이상적으로 대답하면 SO를 다른 참조로 사용하고 완전히 위임하지는 않습니다. "답이이 책에 있습니다"라고 말하는 것만 큼 다르지 않습니다. 기술적으로는 사실이지만 참조를 참조하지 않고 바로 사용할 수는 없습니다.
Adam Lear

22

내 마음에 오는 더 실용적인 문제는 다음과 같습니다.

  • 정렬 병합
  • 이진 검색
  • 트리에서 순회, 삽입 및 제거 (데이터베이스 응용 프로그램에서 주로 사용됨)
  • 순열 생성기
  • 스도쿠 솔버 (역 추적)
  • 맞춤법 검사 (역 추적과 함께)
  • 구문 분석 (예 : 접두사를 접미사 표기법으로 변환하는 프로그램)

11

QuickSort는 가장 먼저 떠오르는 것입니다. 이진 검색은 재귀 문제입니다. 그 외에도 재귀 작업을 시작할 때 솔루션이 거의 무료로 빠지는 전체 클래스의 문제가 있습니다.


3
이진 검색은 종종 재귀 문제로 공식화되지만 명령 방식으로 구현하는 것은 쉽지 않습니다.
푹신한

코드를 사용하는 언어에 따라 명시 적으로 재귀 적이거나 명령 적이거나 재귀적일 수 있습니다. 그러나 솔루션을 얻기 위해 문제를 더 작고 작은 덩어리로 나누는 것은 여전히 ​​재귀 알고리즘입니다.
Zachary K

2
@Zachary : 이진 검색과 같은 테일 재귀 로 구현할 수있는 알고리즘 은 실제 재귀를 요구하는 것 (또는 동일한 고가의 공간 요구 사항을 가진 자체 상태 구조)과 근본적으로 다른 공간 클래스에 있습니다. 나는 그들이 같은 것처럼 함께 가르치는 것이 유익하다고 생각하지 않습니다.
R ..

8

파이썬에서 재귀 적으로 정의하고 정렬하십시오.

def sort( a ):
    if len(a) == 1: return a
    part1= sort( a[:len(a)//2] )
    part2= sort( a[len(a)//2:] )
    return merge( part1, part2 )

재귀 적으로 정의 된 병합

def merge( a, b ):
    if len(b) == 0: return a
    if len(a) == 0: return b
    if a[0] < b[0]:
        return [ a[0] ] + merge(a[1:], b)
    else:
        return [ b[0] ] + merge(a, b[1:]) 

재귀 적으로 정의 된 선형 검색.

def find( element, sequence ):
    if len(sequence) == 0: return False
    if element == sequence[0]: return True
    return find( element, sequence[1:] )

이진 검색으로 재귀 적으로 정의됩니다.

def binsearch( element, sequence ):
    if len(sequence) == 0: return False
    mid = len(sequence)//2
    if element < mid: 
        return binsearch( element, sequence[:mid] )
    else:
        return binsearch( element, sequence[mid:] )

6

어떤 의미에서 재귀는 솔루션을 나누고 정복하는 것입니다. 즉, 간단한 문제에 대한 해결책을 찾기 위해 문제 공간을 더 작은 문제로 분해 한 다음 원래의 문제를 재구성하여 정답을 작성합니다.

재귀를 가르치기 위해 수학을 포함하지 않는 몇 가지 예 (적어도 대학 시절에 기억했던 문제) :

다음은 역 추적을 사용하여 문제를 해결하는 예입니다.

다른 문제는 인공 지능 영역의 고전입니다. 심층 우선 검색 사용, 길 찾기, 계획.

이러한 모든 문제에는 일종의 "복잡한"데이터 구조가 포함되지만 수학 (숫자)으로 가르치지 않으려면 선택이 더 제한 될 수 있습니다. Yoy는 연결된 List와 같은 기본 데이터 구조로 가르치기를 시작할 수 있습니다. 예를 들어 List를 사용하여 자연수를 나타내는 경우 :

0 = 빈 목록 1 = 하나의 노드가있는 목록 2 = 2 개의 노드가있는 목록 ...

그런 다음이 데이터 구조와 관련하여 두 숫자의 합을 다음과 같이 정의하십시오. Empty + N = N Node (X) + N = Node (X + N)


5

하노이 타워는 재귀를 배우는 데 도움이되는 좋은 곳입니다.

웹에는 다양한 언어로 된 솔루션이 많이 있습니다.


3
이것은 실제로 제 생각에는 또 다른 나쁜 예입니다. 먼저 비현실적입니다. 사람들이 실제로 가지고있는 문제는 아닙니다. 둘째, 쉬운 비 재귀 솔루션이 있습니다. (하나는 : 디스크 번호를 매기십시오. 동일한 패리티의 디스크로 디스크를 옮기지 말고 마지막으로 이동 한 것을 절대로 취소하지 마십시오.이 두 규칙을 따르면 퍼즐을 최적의 솔루션으로 해결할 수 있습니다. 재귀가 필요하지 않습니다. )
Eric Lippert

5

회문 검출기 :

문자열로 시작 : "ABCDEEDCBA"시작 및 끝 문자가 동일하면 되풀이하고 "BCDEEDCB"등을 확인하십시오.


6
그것은 재귀없이 해결하는 것이 쉽지 않으며 IMHO는 그것 없이는 더 잘 해결됩니다.
Blrfl

3
동의하지만 OP는 데이터 구조를 최소한으로 사용하는 교육 예제를 구체적으로 요청했습니다.
NWS

5
학생들이 비 재귀 솔루션을 즉시 생각할 수 있다면 좋은 교수법이 아닙니다. 예를 들어 "루프와 관련하여 사소한 일이 있습니다. 이제 분명한 이유없이 더 어려운 방법을 보여 드리겠습니다."
Monica Monica


4

함수형 프로그래밍 언어에서 고차 함수를 사용할 수없는 경우 변경 가능한 상태를 피하기 위해 명령형 루프 대신 재귀가 사용됩니다.

F #은 두 가지 스타일을 모두 허용하는 불완전한 기능 언어이므로 여기서 두 가지를 비교하겠습니다. 다음은 목록의 모든 숫자를 합한 것입니다.

가변 변수가있는 명령 루프

let xlist = [1;2;3;4;5;6;7;8;9;10]
let mutable sum = 0
for x in xlist do
    sum <- sum + x

가변 상태가없는 재귀 루프

let xlist = [1;2;3;4;5;6;7;8;9;10]
let rec loop sum xlist = 
    match xlist with
    | [] -> sum
    | x::xlist -> loop (sum + x) xlist
let sum = loop 0 xlist

이러한 종류의 집계 고차 함수에서 캡처되며 편의 함수 를 사용하여 훨씬 간단하게 List.fold작성할 수 있습니다 .List.fold (+) 0 xlistList.sumList.sum xlist


당신은 나에게서 +1보다 더 많은 점수를받을 자격이 있습니다 재귀가없는 F #은 매우 지루합니다. 반복 구조 만 재귀 만하지 않는 Haskell에게는 더욱 그렇습니다!
schoetbi

3

나는 AI를 플레이하는 게임에서 재귀를 많이 사용했습니다. C ++로 작성하면 서로를 순서대로 호출하는 약 7 개의 함수 시리즈를 사용했습니다 (첫 번째 함수는 모든 함수를 무시하고 대신 2 개의 함수 체인을 호출하는 옵션이 있음). 검색하려는 나머지 깊이가 0이 될 때까지 두 체인의 최종 함수가 첫 번째 함수를 다시 호출했습니다.이 경우 최종 함수는 내 평가 함수를 호출하고 위치 점수를 반환합니다.

여러 기능을 통해 플레이어의 결정 또는 게임의 임의 이벤트를 기반으로 쉽게 분기 할 수있었습니다. 매우 큰 데이터 구조를 전달하고 있었지만 게임 구조 방식으로 인해 검색에서 "실행 취소"를 수행하는 것이 매우 어려웠 기 때문에 가능할 때마다 참조 별 전달을 사용했습니다. 원래 데이터를 변경하지 않으려면 일부 함수에서 값별 전달을 사용합니다. 이 때문에 재귀 적 접근 방식 대신 루프 기반 접근 방식으로 전환하는 것이 너무 어려웠습니다.

이러한 종류의 프로그램에 대한 매우 기본적인 개요는 https://secure.wikimedia.org/wikipedia/ko/wiki/Minimax#Pseudocode를 참조 하십시오.


3

비즈니스에서 실제로 좋은 실례는 "Bill of Materials"입니다. 이것은 완제품을 구성하는 모든 구성 요소를 나타내는 데이터입니다. 예를 들어 자전거를 사용합시다. 자전거에는 핸들 바, 바퀴, 프레임 등이 있으며 각 구성 요소에는 하위 구성 요소가있을 수 있습니다. 예를 들어 휠에는 스포크, 밸브 스템 등이있을 수 있습니다. 따라서 일반적으로 트리 구조로 표시됩니다.

이제 BOM에 대한 집계 정보를 쿼리하거나 BOM의 요소를 변경하기 위해 종종 재귀에 의존합니다.

    class BomPart
    {
        public string PartNumber { get; set; }
        public string Desription { get; set; }
        public int Quantity { get; set; }
        public bool Plastic { get; set; }
        public List<BomPart> Components = new List<BomPart>();
    }

그리고 샘플 재귀 호출 ...

    static int ComponentCount(BomPart part)
    {
        int subCount = 0;
        foreach(BomPart p in part.Components)
            subCount += ComponentCount(p);
        return part.Quantity * Math.Max(1,subCount);

    }

분명히 BomPart 클래스에는 더 많은 필드가 있습니다. 당신은 당신이 가지고있는 플라스틱 부품의 수, 완전한 부품을 만들기 위해 얼마나 많은 노동력이 필요한지 등을 알아 내야 할 수도 있습니다.이 모든 것이 나무 구조에서 재귀의 유용성으로 돌아옵니다.


BOM은 나무가 아닌 방향성 입자 일 수 있습니다. 예를 들어 동일한 사양의 나사를 하나 이상의 구성 요소에서 사용할 수 있습니다.
Ian

-1

가족 관계는 모두가 직관적으로 이해하기 때문에 좋은 모범을 보입니다.

ancestor(joe, me) = (joe == me) 
                    OR ancestor(joe, me.father) 
                    OR ancestor(joe, me.mother);

이 코드는 어떤 언어로 작성됩니까?
törzsmókus

@ törzsmókus 특정 언어가 없습니다. 문법은 C,의 Obj-C, C ++, 자바, 그리고 많은 다른 언어로 아주 가까이,하지만 당신이 진짜 코드를 원하는 경우와 같은 적절한 연산자를 대체해야 할 수도 있습니다 ||에 대한 OR.
Caleb

-1

다소 쓸모는 없지만 재귀 내부 작업을 잘 보여주는 것은 재귀 적입니다 strlen().

size_t strlen( const char* str )
{
    if( *str == 0 ) {
       return 0;
    }
    return 1 + strlen( str + 1 );
}

수학 없음-매우 간단한 기능. 물론 실제로는 재귀 적으로 구현하지 않지만 재귀에 대한 좋은 데모입니다.


-2

학생들이 관련시킬 수있는 또 다른 실제 재귀 문제는 웹 사이트에서 정보를 가져와 해당 사이트 내의 모든 링크 (및 해당 링크의 모든 링크 등)를 따르는 자체 웹 크롤러를 빌드하는 것입니다.


2
일반적으로 전통적인 의미의 재귀와는 대조적으로 프로세스 큐에서 더 잘 제공됩니다.
푹신한

-2

재귀 프로그램을 사용하여 기사 패턴 (체스 보드)의 문제를 해결했습니다. 기사는 몇 개의 표시된 사각형을 제외한 모든 사각형에 닿도록 기사를 움직여야했습니다.

당신은 단순히 :

mark your "Current" square
gather a list of free squares that are valid moves
are there no valid moves?
    are all squares marked?
        you win!
for each free square
    recurse!
clear the mark on your current square.
return.    

이와 같은 트리에서 미래의 가능성을 테스트함으로써 많은 종류의 "생각"시나리오를 수행 할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.