Algorithm routine 8 - binary tree depth-first traversal (pre-, middle-, and post-order traversal)

Algorithm routine 8 - binary tree depth-first traversal (pre-, middle-, and post-order traversal)

Algorithm Example: LeetCode98: Verifying a Binary Search Tree

Given the root node root of a binary tree, determine whether it is a valid binary search tree.
A valid binary search tree is defined as follows:
a node's left subtree contains only numbers less than the current node.
The right subtree of a node contains only numbers greater than the current node.
All left and right subtrees must themselves be binary search trees.
insert image description here

Method 1: Preorder traversal - judge first, then recurse

Preorder traversal means traversing the root node first, and then traversing the left and right subtrees.
Our idea of ​​preorder traversal is to first determine whether the current node meets the conditions of the binary search tree, and then recurse the left and right subtrees.
insert image description here
And as shown in the above figure, in the binary search tree, when using the preorder traversal, the above rules are passed, and the value range is passed from the root node. For any node, the value range has been determined. If the node value is not in the range , it is not a binary search tree.
The steps are as follows:

  1. The value range of the root node is (-inf, +inf), to determine whether the condition is met
  2. Determine whether the left subtree is a binary search tree, and at this time the maximum value should be less than root.val, so the value range is (-inf, root.val]
  3. Determine whether the right subtree is a binary search tree, and at this time the minimum value should be greater than root.val, so the value range is [root.val,inf)
  4. For 2,3 take recursive traversal

And pay attention to determine whether the root is empty

class Solution:
    def isValidBST(self, root: Optional[TreeNode], left=-inf, right=inf) -> bool:
        if root is None:
            return True
        x = root.val
        return left < x < right and \
               self.isValidBST(root.left, left, x) and \
               self.isValidBST(root.right, x, right)

Method 2: In-order traversal - judge first, then recurse

In-order traversal means first traversing the left node, the root node, and finally the right node.
And the binary search tree under the in-order traversal should be an incremental array, so we directly judge whether the current node value is greater than the previous traversal node value pre. In fact,
this is also the case Value is limited to the range of nodes, only the minimum value needs to be modified during in-order traversal, that is, the range of values ​​is (pre, inf)

  1. Determine whether the left subtree is a binary search tree, and record the value of the last traversed node of the left subtree as pre, which is also the maximum value of the left subtree
  2. Compare whether the current node refers to whether it is greater than pre, that is, the value range is (pre,inf),
  3. Determine whether the right subtree is a binary search tree
  4. For 1,3 take recursive traversal
class Solution:
    pre = -inf
    def isValidBST(self, root: Optional[TreeNode]) -> bool:
        if root is None:
            return True
        if not self.isValidBST(root.left):
            return False
        if root.val<=self.pre:
            return False
        self.pre = root.val
        return self.isValidBST(root.right)
        

Method 3: Post-order traversal - recurse first, then judge

Post-order traversal means first traversing the left node and right node, and finally traversing the root node.
Post-order traversal can also pass the range of nodes, but it is passed from the leaf node to the root node. The root node needs to be greater than the maximum value of the left subtree and smaller than the right child The minimum value of the tree.
insert image description here

  1. If the current node is a null node, return (inf,-inf), because any value will be less than inf, and any value will be greater than -inf, so that it will not affect the maximum and minimum values ​​of the tree, you can experience it carefully.
  2. Traverse the left subtree and return the minimum value l_min and maximum value l_max of the left subtree
  3. Traverse the right subtree and return the minimum value r_min and maximum value r_max of the right subtree
  4. Compare the value of the current node, if the value is in (l_max, r_min), update the minimum and maximum values ​​and return min(l_min, x), max(r_max, x). If the value is not within the range, it means that it is not a binary search tree. At this time, we return (-inf, inf) that will not be returned under normal circumstances to indicate False
  5. Compare whether the return value is a normal value, which is equivalent to judging whether it is equal to inf, infinite or abnormal value, if it is equal to inf, it will return False, if it is not equal to inf, it will return True
class Solution:
    def isValidBST(self, root: Optional[TreeNode]) -> bool:
        def dfs(node: Optional[TreeNode]) -> Tuple:
            if node is None:
                return inf, -inf
            l_min, l_max = dfs(node.left)
            r_min, r_max = dfs(node.right)
            x = node.val
            # 也可以在递归完左子树之后立刻判断,如果不是二叉搜索树,就不用递归右子树了
            if x <= l_max or x >= r_min:
                return -inf, inf#返回无穷表示为False,不满足搜索树
            return min(l_min, x), max(r_max, x)
        return dfs(root)[1] != inf

Summarize:

Preorder traversal can return without recursing to the base case under some data, while the other two need to recurse to at least one boundary. From this perspective, it is the fastest.
Inorder traversal makes good use of the properties of binary search trees, using the fewest variables.
The idea of ​​post-order traversal is the most general, that is, the process of computing sub-problems from the bottom up. If you want to learn dynamic programming well, be sure to master this idea.
And it can be seen from the above sample code that it must be defined when writing the code 内部匿名函数dfs, otherwise it may affect the result due to the LeetCode judgment problem

