Algorithms & Understanding Recursion

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]
}

copyThe 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 debuggerdebugging 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!

Guess you like

Origin blog.csdn.net/cookcyq__/article/details/124779789