>웹 프론트엔드 >JS 튜토리얼 >JavaScript 재귀의 재귀 및 루프 예

JavaScript 재귀의 재귀 및 루프 예

高洛峰
高洛峰원래의
2017-02-08 15:34:031006검색

반복적인 계산이 필요한 다양한 유형의 문제에 대해 루프 및 재귀 방법은 고유한 장점을 가지며 보다 직관적이고 간단한 솔루션을 제공할 수 있습니다. 관심 있는 분들을 위해 다음은 JavaScript 재귀 및 루프에 대한 자세한 소개입니다.

재귀와 루프 알아보기

반복적인 계산이 필요한 다양한 유형의 문제에 대해 루프와 재귀 방법은 나름의 장점이 있으며 보다 직관적인 Simple 솔루션을 제공할 수 있습니다. 반면, 루프 방식과 재귀 방식은 서로 변환될 수 있습니다. 동일한 기능을 달성하기 위해 재귀를 사용하여 코드 루프를 다시 작성할 수 있으며 그 반대의 경우도 마찬가지입니다. 일반성을 잃지 않으면서 루프와 재귀는 다음 의사 코드를 사용하여 요약할 수 있습니다.

의사 코드 형식 설명: 루프는 while 형식을 채택하며 변수는 정의되지 않습니다. 할당은 조건식과 실행된 명령문을 함수 형식으로 작성하고 해당 값을 괄호 안에 작성합니다. 다른 구문 측면에서는 가능한 한 Javascript 사양에 가깝게 하십시오.

코드는 다음과 같습니다.

//pseudo code of a loop 
//while形式 
function loop(arguments){ 
//结果的初始值 
result:=initial_value; 
while(condition(variable, arguments)){//循环条件,可能只需arguments,也可能为了方便引入循环变量 
//计算结果。参数包括之前的结果、当前循环变量和外部变量 
result:=calculate(result, variable, extern_variables); 
//影响函数的外部环境,即修改外部变量 
changeStatus(result, variable, extern_variables); 
//执行完循环体中的语句后,修改参数或循环变量。 
modify_arguments_variable(arguments, variable); 
} 
//返回结果 
return result; 
}


마찬가지로 재귀 함수의 의사 코드를 제공합니다.

코드는 다음과 같습니다.

//pseudo code of a recursion 
function recursion(arguments){ 
//以下代码为控制函数重复调用的结构部分。 
//获得再次调用此函数的新的参数,可能为多组arguments值。 
//对应于循环中的condition(variable, arguments)和modify_arguments_variable(arguments, variable)。 
new_arguments:=conditional_get_next(arguments); 
//对新参数的每一组,调用函数自身。 
results:=recursion(new_arguments); 
//以下的代码为每次调用都运行的功能部分 
//计算结果。涉及到之前的结果、当前循环变量和外部变量。 
//对应于循环中的result:=calculate(result, variable, extern_variables)。 
result:=calculate(arguments, extern_variables); 
result:=combine(result, results); 
//影响函数的外部环境,即修改外部变量 
changeStatus(result, arguments, extern_variables); 
return result; 
}

두 코드를 비교해 보면 루프와 재귀의 구성이 유사하다는 것을 알 수 있습니다. 순서를 바꾸고 적절하게 변환하면 됩니다. 루프는 재귀 방법을 사용하여 달성할 수 있습니다. 이 변환은 프로그램이 단순할 때 쉽게 볼 수 있습니다. 예를 들어 다음과 같은 단순 누적 합계 함수:

코드는 다음과 같습니다.

//loop 
function sum(num){ 
var result=1; 
while (num>1){ 
result+=num; 
num--; 
} 
return result; 
}


해당 재귀 형식:
코드는 다음과 같습니다.

//recursion 
function sum2(num){ 
if (num>1){ 
return num+sum(num-1); 
}else{ 
return 1; 
} 
}

반대로 대부분의 재귀 프로그램은 루프를 통해 직접 구현할 수도 있습니다. 다음은 최대 공약수를 구하는 루프 형태의 함수입니다.

코드는 다음과 같습니다.

function gcd2(a, b){ 
var temp; 
if (a<b){ 
temp=a; 
a=b; 
b=temp; 
} 
var c=a%b; 
while (c!==0){ 
a=b; 
b=c; 
c=a%b; 
} 
return b; 
}

하지만 재귀에서 루프로의 변환이 항상 쉽지는 않습니다. 이 함수를 다시 호출하기 위해 새 인수를 생성하는 재귀 의사 코드의 부분

new_arguments:=conditional_get_next(arguments)

은 루프의 해당 부분보다 더 유연합니다. 재귀는 새로 생성된 매개변수 그룹의 수에 따라 두 가지 범주로 나눌 수 있습니다(함수에 필요한 모든 매개변수는 하나의 그룹입니다). 첫 번째 유형은 매개변수 그룹의 수가 고정되어 있고 재귀가 피보나치 수열과 최대 공약수 예시와 같은 루프로 변환될 수 있는 경우입니다. 두 번째 유형은 매개변수 그룹의 수가 불확실한 경우입니다. 그래프나 트리를 탐색할 때 각 점에는 임의 개수의 인접 점이 있습니다. 이 재귀는 루프로 직접 변환될 수 없습니다.

