For most of the front end (including me), the time to learn recursion is the time to learn function, the cognition of recursion is the function call itself, and then define an exit for the function, through this function to break one thing into the same type of small things. But you will find that you write recursion rarely, because in many cases recursion can be used to achieve the same effect with a loop, so for the specific use of recursion, when to use recursion, is the main topic of this article.
Can recursion only solve the problem of tree structure?
It’s easy for people who rarely use recursion to solve problems to think of recursion as something that can only be done in the tree case, so let’s start by solving the tree case to get a better understanding of what situations recursion can solve and what other problems it can solve in addition to tree-structured data?
Problem 1: Obtain the root node name
Click the current node to get the name of the root node
The root node does not include the virtual root node in the tree, which refers to the root node of the data
This is the virtual root node
The result is this
This requirement is very simple, we can easily think of recursion, why?
- Obtain the root level: continuously obtain the parent level of the current node until no parent level is the root level
- Recursive exit condition: pass
Node) or the parent node) level
- Changing state values in recursion:
node.parent
getRootNodeName(node) {
if(! node)return
if (node.level === 1) {
return node.label
}
return this.getRootNodeName(node.parent)
}
Copy the code
We implement this recursive function using the above three conditions. What we need to pay attention to here is the changing state value in the recursion. This is the most difficult and important part of the recursion, which will be discussed later
The recursion is actually very easy to write, and the loop can only achieve the same effect
getRootNodeName(node) {
if(! node)return
while (node.level > 1) {
node = node.parent
}
return node.label
}
Copy the code
Problem 2: Obtain the node at the specified level in the tree where the current node resides
There is also a constraint, node node does not have the level attribute, only the parent attribute points to the parent node, here for better expression, we remove the virtual root node in the node.
We want to achieve something like this: click on the current node to get the current node’s hierarchy
The recursive implementation
getNodeLevel(node) {
if(! node.parent)return 1
return this.getNodeLevel(node.parent) + 1
}
Copy the code
If node.parent does not exist, the current node is the first level node, and in the process of backtracking, the parent node + 1 = the current level node
Recursion: You open the door in front of you and see another door in the room. You walk up to it and find that the key in your hand can still open it. You push the door open and find another door inside. You keep opening it. A few times later, you open the door in front of you and there is only one room, no door. Then you go back the way you came, and count each time you come to a room, and when you get to the entrance, you can answer how many doors you opened with this key.
Cycle to achieve
getNodeLevel(node) {
let n = 1
let p = node
while (p.parent) {
p = p.parent
n++
}
return n
}
Copy the code
Loop: You open the door in front of you and see another door inside. You go to the door and find that the key in your hand can open it. You push the door open and find another door inside. (If the first two doors are the same, then this door is the same as the first two; If the second door is smaller than the first door, then this door is smaller than the second door, and you keep opening this door, and you keep doing this until you open all the doors. However, the person at the entrance can’t wait for you to come back and tell him the answer
Since loops have no backtracking, our current loop has to store state data for each recursion
Problem 3: Tree node filtering
One might ask tree node filtering isn’t that in the el-Tree use case? But that doesn’t solve the problem of our actual scenario, let me give you an example
<el-tree ... :filter-node-method="filterNode" /> <script> export default { methods: { filterNode(value, data) { if (! value) return true return data.label.indexOf(value) ! == -1 }, } } </script>Copy the code
Existing problems
The current filter only filters out all the nodes that do not meet the criteria by means of label inclusion, and the desired effect should be to filter out the branches that do not meet the criteria, if the current level meets the criteria then its children should not be filtered out
The recursive implementation
methods: {
filterNodeMethod(value, data, node) {
if(! value) {return true
}
return this.deepfilterChildNode(value, node)
},
deepfilterChildNode(value, node) {
if (node.level === 1) return false
const { filterNodeByIndexOf, deepfilterChildNode } = this
if (filterNodeByIndexOf(node.label, value)) {
return true
}
return deepfilterChildNode(value, node.parent)
},
filterNodeByIndexOf(label, value) {
returnlabel.indexOf(value) ! = = -1}}Copy the code
Cycle to achieve
The main implementation function is deepfilterChildNode, followed by a loop to achieve the synchronization effect
methods: {
deepfilterChildNode(value, node) {
const { filterNodeByIndexOf } = this
if (filterNodeByIndexOf(node.label, value)) {
return true
}
// Tier 1 nodes have no parent
if (node.level === 1) return false
// Find the largest node
const maxNode = 1
// The parent of the multi-level node meets the requirement, do not filter the child
let parentNode = node.parent
while (parentNode.level > maxNode) {
if (filterNodeByIndexOf(parentNode.label, value)) {
return true
}
parentNode = parentNode.parent
}
// The maximum parent of the current node is not found
return false}},Copy the code
Problem 4: Implement Instanceof
- Function: Checks whether the stereotype object of the right constructor exists in the stereotype chain of the left object
- Principle: The left prototype chain contains the right prototype object
The left-hand side must be objects
1 instaceof Number // false
Number(1) instaceof Number // false
new Number(1) instaceof Number // true
Copy the code
The right hand side must be a constructor
let f1 = () = > {};
let f2 = function () {}
class f3 {}
obj instanceof f1; / / an error
obj instanceof f2 // false
obj instanceof f3 // false
Copy the code
There is a stereotype object on the right in the left stereotype chain
let obj = {}
class ctor {}
console.log(obj instanceof ctor); // false
Object.setPrototypeOf(obj, new ctor)
console.log(obj instanceof ctor); // true
let getProto = Object.getPrototypeOf
getProto(getProto(obj)) === ctor.prototype // true
Copy the code
The recursive implementation
function _instaceof(obj, ctor) {
// The left side must be the object
if(! (obj &&typeof obj === 'object')) {
return false
}
// Get the prototype chain object of the object
let proto = Object.getPrototypeOf(obj)
// Check whether the object's prototype chain object is the prototype object of the constructor function
return proto === ctor.prototype || _instaceof(proto, ctor)
}
Copy the code
Cycle to achieve
function instanceOf1(obj, ctor) {
if(! (obj &&typeof obj === 'object')) {
return false
}
let proto
while(proto = Object.getPrototypeOf(obj)) {
if (Object.is(proto, ctor.prototype)) return true
obj = proto
}
return false
}
Copy the code
“Problem five” depth attributes
The test data
let obj = {
a: {
b: {
c: 1.cc: {
dd: {
f: 'f Val'}}}},data: {
v1: 'v1'.v2: 'v2'.v3: 'v3'}}Copy the code
Retrieve attributes
function getDeepObjAttr(obj, deepPath) {
const deepGet = (o, paths) = > {
if(! paths.length)return o
return deepGet(Reflect.get(o, paths.shift()), paths)
}
return deepGet(obj, deepPath.split('. '))}Copy the code
console.log(getDeepObjAttr(obj, 'a.b.cc.dd.f')) // f Val
Copy the code
Set properties
function setDeepObjAttr(model, deepPath, val) {
let paths = deepPath.split('. ')
if(! paths.length)return model
const setDeep = (o, p, i) = > {
if (i < 0) return o
let prop = p[i]
// The value to be set for the last layer
if (i === p.length - 1) {
Reflect.set(o, prop, val)
} else {
Reflect.set(o, prop, { ... getDeepObjAttr(model, p.slice(0, i + 1).join('. ')),
...o
})
Reflect.deleteProperty(o, paths[i + 1])}return setDeep(o, p, --i)
}
return Object.assign(model,
setDeep({}, [...paths], paths.length - 1))}Copy the code
setDeepObjAttr(obj, 'a.b.c'.'2')
setDeepObjAttr(obj, 'a.b.cc.dd.f'.'f Val21')
Copy the code
Obj after the change
{
"a": {
"b": {
"c": "2"."cc": {
"dd": {
"f": "f Val21"}}}},"data": {
"v1": "v11"."v2": "v2"."v3": "v3"}}Copy the code
This recursion has multiple exit conditions and is not simply doing one type of thing, and the free variable (global variable) model referenced in the recursive function will be affected if the model is modified during the recursive process
Think of recursively those state values (variables) that need to be extracted globally
Similar to Inception, I have $10 in my hand, I have the first dream (n), in which I spend $2, and then I have another dream (n +1), in which I still have $10, and the money spent in the previous dream does not affect my money in this dream. The state value (variable) is a local variable created inside the recursive function, otherwise the state value (variable) needs to be placed globally
The implementation of the loop is also shown here
function getDeepObjAttr(obj, deepPath) {
let paths = deepPath.split('. ')
if(! paths.length)return obj
let prop,
targetVal,
tempObj = obj
while ((prop = paths.shift()) && paths.length) {
if (!Reflect.has(tempObj, prop)) {
return
}
tempObj = tempObj[prop]
}
return tempObj[prop]
}
Copy the code
function setDeepObjAttr(model, deepPath, val) {
/ / path
let paths = deepPath.split('. ')
// Target value, which stores all attributes that match the path
let targetVal = {}
// Find the prop for each object
let pathsNew = paths.concat([])
let prop
for (let i = paths.length - 1, j = i; i >= 0; i--) {
prop = paths[i]
// The value to be set for the last layer
if (i === j) {
targetVal[prop] = val
} else if (i === 0) {
// The first layer needs to replace the first attribute directly
const obj = this.getDeepObjAttr(model, prop);
Reflect.set(model, prop, Object.assign({}, obj, targetVal))
} else {
// Update the values of each level (excluding stored values)
let curDeppObj = getDeepObjAttr(model, pathsNew.join('. '))
// Store the values of the current hierarchy
Reflect.set(targetVal, prop, Object.assign({}, curDeppObj, targetVal))
// Delete the value stored in the previous path
Reflect.deleteProperty(targetVal, paths[i + 1])}// Remove the processed paths
pathsNew.pop()
}
return model
}
Copy the code
We still haven’t answered the question of whether recursion can only solve the problem of tree structure, which raises another question, what’s the difference between recursion and loop?
What’s the difference between recursion and loop?
Through the above five examples, we found that recursion and loop can be realized. In fact, recursion and loop are two different typical ways of solving problems, which have the same characteristics, namely, doing repetitive tasks. In terms of algorithm design alone, recursion and loop are not superior or inferior. In practice, however, recursion often causes performance problems because of the overhead of function calls, especially if the solution size is uncertain; Loops are more efficient than recursion because they have no function call overhead.
By the way, solving for indeterminacy also involves implicitly large data sizes, but front-end pages rarely deal with very large data sizes, so recursion is fine
Similarities:
- Breaking big things down into small things is ** repetitive tasks ** (loop, recursive calls)
- In the process, state value transformation is called state expansion
The difference between:
- If we use loops we have to build a stack instead of a system stack to hold the contents that deal with the current state
So recursion again
let arr = [1.2.3.4.5.6]
function logArr(arr) {
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
}
logArr(arr)
Copy the code
Let you print each item in turn, can this be done recursively??
consider
The answer is yes
function logArr(arr) {
const dfs = (idx) = > {
if (idx === arr.length) return
console.log(arr[idx]);
dfs(idx + 1)
}
dfs(0)}Copy the code
Fibonacci numbers
Fibonacci sequence refers to the sequence 1, 1, 2, 3, 5, 8, 13, 21, which is simply the sum of the first two numbers after the second number: F0=0, F1=1, Fn=F(n-1)+F(n-2) (n >=2).
Recursive solution
function fibonacci(n) {
// Recursive termination condition
if (n <= 2) {
return 1
}
// Repeat the same logic to reduce the size of the problem
return fibonacci(n - 1) + fibonacci(n - 2)}Copy the code
fibonacci(5)
Copy the code
State expands spanning tree
There’s a lot of double counting going on here, when you compute fib of 5
1Fib (4)
2Fib (3)
3Fib (2)
5Fib (1)
3Fib (0)
Copy the code
Such repeated calculations are equivalent to the same branch, and there is an important operation in DFS called pruning; In the current implementation of recursion we can cache the repeated computations and not call them when they happen later, which is pruning, which is a big performance optimization for recursion.
// Create an empty object as a container for the cache
let cache = {};
function fibonacci(n) {
if (n === 1 || n === 2) {
return 1;
}
if (cache[n]) {
return cache[n];
}
return cache[n] = fibonacci(n - 1) + fibonacci(n - 2);
}
Copy the code
There is also a caching scheme, where the computation is performed recursively and the final result is returned backtracking
function fibonacci(n, cur = 0, next = 1) {
if(n == 0) return 0;
if(n == 1) return next;
return fibonacci(n - 1, next, cur + next);
}
Copy the code
Cycle to solve
function fibonacci(n) {
if (n <= 2) return 1
// Maintain the state value of "stack" for state backtracking
let first = 1
// Maintain the state value of "stack" for state backtracking
let second = 1
let ret = 0
n -= 2
while (n--) {
// Current number = previous number + previous two numbers
ret = first + second
first = second
second = ret
}
return ret
}
Copy the code
Now, to answer the first question, recursion is not just for tree data structures and data with distinct hierarchies. For this case, data is very recursive and we can easily imagine things like Fibonacci numbers, factorials, Yang Hui triangles… The way that you can solve it recursively you can also solve it recursively by changing the state values as you recurse.
Binary search
Cycle to achieve
function binarySearch(nums, target) {
let l = 0
let r = nums.length - 1
let mid
while (l <= r) {
mid = (l + r) >> 1
if (nums[mid] === target) {
return mid
} else if (nums[mid] > target) {
r = mid - 1
} else {
l = mid + 1}}return -1
}
Copy the code
The recursive implementation
Transform the state value and expand the spanning tree
function binarySearch(nums, target, l = 0, r = nums.length - 1) {
let mid
while (l <= r) {
mid = (l + r) >> 1
if (nums[mid] === target) {
return mid
} else if (nums[mid] > target) {
return binarySearch(nums, target, l, mid - 1)}else {
return binarySearch(nums, target, mid + 1, r)
}
}
return -1
}
Copy the code
Application of recursion in algorithms
In the algorithm for recursion application scenarios are particularly special, DFS: backtracking, binary tree traversal, rollover, path summation… , the above types of problems with recursion brush a few questions, for recursion will have a deeper cognition and understanding, if the needs encountered behind the recursive solution, naturally you can think of recursion.
Now give some recursively solvable Leetcode problems, not to say to learn algorithm brush problems, the purpose here is to do these problems can let us have a deeper understanding of the use of recursion, recursion is not too much may take some time, after mastering all of them is also a promotion for myself
Binary tree traversal
There are three ways to traverse a binary tree:
- Front-order traversal: root, left, right
- Middle order traversal: left, root, right
- Subsequent traversal: left, right, root
The former sequence traversal
Leetcode144. Antecedent traversal of binary trees
/ * * *@param {TreeNode} root
* @return {number[]}* /
var preorderTraversal = function(root) {
let ans = []
const preIterator = (root) = > {
if(! root)return
ans.push(root.val)
preIterator(root.left)
preIterator(root.right)
}
preIterator(root)
return ans
}
Copy the code
In the sequence traversal
Leetcode94. Middle order traversal of binary trees
/ * * *@param {TreeNode} root
* @return {number[]}* /
var inorderTraversal = function(root) {
let ans = []
const inIterator = (root) = > {
if(! root)return
inIterator(root.left)
ans.push(root.val)
inIterator(root.right)
}
inIterator(root)
return ans
};
Copy the code
After the sequence traversal
Leetcode145. Post-order traversal of binary trees
/ * * *@param {TreeNode} root
* @return {number[]}* /
var postorderTraversal = function(root) {
let ans = []
const postIterator = (root) = > {
if(! root)return
if(! postIterator(root.left)) {if(! postIterator(root.right)) { ans.push(root.val) } } } postIterator(root)return ans
};
Copy the code
Path to the combined
Leetcode112. Sum of paths
var hasPathSum = function (root, targetSum) {
if(! root)return false
targetSum = targetSum - root.val
// The current node is a leaf node
if(! root.left && ! root.right) {// If the current node matches all ragetsums, the current node is the target node
return targetSum === 0
}
return hasPathSum(root.left, targetSum) || hasPathSum(root.right, targetSum)
};
Copy the code
Leetcode113. Sum of paths II
/ * * *@param {TreeNode} root
* @param {number} targetSum
* @return {number[][]}* /
var pathSum = function (root, targetSum) {
let ans = []
const dfs = (root, targetSum, temp = []) = > {
if(! root)return
temp.push(root.val)
targetSum -= root.val
if(! root.left && ! root.right && targetSum ===0) {
ans.push([...temp])
}
dfs(root.left, targetSum, temp)
dfs(root.right, targetSum, temp)
temp.pop()
}
dfs(root, targetSum)
return ans
};
Copy the code
back
Leetcode39. Sum of combinations
/ * * *@param {number[]} candidates
* @param {number} target
* @return {number[][]}* /
var combinationSum = function (candidates, target) {
const ans = []
const dfs = (idx, target, temp = []) = > {
if (idx === candidates.length) return
if (target < 0) return
if (target === 0) {
ans.push([...temp])
temp = []
return
}
temp.push(candidates[idx])
dfs(idx, target - candidates[idx], temp)
temp.pop()
dfs(idx + 1, target, temp)
}
dfs(0, target)
return ans
};
Copy the code
Write in the last
Practice makes perfect, shortage in play
If there is that piece of writing is not good or there is a problem, you are welcome to point out, I will also keep modifying in the following article. I hope I can grow with you as I progress. Friends who like my article can also follow it, I will be grateful for the first batch of people to follow me. At this time, young you and I, travel light; Then, rich you and I, full of goods.