Algorithm exercise 1: LeetCode230. The Kth smallest element in a binary search tree

Given a root node root of a binary search tree and an integer k, please design an algorithm to find the kth smallest element (counting from 1).insert image description here

Using the in-order traversal feature, the binary search tree should be an increasing array
. In this question, in-order traversal can be used. When traversing each time, k–, when k is 0, it means that the current node is the kth node. Let ans be equal to this value

func kthSmallest(root *TreeNode, k int) int {
    
    
    var ans int
    var dfs func(node *TreeNode) 
    dfs=func(node *TreeNode) {
    
    
        if node==nil{
    
    
            return 
        }
        dfs(node.Left)
        k--
        if k==0{
    
    
            ans=node.Val
        }
        dfs(node.Right)
    }
    dfs(root)
    return ans
}

Algorithm Exercise 2: LeetCode501. Moderate Number in Binary Search Tree

Given the root node of a binary search tree (BST) with duplicate values, find and return all modes (ie, elements that occur most frequently) in the BST. If there is more than one mode in the tree, it can be returned in any order.
insert image description here

Using the in-order traversal feature, the binary search tree should be an increasing array. In
this question, in-order traversal can be used to compare the traversed node with the previous node, and then use the variables cur and max to record the current node and the most nodes, and pay attention to define an anonymous function solve.

func findMode(root *TreeNode) []int {
    
    
    var (
        ans []int
        pre, cur, max int
        dfs func(*TreeNode)
    )
    dfs = func(node *TreeNode) {
    
    
        if node == nil {
    
    
            return
        }
        dfs(node.Left)
        if node.Val == pre {
    
    
            cur++
        } else {
    
    
            cur = 1
        }
        if cur > max {
    
    
            max = cur
            ans = []int{
    
    node.Val}
        } else if cur == max {
    
    
            ans = append(ans, node.Val)
        }
        pre = node.Val
        dfs(node.Right)
    }
    dfs(root)
    return ans
}

Algorithm exercise 3: LeetCode530. Minimum absolute difference of binary search tree

Given the root node root of a binary search tree, return the minimum difference between any two different node values ​​in the tree. Difference is a positive number whose value is equal to the absolute value of the difference between the two values.insert image description here

Using the in-order traversal feature, the binary search tree should be an increasing array. In
this question, in-order traversal can be used to compare the traversed node with the previous node, and then use the variables pre and min to record the minimum difference between the previous node value and the current one. And define an anonymous function to resolve.

func getMinimumDifference(root *TreeNode) int {
    
    
    min, pre := math.MaxInt64, -1
    var dfs func(node *TreeNode)
    dfs=func(node *TreeNode){
    
    
    if node==nil{
    
    
        return 
    }
    dfs(node.Left)
    sub:=node.Val-pre
    if sub<min&&pre!=-1{
    
    
        min=sub
    }
    pre=node.Val
    dfs(node.Right)
}
    dfs(root)
    return min
}

Algorithm Exercise 4: LeetCode700. Search in Binary Search Tree

Given a binary search tree (BST) root node and an integer value val. You need to find a node in the BST whose node value is equal to val. Returns the subtree rooted at this node. Returns null if the node does not exist.insert image description here

If root is empty, return an empty node;
if val=root.val, return \textit{root}root;
if val<root.val, recurse the left subtree;
if val>root.val, recurse the right subtree.

func searchBST(root *TreeNode, val int) *TreeNode {
    
    
    if root == nil {
    
    
        return nil
    }
    if val == root.Val {
    
    
        return root
    }
    if val < root.Val {
    
    
        return searchBST(root.Left, val)
    }
    return searchBST(root.Right, val)
}

Algorithm Advancement 1: LeetCode236. The nearest common ancestor of the binary tree

Given a binary tree, find the nearest common ancestor of two specified nodes in the tree.insert image description here

This question can be discussed by category, as shown in the figure below, define the function dfs() to return whether p or q is found in the subtree of the current node node, the following situations
insert image description here

func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
    
    
	return dfs(root,p,q)
}
func dfs(node, p, q *TreeNode) *TreeNode{
    
    
    if node == nil || node == p || node == q {
    
    
		return node
	}
	left := dfs(node.Left, p, q)
	right := dfs(node.Right, p, q)
	if left != nil && right != nil {
    
    
		return node
	}
	if left != nil {
    
    
		return left
	}
	return right
}

Algorithm Advanced 2: LeetCode236. The nearest common ancestor of the binary tree

Given a binary search tree, find the nearest common ancestor of two specified nodes in the tree.
insert image description here

This question is the same as the previous question, except that when judging the positions of p and q, the size and properties of the clue binary tree value can be used to judge
insert image description here

func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
    
    
	return dfs(root,p,q)
}
func dfs(node, p, q *TreeNode) *TreeNode{
    
    
    if node == nil || node == p || node == q {
    
    
		return node
	}
	if node.Val>p.Val&&node.Val>q.Val{
    
    
        return dfs(node.Left,p,q)
    }else if node.Val<p.Val&&node.Val<q.Val{
    
    
        return dfs(node.Right,p,q)
    }
    return node
}

Guess you like

Origin blog.csdn.net/qq_45808700/article/details/129997966