Animation demonstration | binary tree de depth first search DFS

Keywords: Programming

principle

Depth first search (DFS) follows the principle that it always goes along one side of the node, all the way to black, then returns to the starting node, and then continues to the next side. If it finds the target node, it returns, if it cannot find it, it will traverse all the nodes. Because there are only two sides of a binary tree, DFS, for a binary tree, first traverses the left subtree, then traverses the right subtree, which is equivalent to traversing in order.

The following animation demonstrates the process of finding 5:

DFS animation demonstration

The principle is simple and clear, but as the saying goes:

Details are the devil

Let's see how to implement DFS with Swfit, and give some problems that need to be noticed in programming.

Recursive implementation of DFS

Since recursion is very consistent with algorithm logic, let's first look at the implementation of recursion. First, define the nodes of binary tree:

public class TreeNode {
    public var val: Int //Node value
    public var left: TreeNode? //Left node
    public var right: TreeNode? //Right node
    public init(_ val: Int) {
        self.val = val
        self.left = nil
        self.right = nil
    }
}

Complete recursive implementation:

func dfsTree(_ root: TreeNode?, _ dst: Int) -> TreeNode? {
    if (root == nil) {
        return nil
    }
    if (root?.val == dst) {
        return root
    }
    var dstNode = self.dfsTree(root?.left, dst)
    if (dstNode == nil) {
        dstNode = self.dfsTree(root?.right, dst)
    }
    
    return dstNode
}

Because binary tree itself is defined recursively, it is very natural to use recursive call, as long as we deal with left first, then right.

Non recursive implementation of DFS

Due to the low efficiency of most recursion, we try to expand recursion into a loop. At this time, there are two problems to consider:

  1. After the left branch access is completed, to get the right branch back to the node, you need to record the visited nodes, here Array is used as the stack for storage.
  2. There are three states of a node: not accessed, accessed, and branch accessed.

For example, as shown in the animation, blue corresponds to no access, that is, the initial state of the tree; yellow represents that the node has been accessed and put into the stack; gray corresponds to that both left and right branches have been accessed, so how to store it? To save the access state of the left and right branches, it is used together with the node here tuple Save in stack:

(node:TreeNode, isCheckLeft:Bool, isCheckRight:Bool)

The complete non recursive implementation is as follows:

func loopDfsTree(_ root: TreeNode?, _ dst: Int) -> TreeNode? {
    if (root == nil) {
        return nil
    }
    var checkNodes = Array<(node:TreeNode, isCheckLeft:Bool, isCheckRight:Bool)>()
    checkNodes.append((root!, false, false))
    while checkNodes.last != nil {
        var nodeInfo = (checkNodes.popLast())!
        let dstNode = nodeInfo.node

        if (dstNode.val == dst) {
            return dstNode
        }

        if (dstNode.left != nil && nodeInfo.isCheckLeft == false) {
            nodeInfo.isCheckLeft = true
            checkNodes.append(nodeInfo)
            checkNodes.append(((dstNode.left)!, false, false))
        } else if (dstNode.right != nil && nodeInfo.isCheckRight == false) {
            nodeInfo.isCheckRight = true
            checkNodes.append(nodeInfo)
            checkNodes.append(((dstNode.right)!, false, false))
        } else {

        }
    }
    
    return nil
}

Thinking questions

Compared with the recursive and non recursive implementation of DFS, why does the recursive implementation not need to save the state information of nodes? You are welcome to leave a message. I believe that knowledge after deep thinking will last forever.

Posted by mrobertson on Sun, 01 Dec 2019 12:19:07 -0800