1. What is recursion
The function call itself is called recursion. There is nothing to say about it. What we really need to understand is the meaning of the word "recursion" and "return". Before understanding the concept of these two words, we must first be clear that there will be One 调用栈
thing, it is just like an array 容器
, follow 进栈
and 出栈
, the order of elimination of entry and exit is according to 先进后出
, recursion corresponds to this relationship, 递
that is 进
, 归
that is 出
, when other functions are called in the f1 function, the f1 function advances , wait until the end, see the example
function f3() {
}
function f2() {
f3() }
function f1() {
f2() }
f1()
Into [f1,f2,f3] Out of f3, f2, f1
If no other functions are called in the function, it will be popped immediately after being pushed into the stack
function f1(){
}
function f2() {
}
f1()
f2()
In [f1] Out of f1 In [f2] Out of f2
Now think about the following question, what is its push into the stack?
function f1() {
f1()
}
f1()
Obviously, it has been entering [f1, f1, f1, f1, f1, f1, f1 ...] but not exiting. Since is 调用栈
limited 容器
, it will report after a certain number of calls (push into the stack) 栈溢出
. The above case is a dead recursion , we should give it a terminating condition to turn it into a live recursion.
function f1(n) {
if (n == 1) return 1
fn(n - 1)
}
f1(4)
Push f1(4), f1(3), f1(2), f1(1)] pop f1(1), f1(2), f1(3), f1(4)
2. The role of recursion
Recursion can be used in various scenarios such as 深拷贝
, 渲染树形图
, 计算阶乘
, 排序
, 移动位置
, 中间件调用
etc. Its essence is reuse, saving code and loops, and making the code look more concise and elegant.
3. Recursive case analysis
Next I will use JS as a case demo
3.1 JavaScript deep copy
function copyDeep(obj) {
let o = {
}
for (let key in obj) {
if (typeof key === 'Object') {
o[key] = copyDeep(key)
} else {
o[key] = obj[key]
}
}
return o
}
let father = {
name: 'Jack',
children: [
{
name: 'Tony',
age: 15,
children: [{
name: 'Baby', age: 20}, {
name: 'Anna', age: 2}]
},
{
name: 'Candy',
age: 20
}
]
}
let mother = copyDeep(father)
Let's analyze the above execution process. First, we only need to care about o[key] = copyDeep(key)
the recursive calls . Suppose we don't use recursion but replace copyDeep with copy function
if (typeof key === 'Object') {
o[key] = copy(key)
} else {
o[key] = obj[key]
}
copy
The code of the function is as follows
function copy(obj) {
let o = {
}
for (let key in obj) {
o[key] = obj(key)
}
return o
}
It’s okay to do this, but we know that Tony still has a layer of children, so the copy inside needs to add a copy2 function, the code is as follows
function copy2(obj) {
let o = {
}
for (let key in obj) {
o[key] = key
}
return o
}
function copy(obj) {
let o = {
}
for (let key in obj) {
if (key === 'Object') {
o[key] = copy2(key)
} else {
o[key] = key
}
}
return o
}
According to this situation, a new method must be added for each layer of objects, and these methods are exactly the same. Now we can understand its function by understanding the recursive call.
if (typeof key === 'Object') {
o[key] = copyDeep(key)
} else {
o[key] = obj[key]
}
3.2 Quick Sort
Quick sort is an upgraded version of bubble sort. If you want to know more about quick sort, you can read what I wrote earlier. Understanding Quick Sort
function swap(arr, i, j) {
let temp = arr[i]
arr[i] = arr[j]
arr[j] = temp
}
function QuickSort(arr, low, high) {
if (low >= high) return
// 左右各自一个指针
// pivot 作为左右指针时比较的基准值
let i = low, j = high, pivot = arr[low]
while(i < j) {
// 当出现 i < j 时说明左右边指针已经碰到一起了,可以结束掉
// 右边大于基准值的忽略掉继续从右往左走
while (arr[j] >= pivot && i < j ) {
j --
}
// 左边大于基准值的忽略掉继续从左往右走
while (arr[i] <= pivot && i < j ) {
i ++
}
// 指针走完后会出现 arr[j] < pivot && arr[i] > pivot 的局面,
// 这时候就可以调换位置了
swap(arr, i, j)
}
// 第一次排序完后变成这样:[10, 8, 2, 9, 4, 20, 30, 12]
// 这时候我们再将基准值放在它们两者的中间,最后就会变成
// [8, 2, 9, 4, 10, 20, 30, 12] 左边都是小于10,右边都是大于10
swap(arr, low, j)
// 按照上面这种关系,我们使用递归继续复用,传递左边的集合对应的索引即:[8, 2, 9, 4]
QuickSort(arr, low, j - 1)
// 按照上面这种关系,我们使用递归继续复用,传递左边的集合对应的索引即:[20, 30, 12]
QuickSort(arr, j + 1, high)
// 最终就能排好顺序了
return arr
}
let arr = [10, 2, 9, 12, 8, 20, 30, 4]
QuickSort(arr, 0, arr.length - 1)
// 排序后
// arr: [2, 4, 8, 9, 10, 12, 20, 30]
There are many implementation versions of quick sort, but the problem-solving ideas are the same. It mainly uses the idea of divide and conquer. Students who do not understand quick sort are advised not to just read the code, but to write it first, and then use the relevant debugger
debugging Understand the transfer logic between them.
4. Understand the execution order of two-level recursion
If you can understand the execution order of two layers of recursion, then I believe you will have a deeper understanding of recursion. Before reading the analysis below, think about what the following code will eventually print.
function f(n):
console.log('f::', n)
if (n == 1) {
return 1
}
return f(n-1) + f(n-1)
f(4)
Analysis:
Call f(4) and print f::4, if the condition is not met, continue to go down
Call f(n-1), print f::3, if the condition is not met, continue to call
f(n-1) , print f::2, if the condition is not met, continue to go down
Call f(n-1), print f::1, if the condition is met, end the current function, go 回栈
to f::2
and call + after the function f::2 f(n-1), print f::1, if the condition is met, end the current function, 回栈
go to f::3 call
after the function f::3 + f(n-1), print f::2, if the condition is not Continuing to go down
Call f(n-1), print f::1, if the condition is met, end the current function, return to f::2 Call
+ f(n-1) after function f::2, print f::1, end the current function, call + f(n-1) after 回栈
f::4 function
f::4, print f::3, if the condition is not met, continue to call
f(n-1) , print f::2, if the condition is not met, continue to go down
Call f(n-1), print f::1, if the condition is met, end the current function, return to f::2 and call
after function f::2 + f(n-1), print f::1, if the condition is met, end the current function, return to f::3 function f::3 call
+ f(n-1), print f::2, if If the condition is not met, continue to go down
Call f(n-1), print f::1, if the condition is met, end the current function, and return to the f::2 function
Call +f(n-1) after f::2, print f::1, end the current function, and all the stacks have been eliminated. So far, all recursion calls have been completed
, and finally print:
f:: 4
f:: 3
f:: 2
f:: 1
f
:: 1
f:: 2
f:: 1 f:: 1
f:: 3
f:: 2
f
:: 1
f:: 1 f:: 2
f:: 1
f:: 1
over!