재귀 적 접근 방식이 DFS (Depth-First Search) 외에 자연스러운 솔루션 인 실제 문제 는 무엇입니까 ?
( 하노이의 탑 , 피보나치 수 또는 계승 현실 세계 문제 는 고려하지 않습니다 . 그것들은 제 마음에 약간 인위적입니다.)
답변:
여기에는 많은 수학적 예제가 있지만 실제 예제 를 원했기 때문에 약간의 생각으로 이것이 아마도 제가 제공 할 수있는 최선의 방법 일 것입니다.
치명적이지 않고 신속하게 스스로 고치는 특정 연속 감염에 걸린 사람을 찾습니다 (유형 A) 증상을 나타내며 단순히 스프레더 역할을합니다.
이것은 B 형이 A 형의 다수를 감염시킬 때 매우 성가신 혼란을 야기합니다.
당신의 임무는 모든 유형 B를 추적하고 질병의 중추를 막기 위해 면역화하는 것입니다. 불행히도, 당신은 모든 사람에게 전국적인 치료법을 투여 할 수 없습니다.
이 작업을 수행하는 방법은 감염된 사람 (유형 A)이 주어진 경우, 지난주에 모든 연락처를 선택하여 각 연락처를 힙에 표시하는 사회적 발견입니다. 사람이 감염되었는지 테스트 할 때 "후속 조치"대기열에 추가하십시오. 사람이 B 형인 경우 머리의 "후속 조치"에 추가합니다 (이 빠르게 중지하고 싶기 때문에).
지정된 사람을 처리 한 후 대기열에서 사람을 선택하고 필요한 경우 예방 접종을 적용합니다. 이전에 방문하지 않은 모든 연락처를 가져온 다음 감염되었는지 테스트하십시오.
감염된 사람의 대기열이 0이 될 때까지 반복 한 다음 또 다른 발병을 기다립니다.
(좋아, 이것은 약간 반복적이지만 반복적 인 문제를 해결하는 반복적 인 방법입니다.이 경우 문제에 대한 가능성있는 경로를 찾으려고하는 인구 기반의 폭 우선 순회는 물론 반복적 인 솔루션이 종종 더 빠르고 더 효과적입니다. , 그리고 나는 모든 곳에서 재귀를 강제로 제거하여 본능적으로 변합니다. .... 젠장!)
파일 시스템의 디렉토리 구조와 관련된 모든 것은 어떻습니까? 재귀 적으로 파일 찾기, 파일 삭제, 디렉토리 생성 등
다음은 디렉토리 및 하위 디렉토리의 내용을 재귀 적으로 인쇄하는 Java 구현입니다.
import java.io.File;
public class DirectoryContentAnalyserOne implements DirectoryContentAnalyser {
private static StringBuilder indentation = new StringBuilder();
public static void main (String args [] ){
// Here you pass the path to the directory to be scanned
getDirectoryContent("C:\\DirOne\\DirTwo\\AndSoOn");
}
private static void getDirectoryContent(String filePath) {
File currentDirOrFile = new File(filePath);
if ( !currentDirOrFile.exists() ){
return;
}
else if ( currentDirOrFile.isFile() ){
System.out.println(indentation + currentDirOrFile.getName());
return;
}
else{
System.out.println("\n" + indentation + "|_" +currentDirOrFile.getName());
indentation.append(" ");
for ( String currentFileOrDirName : currentDirOrFile.list()){
getPrivateDirectoryContent(currentDirOrFile + "\\" + currentFileOrDirName);
}
if (indentation.length() - 3 > 3 ){
indentation.delete(indentation.length() - 3, indentation.length());
}
}
}
}
Quicksort , merge sort , 대부분의 다른 N-log N 정렬.
Matt Dillard의 예가 좋습니다. 더 일반적으로, 나무를 걷는 것은 일반적으로 재귀에 의해 매우 쉽게 처리 될 수 있습니다. 예를 들어, 구문 분석 트리 컴파일, XML 또는 HTML 위의 걷기 등이 있습니다.
재귀는 문제를 해결하는 데 동일한 알고리즘을 사용할 수있는 하위 문제로 나누어 문제를 해결할 수있을 때마다 적절합니다. 트리와 정렬 된 목록에 대한 알고리즘은 자연스럽게 적합합니다. 계산 기하학 (및 3D 게임)의 많은 문제는 바이너리 공간 분할 (BSP) 트리, 지방 분할 또는 세계를 하위 부분으로 나누는 다른 방법을 사용하여 재귀 적으로 해결할 수 있습니다 .
알고리즘의 정확성을 보장하려는 경우에도 재귀가 적합합니다. 불변 입력을 받고 입력에 대한 재귀 호출과 비 재귀 호출의 조합 인 결과를 반환하는 함수가 주어지면 일반적으로 수학적 유도를 사용하여 함수가 올바른지 (또는 그렇지 않은지) 쉽게 증명할 수 있습니다. 반복 함수 나 변이 할 수있는 입력으로이 작업을 수행하는 것은 종종 다루기 어렵습니다. 이는 재무 계산 및 정확성이 매우 중요한 기타 응용 프로그램을 처리 할 때 유용 할 수 있습니다.
확실히 많은 컴파일러가 재귀를 많이 사용합니다. 컴퓨터 언어는 본질적으로 재귀 적입니다 (즉, 다른 'if'문 등에 'if'문을 포함 할 수 있습니다).
컨테이너 컨트롤의 모든 자식 컨트롤에 대해 읽기 전용을 비활성화 / 설정합니다. 일부 자식 컨트롤이 컨테이너 자체이기 때문에이 작업을 수행해야했습니다.
public static void SetReadOnly(Control ctrl, bool readOnly)
{
//set the control read only
SetControlReadOnly(ctrl, readOnly);
if (ctrl.Controls != null && ctrl.Controls.Count > 0)
{
//recursively loop through all child controls
foreach (Control c in ctrl.Controls)
SetReadOnly(c, readOnly);
}
}
(출처 : mit.edu )
eval의 정의는 다음과 같습니다.
(define (eval exp env)
(cond ((self-evaluating? exp) exp)
((variable? exp) (lookup-variable-value exp env))
((quoted? exp) (text-of-quotation exp))
((assignment? exp) (eval-assignment exp env))
((definition? exp) (eval-definition exp env))
((if? exp) (eval-if exp env))
((lambda? exp)
(make-procedure (lambda-parameters exp)
(lambda-body exp)
env))
((begin? exp)
(eval-sequence (begin-actions exp) env))
((cond? exp) (eval (cond->if exp) env))
((application? exp)
(apply (eval (operator exp) env)
(list-of-values (operands exp) env)))
(else
(error "Unknown expression type - EVAL" exp))))
적용의 정의는 다음과 같습니다.
(define (apply procedure arguments)
(cond ((primitive-procedure? procedure)
(apply-primitive-procedure procedure arguments))
((compound-procedure? procedure)
(eval-sequence
(procedure-body procedure)
(extend-environment
(procedure-parameters procedure)
arguments
(procedure-environment procedure))))
(else
(error
"Unknown procedure type - APPLY" procedure))))
다음은 eval-sequence의 정의입니다.
(define (eval-sequence exps env)
(cond ((last-exp? exps) (eval (first-exp exps) env))
(else (eval (first-exp exps) env)
(eval-sequence (rest-exps exps) env))))
eval
-> apply
-> eval-sequence
->eval
재귀는 문제 (상황)에 적용되어 더 작은 부분으로 분해 (축소) 할 수 있으며 각 부분은 원래 문제와 비슷해 보입니다.
자신과 유사한 작은 부분을 포함하는 항목의 좋은 예는 다음과 같습니다.
재귀는 그 조각 중 하나가 케이크 조각이 될만큼 충분히 작아 질 때까지 문제를 더 작고 작은 조각으로 계속 나누는 기술입니다. 물론, 그것들을 분리 한 후에는 원래 문제의 전체적인 해결책을 형성하기 위해 올바른 순서로 결과를 다시 결합해야합니다.
일부 재귀 정렬 알고리즘, 트리 워킹 알고리즘, 맵 / 리 듀스 알고리즘, 분할 및 정복이 모두이 기술의 예입니다.
컴퓨터 프로그래밍에서 대부분의 스택 기반 호출 반환 유형 언어에는 이미 재귀 기능이 내장되어 있습니다.
상태 머신을 시뮬레이션하기 위해 몇 군데에서 순수 꼬리 재귀 를 사용하는 시스템이 있습니다.
재귀의 몇 가지 훌륭한 예는 함수형 프로그래밍 언어 에서 찾을 수 있습니다. 함수형 프로그래밍 언어 ( Erlang , Haskell , ML / OCaml / F # 등)에서는 모든 목록 처리에서 재귀를 사용하는 것이 매우 일반적입니다.
일반적인 명령형 OOP 스타일 언어의 목록을 다룰 때 연결 목록으로 구현 된 목록을 보는 것은 매우 일반적입니다 ([item1-> item2-> item3-> item4]). 그러나 일부 함수형 프로그래밍 언어에서는 목록 자체가 재귀 적으로 구현됩니다. 여기서 목록의 "머리"는 목록의 첫 번째 항목을 가리키고 "꼬리"는 나머지 항목을 포함하는 목록을 가리 킵니다 ( [항목 1-> [항목 2-> [항목 3-> [항목 4-> []]]]]). 제 생각에는 꽤 창의적입니다.
이러한 목록 처리는 패턴 일치와 결합 될 때 매우 강력합니다. 숫자 목록을 합산하고 싶다고 가정 해 보겠습니다.
let rec Sum numbers =
match numbers with
| [] -> 0
| head::tail -> head + Sum tail
이것은 본질적으로 "빈 목록으로 호출 된 경우 0을 반환"(재귀를 중단 할 수 있음)하고, 그렇지 않으면 head 값 + 나머지 항목과 함께 호출 된 Sum 값 (따라서 재귀)을 반환합니다.
예를 들어 URL 목록이있을 수 있고 각 URL이 연결되는 모든 URL을 분리 한 다음 모든 URL과의 총 링크 수를 줄여 페이지에 대한 '값'을 생성합니다 (Google PageRank 와 함께 가져 오고 원본 MapReduce 문서에 정의 된 것을 찾을 수 있습니다 .) 이렇게하면 문서에서 단어 수를 생성 할 수도 있습니다. 그리고 아주 많은 것들이 있습니다.
이 기능 패턴을 모든 유형의 MapReduce 코드로 확장 할 수 있습니다. 여기서 목록을 가져 와서 변환하고 다른 것을 반환 할 수 있습니다 (다른 목록 또는 목록의 일부 zip 명령).
XML 또는 트리 인 모든 것을 순회합니다. 솔직히 말해서 나는 일에서 재귀를 거의 사용하지 않습니다.
계층 적 조직의 피드백 루프.
최고 상사는 최고 경영진에게 회사의 모든 사람으로부터 피드백을 수집하라고 말합니다.
각 임원은 부하 직원을 수집하고 부하 직원으로부터 피드백을 수집하도록 지시합니다.
그리고 아래로.
직접보고가없는 사람 (트리의 리프 노드)은 피드백을 제공합니다.
피드백은 각 관리자가 자신의 피드백을 추가하는 트리 위로 이동합니다.
결국 모든 피드백은 최고 보스에게 다시 돌아갑니다.
재귀 적 방법은 각 수준에서 필터링을 허용하기 때문에 자연스러운 해결책입니다. 최고 보스 수있는 글로벌 이메일을 보내 직접 다시 그 / 그녀에게 각 직원의 보고서 의견이 있지만, 거기에 재귀 가장 여기서 일하는 때문에, 그리고 "당신은 해고 야"문제 "당신이 진실을 처리 할 수 없습니다."
웹 사이트에 대한 CMS를 구축하고 있다고 가정합니다. 여기서 페이지는 트리 구조로되어 있으며 루트는 홈페이지입니다.
또한 당신의 {user | client | customer | boss}가 당신이 트리에서 당신이 어디에 있는지 보여주기 위해 모든 페이지에 이동 경로를 배치하도록 요청했다고 가정 해보자.
주어진 페이지 n에 대해, 페이지 트리의 루트로 백업되는 노드 목록을 재귀 적으로 작성하기 위해 n의 부모와 그 부모 등으로 걸어 갈 수 있습니다.
물론이 예제에서는 페이지 당 여러 번 db를 치고 있으므로 페이지 테이블을 a로, 페이지 테이블을 다시 b로 조회하고 a.id를 조인하는 SQL 별칭을 사용할 수 있습니다. b. parent 그래서 데이터베이스가 재귀 조인을 수행하도록합니다. 오랜만에 구문이 도움이되지 않을 것입니다.
그런 다음 다시 한 번만 계산하여 페이지 레코드와 함께 저장하고 페이지를 이동하는 경우에만 업데이트 할 수 있습니다. 아마도 더 효율적일 것입니다.
어쨌든 내 $ .02
Windows Forms 또는 WebForms (.NET Windows Forms / ASP.NET ) 에서 컨트롤 트리를 구문 분석합니다 .
내가 아는 가장 좋은 예는 quicksort 이며 재귀를 사용하면 훨씬 간단합니다. 보세요:
shop.oreilly.com/product/9780596510046.do
www.amazon.com/Beautiful-Code-Leading-Programmers-Practice/dp/0596510047
(3 장 : "내가 작성한 가장 아름다운 코드"아래의 첫 번째 부제목을 클릭하십시오.)