Binary tree is a kind of data structure, and has complex kinds of branches. This article serves as an introductory article, only introduces some basic binary tree questions, such as binary search tree, etc. are not introduced in this article.
The binary tree is actually an upgraded version of the linked list, that is, the linked list has two Next pointers at the same time, and it becomes a binary tree.
Binary trees can reduce the time complexity of searching to logn according to some characteristics, such as searching binary trees, and the data structure of heap is also a special binary tree, which can find the maximum or minimum value with O(1) time complexity . Therefore, there are many variants of binary trees, which can solve the problems of specific scenarios.
intensive reading
To get started with binary trees, you must understand the three traversal strategies of binary trees, namely: pre-order traversal, middle-order traversal, and post-order traversal. These are all depth-first traversals.
The so-called front, middle and back refers to the time when the node value is accessed, and the other time is to visit the child nodes from the left and then the right. For example, the pre-order traversal is to access the value first, and then the left and right; the subsequent traversal is to first visit the left and right, and then the value; the middle-order traversal is the left, value, and right.
It is very simple to traverse the tree recursively:
function visitTree(node: TreeNode) {
// 三选一:前序遍历
// console.log(node.val)
visitTree(node.left)
// 三选一:中序遍历
// console.log(node.val)
visitTree(node.right)
// 三选一:后序遍历
// console.log(node.val)
}
Of course, the problem requires us to cleverly use the three traversal characteristics of the binary tree to solve the problem, such as rebuilding the binary tree.
Rebuild Binary Tree
Rebuilding a binary tree is a middle-level problem, the topic is as follows:
Enter the results of pre-order traversal and mid-order traversal of a binary tree, and please rebuild the binary tree. Assume that the input result of pre-order traversal and middle-order traversal does not contain repeated numbers.
E.g
[3,9,20,15,7]
traversal preorder = 060e28ad97b9bb
[9,3,15,20,7]
traversal inorder = 060e28ad97b9df
First give you the pre-order and middle-order traversal results of the binary tree, and let you rebuild the binary tree. This kind of reverse thinking problem is a lot more difficult.
Observing the traversal characteristics carefully, we can see that we may be able to infer the location of some key nodes, and then we can solve the problem by cutting and recursing the array.
The first visit in the pre-order traversal must be the root node, so 3
must be the root node, and then we find 3
in the middle-order traversal, so that the is the result of the in-order traversal of all left subtrees, and the right side is the results of all right subtrees. The middle order traversal result , we only need to find the left subtree preorder traversal result and the right subtree preorder traversal result , then recursion, the termination condition is that the left or right subtree has only one value, so it means Leaf node.
So how to find the preorder traversal of the left and right subtrees? In the above example, we found the 3
of the in-order traversal of the left and right subtrees of 060e28ad97bab2. Since the pre-order traversal gives priority to access to the left subtree, we count the number on the 3
in the in-order traversal. There is only one 9
, so we used to order traversal 3,9,20,15,7
in 3
push one after, then 9
is the result of pre-order traversal left subtree, 9
back 20,15,7
preorder traversal result is a grapefruit tree.
Finally, the problem can be solved by recursively. We will continue to disassemble the input into the input of the left and right subtrees until the termination condition is reached.
The key to solving this problem is not only to know how to write the first, middle, and last traversal, but also to know that the first node of the pre-order traversal is the root node, the last node of the post-order traversal is the root node, and the middle-order traversal centers on the root node. , Left and right are its left and right subtrees respectively, these are important extension features.
After talking about the reverse, we said forward, that is, recursively a binary tree.
In fact, in addition to recursion, a common traversal method for binary trees is to use the stack for breadth-first traversal. Typical problems include printing binary trees from top to bottom.
Print binary tree from top to bottom
Printing a binary tree from top to bottom is a simple problem, the topic is as follows:
The binary tree is printed in layers from top to bottom, the nodes in the same layer are printed in order from left to right, and each layer is printed to one line.
This question requires printing in order from left to right, fully following the breadth-first traversal. When the binary tree is recursive, we can not rush to read the value, but according to the left, middle, and right, when we encounter the left and right subtree nodes, we push At the end of the stack, use the while
statement to loop continuously until the stack is empty.
The method of appending to the end of the stack during unwinding and continuously looping through stack elements is very elegant and conforms to the characteristics of the stack.
Of course, if the title requires printing in reverse order, you can process it in the order of right, center, and left.
Next, let's look at depth-first traversal. The typical topic is the depth of the binary tree.
Depth of binary tree
The depth of the binary tree is a simple question, the topic is as follows:
Enter the root node of a binary tree and find the depth of the tree. The nodes (including root and leaf nodes) that pass through from the root node to the leaf node in turn form a path of the tree, and the length of the longest path is the depth of the tree.
Since the binary tree has multiple branches, we don't know which route is the deepest before traversing, so we must use recursion to try.
We can change our thinking and use functional semantics to understand it. Suppose we have such a function deep
to find the depth of the binary tree, then what is the content of this function? Binary trees can only have left and right subtrees, so deep
must be the maximum depth of the left and right subtrees + 1 (itself).
To find the depth of the left and right subtrees, you can reuse the deep
function to form recursion. We only need to consider the boundary condition, that is, when the access node does not exist, return the depth 0
, so the code is as follows:
function deep(node: TreeNode) {
if (!node) return 0
return Math.max(deep(node.left), deep(node.right)) + 1
}
It can be seen from this that binary trees can generally be solved with more elegant recursive functions. If your problem-solving idea does not include recursion, it is often not the most elegant solution.
There is a similarly elegant topic, the balanced binary tree.
Balanced binary tree
Balanced binary tree is a simple problem, the topic is as follows:
Enter the root node of a binary tree to determine whether the tree is a balanced binary tree. If the depth of the left and right subtrees of any node in a binary tree does not exceed 1, then it is a balanced binary tree.
In the same way, we set the function isBalance
as the answer function, then the characteristic of a balanced binary tree must be that its left and right subtrees are also balanced, so it can be written as:
function isBalance(node: TreeNode) {
if (root == null) return true
return isBalance(node.left) && isBalance(node.right)
}
But what's wrong, the balance between the left and right subtrees is not enough. In case the depth difference between the left and right subtrees exceeds 1, it will be broken, so the depth of the left and right subtrees is also required. We reuse the function deep
question and organize it as follows:
function isBalance(node: TreeNode) {
if (root == null) return true
return isBalance(root.left) && isBalance(root.right) &&
Math.abs(deep(root.left) - deep(root.right)) < 2
}
This question reminds us that not all recursion can be perfectly written in a mode that only calls itself. Different questions should be supplemented with other functions, and we must be keenly aware of what conditions are still missing.
There is also a kind of recursion. It is not a simple function to recurse itself, but to construct another function to recurse. The reason is that the recursive parameters are different. A typical topic is a symmetric binary tree.
Symmetric binary tree
Symmetrical binary tree is a simple problem, the topic is as follows:
Please implement a function to determine whether a binary tree is symmetric. If a binary tree is the same as its mirror image, then it is symmetric.
We should note that the mirroring of a binary tree is special. For example, the leftmost node and the rightmost node are mirror images of each other, but their parent nodes are not the same. Therefore isSymmetric(tree)
cannot be sub-recursive, and we must disassemble it into left and right Subtrees are used as parameters, and let them be equal. When passing parameters, pass in the left and right nodes that are different in parent but are mirror images of each other.
So we have to create a new function isSymmetricNew(left, right)
, compare left.left
with right.right
, and compare left.right
with right.left
.
The specific code is not written, and then pay attention to the boundary conditions.
The point of this question is that because of the mirroring relationship, they do not have the same parent node, so a function with a new parameter must be used for recursion.
What if this question is reversed? What about the construction of a binary tree mirror?
Mirror of Binary Tree
Mirroring a binary tree is a simple question, the topic is as follows:
Please complete a function, input a binary tree, and the function outputs its mirror image.
It is easier to judge the mirror, but it is necessary to think about the construction of the mirror:
例如输入:
4
/ \
2 7
/ \ / \
1 3 6 9
镜像输出:
4
/ \
7 2
/ \ / \
9 6 3 1
Observed that, in fact, can be understood as mirroring left and right subtrees are interchangeable, while about each of its sub-tree sub-tree recursively exchange , which constitutes a recursive:
function mirrorTree(node: TreeNode) {
if (node === null) return null
const left = mirrorTree(node.left)
const right = mirrorTree(node.right)
node.left = right
node.right = left
return node
}
We want to go from bottom to top, so first generate the recursive left and right subtrees, then exchange the current nodes, and finally return to the root node.
Next, some classic questions with a certain degree of difficulty are introduced.
Nearest common ancestor of binary tree
The nearest common ancestor of a binary tree is a middle-level problem, the topic is as follows:
Given a binary tree, find the nearest common ancestor of two specified nodes in the tree.
The topic is very short and clear. It is to find the nearest common ancestor. Obviously, the root node is the common ancestor of all nodes, but not necessarily the nearest.
We still use recursion, first consider the special case: if any node is equal to the current node, then the current node must be the nearest common ancestor, because another node must be among its children.
Then, using recursive thinking, suppose we use the lowestCommonAncestor
function to find the nearest common ancestor of the left and right child nodes respectively?
function lowestCommonAncestor(node, a, b) {
const left = lowestCommonAncestor(node.left)
const right = lowestCommonAncestor(node.right)
}
If the left and right nodes are not found, it means that only the current node is the nearest public child node:
if (!left && !right) return node
If the left node is not found, the right node is the answer, otherwise the opposite:
if (!left) return right
return left
Here, function semantics are used cleverly to judge the result.
Right view of the binary tree
The right view of the binary tree is a middle-level problem, the topic is as follows:
Given a binary tree, imagine yourself standing on the right side of it, and return the node values that can be seen from the right side in order from top to bottom.
Imagine a beam of light, shining from the right side of the binary tree to the left, and reading from top to bottom is the answer.
In fact, this question can be considered a fusion question. The beam on the right can be considered as layered illumination, so when we traverse the breadth-first algorithm, for each layer, we find the last node to print, and printing in order is the final answer.
There is a binary tree problem. According to the depth of the tree, it is printed as a two-dimensional array according to the breadth-first traversal. In fact, there is also a clever way to record the depth of the tree. That is, when adding an element at the end of the stack, add a depth key, then the access will naturally be possible. Read the depth value.
Number of nodes in a complete binary tree
The number of nodes in a complete binary tree is a middle-level question, the question is as follows:
Give you the root node
root
complete binary tree , and find the number of nodes in the tree.complete binary tree is defined as follows: In a complete binary tree, except for the lowest node may not be filled, the number of nodes in each layer reaches the maximum, and the nodes of the lowest layer are concentrated in the leftmost positions of the layer . If the bottom layer is the
h
layer, this layer contains1 ~ 2^h
nodes.
If you use recursion to solve this problem, the key is to explore the complete binary tree in several situations.
Since the bottom layer may not be filled, but there must be nodes at the bottom layer, and it is filled from left to right, then the maximum depth of the tree can be obtained by recursively traversing the left node. Through the maximum depth, we can quickly calculate the number of nodes. The premise is that the binary tree must be full.
But the lowest node may be dissatisfied, so what should I do? It can be divided into situations. First, if the node.right....right
, and it is found to be the same as the maximum depth, then it is a full binary tree, and the result can be directly calculated.
Let's look at node.right...left
if it is equal to the maximum depth, indicating that node.left
that is, the left subtree is a full binary tree, and the number of nodes can be quickly calculated 2^n-1
What if it is not equal to the maximum depth? means that the depth of the right subtree minus 1 is a full binary tree . You can also use mathematical formulas to quickly calculate the number of nodes, and then recursively calculate the other side.
to sum up
From the problem, we can see that the charm of binary tree solution lies in recursion. In binary tree problem, we can pursue elegance and answer at the same time.
The discussion address is: Intensive Reading "Algorithm-Binary Tree" · Issue #331 · dt-fe/weekly
If you want to participate in the discussion, please click here , there are new topics every week, weekend or Monday release. Front-end intensive reading-to help you filter reliable content.
Follow front-end intensive reading WeChat public
<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">
Copyright statement: Freely reprinted-non-commercial-non-derivative-keep the signature ( creative commons 3.0 license )
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。