루프는 1차원 반복만 할 수 있는 반면 재귀는 2차원 구조를 순회할 수 있기 때문입니다. 예를 들어, 트리에서 노드에는 동일한 수준에 하위 노드와 노드가 모두 있습니다. 간단한 1차원 루프는 양방향으로 이동할 수 없습니다.

그러나 일부 데이터 구조의 도움으로 노드 위치에 대한 일부 정보를 기억하면 두 번째 유형의 재귀를 루프로 구현할 수도 있습니다.

위의 관찰에서 도출된 결론을 연습하기 위해 또 다른 예를 들어보겠습니다. HTML5는 주어진 클래스 값을 가진 모든 요소를 ​​반환하는 Document 및 Element에 대한 새로운 메소드 getElementsByClassName(names)를 정의합니다. Firefox 3을 포함한 일부 브라우저는 이미 이 방법을 지원합니다. 아래에서는 먼저 재귀적 방법을 사용하여 더 약한 버전을 제공한 다음 루프 방법을 사용하여 다시 작성합니다.

코드는 다음과 같습니다.

var getElementsByClass={}; 
//elem为一个HTMLElement 
//name为单个class名 
//返回包含elem下所有class属性包含给定名称的element的数组 
getElementsByClass.recursion1=function (elem, name){ 
var list=[]; 
function getElements(el){ 
if (el.className.split(&#39; &#39;).indexOf(name)>-1){ 
list.push(el); 
} 
for (var i=0, c=el.children; i<c.length; i++){ 
getElements(c[i]); 
} 
} 
getElements(elem); 
return list; 
}

위에서 언급한 것처럼 루프에서 노드의 위치 정보를 기억하기 위해서는 다음을 구현할 수 있는 데이터 구조가 필요합니다. 방법.

push(object) //객체를 작성합니다.

objectpop() //가장 최근에 작성된 객체를 읽고 데이터 구조에서 삭제합니다.

objectget() //데이터 구조의 내용을 변경하지 않고 가장 최근에 작성된 객체를 읽습니다.

스택은 바로 이러한 후입선출(Last In First Out) 데이터 구조입니다. Javascript의 Array 객체는 처음 두 메서드를 지원하며 여기에 세 번째 메서드를 추가할 수 있습니다.

루프 버전:

코드는 다음과 같습니다.

getElementsByClass.loop1 = function(elem, name){ 
//use a js array as the basis of a needed stack 
var stack = []; 
stack.get = function(){ 
return stack[stack.length - 1]; 
} 
var list = []; 
//the business logic part. put the eligible element to the list. 
function testElem(el){ 
if (el.className.split(&#39; &#39;).indexOf(name) > -1) { 
list.push(el); 
} 
} 
//check the root element 
testElem(elem); 
//initialize the stack 
stack.push({ 
pointer: elem, 
num: 0 
}); 
var parent, num, el; 
while (true) { 
parent = stack.get(); 
el = parent.pointer.children[parent.num]; 
if (el) {//enter a deeper layer of the tree 
testElem(el); 
stack.push({ 
pointer: el, 
num: 0 
}); 
} 
else {//return to the upper layer 
if (stack.pop().pointer === elem) { 
break; 
} 
else { 
stack.get().num += 1; 
} 
} 
} 

return list; 
}

 归纳起来。所有循环都可以用递归实现;所有递归都可以用循环实现。采用哪种方法,由具体问题下哪种思路更方便直观和使用者的喜好决定。

效率

性能方面,递归不比循环有优势。除了多次函数调用的开销,在某些情况下,递归还会带来不必要的重复计算。以计算斐波那契数列的递归程序为例。求第n项A(n)时,从第n-2项起,每一项都被重复计算。项数越小,重复的次数越多。令B(i)为第i项被计算的次数,则有

B(i)=1; i=n, n-1

B(i)=B(i+1)+B(i+2); ia37026b3b3430a8f991b4321a39b15391

令D(i)=C(i)+1,有

D(i)=1; i=0, 1

D(i)=D(i-1)+D(i-1)

所以D(i)又形成一个斐波那契数列。并可因此得出:

C(n)=A(n+1)-1

而A(n)是以几何级数增长,这种多余的重复在n较大时会变得十分惊人。与之相对应的采用循环的程序,有

B(n)=1; n为任意值

C(n)=0; n=0, 1

C(n)=n-1; n>1

因而当n较大时,前面给出的采用循环的程序会比采用递归的程序快很多。

如上一节中的循环一样,递归中的这个缺陷也是可以弥补的。我们只需要记住已经计算出来的项,求较高项时,就可以直接读取以前的项。这种技术在递归中很普遍,被称为“存储”(memorization)。

下面是采用存储技术的求斐波那契数列的递归算法。

代码如下:

//recursion with memorization 
function fibonacci4(n){ 
var memory = []; //used to store each calculated item 
function calc(n){ 
var result, p, q; 
if (n < 2) { 
memory[n] = n; 
return n; 
} 
else { 
p = memory[n - 1] ? memory[n - 1] : calc(n - 1); 
q = memory[n - 2] ? memory[n - 2] : calc(n - 2); 
result = p + q; 
memory[n] = result; 
return result; 
} 
} 
return calc(n); 
}

更多JavaScript的递归之递归与循环示例介绍相关文章请关注PHP中文网!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.