첫째, 개방 문제
위원회에 추천 등록 다시 나는 당신이 당신에게 익숙하지한다고 생각이 함수? 이제 응용 프로그램의 많은이 기능이 있습니다. 이 기능은, 사용자 A는 사용자 B, 사용자 B를 등록 할 것을 권장하고, 사용자 C는 등록 할 것을 권장한다. 우리는 사용자 C의은 사용자 A,은 "권장 최종"고 말할 수있는
사용자 A에 대한 또한 사용자 B "최종 추천"와, 사용자 A가 전혀 없다 "최종 심판."
일반적으로, 우리는 데이터베이스를 통해이 관계를 기록하는 것이 좋습니다. 데이터베이스 테이블에서, 우리는 권장 ID를 나타냅니다 referrer_id, actor_id 사용자 ID를 나타내는 두 개의 ⾏ 데이터를 기록 할 수 있습니다.
이러한 배경을 바탕으로, 내 질문은, 사용자 ID 부여 방법 찾기 위해 사용자의 "최종 추천"? 이 문제로, 우리는 내 오늘 배울 내용, 재귀 (재귀)
둘째, 어떻게 재귀를 이해하기
1, 영화 케이스
주말에, 당신은, 우리가 지금 아 처음 몇 행에 앉아 그의 여자 친구, 여자 친구 물어와 함께 영화를 보러? 너무 어두운 영화가 아닌 수를 확인하기 위해 내부, 어떻게 당신이 지금해야합니까?
처음 몇 행에 문제에 대한 재귀 솔루션을 사용하여 2,
당신이 프로그래머 잊지 마세요, 이것은 재귀 편리 뗏목을 시작, 당신을 이길 수 없습니다 :
- 당신은 그들이 무엇에 왔다는 것을 알고 그만큼 당신이 그에게 번호를 추가하고 싶은대로하는 사람의 처음 몇 행의 맨 앞줄을 물어 그래서.
- 그러나, 나는 아, 볼 수없는 사람들의 앞에, 그래서 그는 사람들 앞에서 그에게 물었다. 행과 행을 물어 전달 그래서, 사람들의 첫 번째 행까지 물어, 나는 첫 번째 행에서 말
- 이러한 숫자는 다시 통과 한 후 행에 의해 행. 그는 몇 행 어디 당신 앞에있는 사람이 당신에게 때까지, 당신은 답을 알고 있습니다.
이 문제를 해결 꽤 표준 재귀 분해 과정이다 :
"배달"이라는 프로세스에 다시 프로세스는 "정상화"라고합니다 . 기본적으로 모든 재귀 재귀 식을 나타내는 데 사용될 수있다. 그냥이 세상의 예는, 우리는 다음과 같이 표현된다 재귀 공식을 사용 :
F (N) = F (N-1) +1이고, F (1) = 1
세 가지 조건을 충족 셋째, 재귀 필요
이 단지 재귀의 아주 전형적인 예입니다, 당신은 재귀를 사용할 수있는 어떤 종류의 문제를 해결하기 위해? 나는만큼 당신이 해결하기 위해 재귀를 사용하여, 다음과 같은 세 가지 조건을 충족, 세 가지 조건을 요약.
문제 1. 솔루션은 문제의 여러 하위 솔루션으로 분해 될 수있다
문제의 아이는 무엇입니까?
하위 문제는 작은 문제의 데이터 크기입니다. 예를 들어, 영화의 앞의 예로 말하면,
당신은 "자신의 행에있는"문제는 이러한 문제 아이 "행의 앞줄에 사람이"로 분해 될 수있다, 알고 싶어요.
정확히 같은 사고를 해결하는 데이터의 다른 크기뿐만 아니라 분해 후 하위 문제 2. 문제,
예를 들어, 영화관 그 예를 들어, 당신은 "자신의 행이있는"아이디어가 완전히 동일 해결하기 위해 생각하고, 사람들이 줄 앞에 "하는 자신의 행을"해결했다.
재귀 종료 조건 3. 존재
서브 - 서브 문제로 분해 한 후 소 문제 핸들 질문에 문제는 층 분해에 의해 층이 계속 종료 조건을 필요 무한한 루프가 아니다.
또는 영화의 예는, 첫 번째 행이 사람을 물어 계속할 필요가 없습니다, 그들은 종료 조건입니다 행, F (1) = 1, 재귀가 뭔지 알아.
넷째, 어떻게 재귀 코드를 작성하는
1, 계단의 사건을 맡아
가 N 단계의 수는, 때마다 당신은 단계 또는 두 단계,이 n 개의 단계 얼마나 많은 방법을 물어 가서 교차하는 경우? 일곱 단계가있는 경우 수행 할 수 있습니다
이처럼 2,2,2,1, 당신은 또한이 같은 1,2,1,1,2 최대, 짧은에, 법이 많이있다, 그것을 사용하는 방법 프로그래밍 얼마나 많은 종류의 총을 얻을 수 있습니다 법을?
1, 작은 문제가 큰 문제의 법칙
우리는 방법의 첫 번째 단계에 따라 신중하게 낮은 사실, 모든 방법은 두 가지 범주로 나눌 수 있습니다 싶어
- 첫 번째 범주는 한 단계를 갈 수있는 첫 번째 단계입니다,
- 다른 하나는 두 단계를 갈 수있는 첫 번째 단계입니다.
그래서 방법 단계 이후에, N-2 및 거리 스텝 번호 N-1 차 이동 동일하고, n-1 개의 2 차 이동과 함께 번째 단계로 이동한다. 이 배합
F (N) = F (N-1) + F (N-2)
2 기록 재귀 수식
우리는 표현을 걸어, 종료 조건으로 두 단계를 F (2) = 2를 넣을 수 산보 마무리 개의 이동하고, 한 단계 또는 두 단계가있다.
3, 고의적 종료 조건,
따라서, 재귀 종료 조건은 F (1) = 1, F이다 (2) = 2. 이 시간, 당신은 탈환 N = 3, N = 4 종료 조건이 적절하고 정확한지 확인 할 수 있습니다.
우리는 재귀 그냥 함께 넣어 얻을 재귀 공식 종료 조건이 있습니다 :
F (1) = 1; F (2) = 2; F (N) = F (N-1) + F (N-2)
4, 재귀 수식과 코드 종료 조건으로 번역했다.
이 공식, 우리는 재귀 코드로 변환이 훨씬 간단합니다. 다음과 같이 최종 재귀 코드는 다음과 같습니다
INT의 F (INT 않음) { 경우 (N == 1) 창 1; 경우 (N == 2) 창 2; 리턴 F (N-1) + F (N-2); }
나는 요약 :
- 재귀 코드를 작성하는 것이 핵심이 쓰기 재귀 공식에 따라 법의 작은 문제가 큰 문제를 분해하고 방법을 찾는 것입니다,
- 그리고 조사의 종료 조건
- 마지막으로, 번역 종료 및 조건 코드로 공식 재귀
2, 생각의 오류는 재귀
난 그냥 말할 첫 번째 예와 같이, 인간의 뇌는 단계별로 전체 "배달"와 "정상화"공정 단계를 넣어 거의 방법입니다, 우리는 취소합니다.
1, 컴퓨터는 그래서 재귀 긍정적 인 식욕의 반복적 인 일을 잘합니다.
(2)는, 인간의 두뇌와 우리는 사고의 간단한 방법을 선호합니다. 우리가 재귀를 볼 때, 우리는 항상 재귀, 타일 확장하려는
3, 그의 마음의 뜻을주기는 한 단계 아래로 이동 한 다음 한 단계, 반환
컴퓨터의 모든 단계에서 그림을 시도 4, 이 주변에 이동하기 쉽고, 그래서 어떻게 수행 할 수 있습니다.
재귀 코드의 경우 즉, 전체 납품 관행과 실제로 생각하는 오류를 입력 정상화 과정을 취소했습니다. 많은 경우에, 우리는 그들이이 질환의 자신의 이해를 만들기 위해 자신의 주로하기 때문에, 더 어려운 이해해야한다.
3, 재귀 적 사고의 올바른 이해
즉, 무엇을 생각하는 올바른 방법이 될 것인가?
문제가 몇 가지 단어 문제 B, C, D,로 분해 될 수있는 경우에 1,
2, 당신이 그 하위 문제 B를 가정 할 수있다, C, D가 해결되었습니다, A.에 근거하여 문제를 해결하는 방법에 대한 생각 또한, 당신은 단지 문제 및 하위 메뉴 문제 B 사이의 관계에 대해 생각해야, C, D이,
3 층 및 하위 하위 하위 문제 문제 아래로 생각하지 않아도, 서브 서브 서브 서브 서브 - 문제 문제의 관계. 재귀 세부 사항은 다음과 같이 이해하기 훨씬 쉽게 마스크.
따라서, 재귀 코드를 작성하는 열쇠는 긴 만남의 재귀로, 우리가 말했듯이, 재귀 공식에 추상화 레이어 사이의 관계를 호출하지 않고, 재귀 뇌를 사용의 각 단계를 분해하려고하지 않는다는 것입니다.
V. 재귀 코드는 경계 스택 오버플로 될 수 있습니다
1 스택 오버플 위험
스택 오버 플로우가됩니다 전신 붕괴의 원인 , 결과는 매우 심각 할 것
2. 왜 재귀 코드는 오버 플로우 그것을 스택을 일으킬?
- 함수 호출 스택은 임시 변수를 저장하는 데 사용됩니다.
- 함수에 대한 각 호출은 스택 메모리로 가압되는 임시 변수 스택 프레임에 캡슐화 될
- 기능, 실행 반환 및 기타 스택 전에 완료된다.
시스템 스택 또는 가상 머신 스택 공간은 일반적으로 크지 않다. 대규모 데이터의 재귀 용액 깊은 레벨을 호출하는 경우, 스택으로 푸시 된 스택 오버 플로우의 위험이
우리가 시스템 또는 1킬로바이트에 JVM 스택 크기를 적층하는 경우와 같은 앞에서 언급 한 영화의 예로는, 다음 스택 해결 F (19999)에가 주어질 것이다 :
스레드에서 예외 "주요"java.lang.StackOverflowError의
3, 어떻게 오버 플로우 그것은 우리가 스택을 방지합니까?
우리는이 문제를 해결하는 코드의 최대 깊이 방식으로 재귀 호출을 제한 할 수 있습니다.
- 재귀 호출이 특정 깊이를 (예 : 1000)를 초과 한 후, 우리는 반복적으로 아래로 계속, 직접 오류를 반환 할 수 없습니다.
- 영화 예를한다는 또는, 우리는이 같은 다음으로 변환 할 수있다, 당신은 스택 오버 플로우를 방지 할 수 있습니다.
- 그러나, 나는 코드를 쓴 의사 코드, 간결 코드입니다, 일부 경계 조건은 같은 X <= 0로 간주되지 않습니다.
// 전역 변수는 재귀의 깊이를 나타냅니다. INT 깊이 = 0; INT의 F (INT 않음) { ++ 깊이; (깊이> 1,000) 투사 예외 경우; 경우 (N == 1) 창 1; 리턴 F (N-1) + 1; }
최대 현재 스레드 스택 공간 나머지에 대해 재귀 깊이를 허용하기 때문에,이 방법으로 문제가 해결되지 않습니다,
그것은 사전에 계산 될 수 없다. 실시간 계산, 코드가 너무 복잡하면 코드의 가독성에 영향을 미칠 것입니다.
최대 깊이 비교 효율 등 10, 50, 당신은이 방법을 사용할 수 있습니다 경우에 따라서, 그렇지 않으면이 방법은 매우 실용적되지 않습니다.
여섯째는, 재귀 코드는 이중 계산주의해야합니다
1 나타내는 이중 계산 사례
재귀를 사용하는 경우 또한, 이중 계산이 될 것입니다. 우리는 다음과 같이 아래로 전체 순환 과정 나누기를 넣으면 그냥, 이야기 한 재귀 코드의 첫 번째 예는 다음이 있습니다 :
도면에서 우리는 시각적 계산 F (4), F (3) 계산 된 F (3) 및 (4) 필요 F 계산 따라서, F (에 필요한, F (5)를 산출 할 볼 수 3) 여러 번 계산되었다 이 이중 계산의 문제이다.
2, 어떻게 이중 계산을 피하기 위해
이중 카운팅을 방지하기 위해 (예를 들면, 해시 테이블로서) 데이터 구조에 의해 해결했다 이미 F (k)를 저장할 수있다.
경우 이전에 이미 해결 여부에 보이는 다음의 재귀 호출하면 F (K). 그래서, 프로세스가 직접 해시 테이블 값에서 반환하는 경우, 필요, 반복 계산하지
그래서처럼 문제에 대해 이야기 방지 할 수 있습니다.
위의 아이디어에 따라, 우리는 단지 코드를 변환 보면 :
공개 INT의 F (INT N) { 경우 (N == 1) 창 1; 경우 (N == 2) 창 2; // hasSolvedList가지도하는 것으로 이해 될 수 있고, 키는 값 f를, n은 (N) 경우 (hasSolvedList.containsKey (N)) { 창 hasSolvedList.get (N); } INT RET = F (N-1) + F (N-2); hasSolvedList.put (N, RET); 권리를 반환; }
스택 오버 플로우,이 두 가지 일반적인 이중 계산 문제뿐만 아니라. 다른 많은 문제가 재귀 코드 있음.
- 시간의 측면에서 효율성이 많이 더 코드 재귀 함수 호출, 때이 함수 호출 많은 수의, 그것은 상당한 시간 비용에 축적됩니다.
- 공간의 복잡성에서, 재귀 호출은 메모리 스택에 시간 필드 데이터를 절약 할 수 있기 때문에,
- 따라서 재귀 코드 공간의 복잡성을 분석 할 때, 우리가 이전에 영화 재귀 코드에 대해 이야기 등이 섹션의 추가 오버 헤드에 대한 필요성은 공간 복잡도 O (1)되지 고려하지만, O (N).
세븐, 어떻게 코드가 편리 재귀 재귀 코드에서 온다 재 작성?
1, 장점과 재귀 코드의 단점
1 장점
- 표현 재귀 코드를 쓰기에 매우 간단하고 매우 강하다
2, 단점
- 우주의 복잡성이 높다
- 위험 스택 오버 플로우
- 중복 계산
- 너무 많은 함수 호출 및 기타 문제는 더 많은 시간이 소요됩니다
2, 어떻게 비 재귀 재귀 코드에 대한 코드를 재 작성?
그건에게 우리는 비 재귀 재귀 코드의 코드를 다시 작성할 수 있다면? 영화관 단지 예로서, 우리는 오직 (X) = F (X-1) +1이 재귀 식 F를 제외하고 장면을 넣어. 우리는이 재 작성보고 :
INT의 F (INT 않음) { INT RET = 1; 위한 (INT 나 2 =; I <= N; ++ i)는 { 오른쪽 = K + 1; } 권리를 반환; }
마찬가지로, 제 2 예는 비 재귀 적 구현을 변경할 수있다.
INT의 F (INT 않음) { 경우 (N == 1) 창 1; 경우 (N == 2) 창 2; INT RET = 0; INT 사전 = 2; INT 방지 = 1; 대해 INT (I = 3, I <= N; ++ i)는 { RET = + prepro하는 단계; 사전 예방 =; RET; } 권리를 반환; }
3, 모든 재귀 코드는 비 재귀 쓰기 그것의 반복주기를 변경할 수 있습니다되지 않는 이유는 무엇입니까?
- 일반적으로, 예. 원조 자체가 달성하기 위해 재귀 스택이지만, 우리는 시스템 스택 또는 가상 머신 자체를 사용하고 있기 때문에, 우리가하지 인식 아무것도하지 않고 제공.
- 우리는 어떤 재귀 코드가 같이 재 작성 될 수 있도록 자신의 구현 스택 메모리 힙, 수동 시뮬레이션 스택, 스택 과정은 재귀 코드가없는 경우
- 그러나이 아이디어는 실제로 재귀 "수동"재귀로 변경, 자연은 변경되지 않은 구현의 복잡성을 초청, 앞서 언급 한 몇 가지 문제를 해결하지 않습니다도 있지만,
여덟, 대답은 시작된다
어떻게 "궁극적 권장"을 찾을 : 지금까지 재귀에 관한 기본 지식은 이미 문제에서 살펴 보자이 시작 완료? 내 솔루션이 있습니다 :
긴 findRootReferrerId (긴 actorId) { 롱 referrerId는 [표]의 선택을 referrer_id = 여기서 actor_id = actorId; (referrerId == NULL) 창 actorId 경우; 창 findRootReferrerId (referrerId); }
그것은 매우 간단하지 않다? 3 줄의 코드로 얻을 수 있지만 실제 프로젝트에서 위의 코드는 작동하지 않는 이유는 무엇입니까? 그리고 두 가지 문제가있다.
- 먼저, 깊은 재귀 경우, 스택 오버 플로우 문제가있을 수 있습니다.
- 더티 데이터 ⾥ 데이터베이스가있는 경우 둘째, 우리는 또한 무한 재귀 발생하는 문제를 처리해야합니다. 예를 들어 데모 데이터베이스 환경의 경우, 테스트 엔지니어는 인위적으로 일부 데이터를 삽입, 테스트를 용이하게하기 위해, 더티 데이터가있을 것입니다. A가 B 인 경우에는 추천 추천 B는 C를, C는 상기 사용자 A를 추천이다 무한 루프 발생되도록 .
- 나는 이미 그들에게 대답 한 첫 번째 질문은 해결하기 위해 재귀의 깊이를 제한 할 수 있습니다.
- 두 번째 문제는 재귀 수준 제한으로 해결 될 수있다.
- 그러나, 더 진보 된 방법은 자동으로 ABCA 존재의 "링"을 감지하는 것입니다있다.
어떻게 반지 그것의 존재를 감지? 나는이 설명하지 않습니다, 당신은 우리가 다음 장에 대해 이야기합니다, 다음에 대해 생각할 수 있습니다.
IX 요약
모두 완료 경우에도 여기에 재귀의 지식. 저를 정리해 보자.
- 재귀는 코딩 기술은 매우 효율적이고 간단하다. "세 가지 조건"의 문제가 만족되는 한 그것은 재귀 코드에 의해 해결 될 수있다.
- 그러나 재귀 코드는 이해하기 어려운, 쓰기 어렵습니다. 주위에 자신을 이동하지 재귀 코드를 작성하는 것이 중요합니다, 올바른 자세는 종료의 조건을 찾기 위해 재귀 공식을 작성하는 것입니다, 다음 재귀 코드로 변환.
- 비록 재귀 코드는 간단하고 효율적이지만 많은 단점 재귀 코드가있다. 재귀 코드를 작성할 때 예를 들어, 스택 오버 플로우, 이중 계산, 시간이 많이 걸리는 함수 호출, 공간 복잡도 초안 등 그래서, 우리는 이러한 부작용을 제어해야합니다.
열, 방과 후 사고
우리는 일반적으로이 디버그 모드를 사용하는 것은 거의 불가능하다 재귀 재귀 코드의 비교적 큰, 깊은 수준과 같은 단일 단계의 추적 기능처럼 IDE의 디버그 코드를 사용합니다. 재귀 코드의 경우, 당신은 그렇게 할 수있는 좋은 방법을 디버깅 할 수 있나요?
- 인쇄 로그 재귀 값을 발견했다.
- 조건부 중단 점 디버깅과 결합.
- 기본값은 998 층 파이썬 재귀입니다