美文网首页
Swift实践翻转二叉树

Swift实践翻转二叉树

作者: bo_song | 来源:发表于2017-06-15 11:28 被阅读0次

    HomeBrew作者,天才程序员Max Howell去Google面试),结果却因不会在白板上翻转二叉树被Google粗鲁地拒绝了,舆论甚是哗然,其中缘由知乎上也讨论的热火朝天。谁料,始作俑者翻转二叉树。作为程序猿的我,从未翻过二叉树。最近闲来无事,闲暇之余,想及至此,就顺手用Swift翻转了二叉树。

    1.需求如下图

    image.png

    2.二叉树定义

    先定义一个二叉树类Tree,主要定义树的三个必要属性,关键值key,左子树leftTree和右子树rightTree。

    class Tree {
        var key: Int   //节点上显示的关键值
        var leftTree: Tree?  //左子树
        var rightTree: Tree? //右子树
        
        //初始化方法
        init(key: Int) {
            self.key = key
        }
        
    }
    

    3. 解决方案

    3.1 递归翻转

    用递归的方式翻转二叉树,主要分为两步:

    func invertBinaryTreeWithRecursive(root: Tree) {
        //1
        let tempT = root.leftTree
        root.leftTree = root.rightTree
        root.rightTree = tempT
    
        //2
        if let leftT = root.leftTree {
            //递归调用
            invertBinaryTreeWithRecursive(leftT)
        }
        if let rightT = root.rightTree {
            invertBinaryTreeWithRecursive(rightT)
        }
    }
    

    第1步:翻转本树的左子树和右子树,需要借助一个零时变量tempT;
    第2步:判断左右树是否存在,如存在,则递归调用进行翻转。
    递归翻转二叉树每个节点只访问1次,在每个节点上的耗时是常数,因此总耗时Θ(n),n是树的节点树。

    3.2 非递归翻转

    其实也可以不用递归翻转二叉树,分为3步:

    func invertBinaryTreeWithoutRecursive(root: Tree) {
        //1
        var arr = [Tree]()
        arr.append(root)
        //2
        while !arr.isEmpty {
            let node = arr.removeFirst()
            let tempT = node.leftTree
            node.leftTree = node.rightTree
            node.rightTree = tempT
            
            //3
            if let leftT = node.leftTree {
                arr.append(leftT)
            }
            if let rightT = node.rightTree {
                arr.append(rightT)
            }
        }
    }
    

    第1步:初始化一个数组,用来存储尚未翻转的树,第一个尚未翻转的树即是根树;
    第2步:判断是否仍有树未翻转,如果有,从数组中取出第一个树,并将它从数组中删除,然后对这个树翻转左子树和右子树,需要借助一个零时变量tempT;
    第3步:判断被翻的树左右子树是否存在,如存在,将它依次存储在数组的最后面。
    这种翻转二叉树的方式是从左到右翻完同一层的树节点,再从左到右翻下一层的树节点,直至翻完,其耗时也是Θ(n)。

    4. 遍历二叉树

    为了遍历二叉树,输出有树形状的结果,先定义一个辅助函数,计算树的高度.

    4.1 计算树的高度
    //calculate the height of a tree 
    func heightOfTree(root: Tree) -> Int {
        //1
        var leftTreeHeight = 0
        var rightTreeHeight = 0
        //2
        if let leftT = root.leftTree {
            leftTreeHeight = 1 + heightOfTree(leftT)
        }
        
        if let rightT = root.rightTree {
            rightTreeHeight = 1 + heightOfTree(rightT)
        }
        //3
        return max(leftTreeHeight, rightTreeHeight)
    }
    

    计算树的高度也必须用到递归。
    第1步:初始化两个高度变量,分别代表左右子树高度,缺省值为0,即左右子树均为nil
    时的值;
    第2步:判断左右子树是否存在,如存在递归求左右子树的高度;
    第3步:返回左右子树高度的最大值,这一步是递归终止条件,是整个递归的精髓。
    因为遍历了所有树节点,因此总耗时Θ(n)。

    4.2 遍历

    为输出的结果具有树的形状,树的遍历输出要费点脑筋,分4步:

    //print out binary tree 
    func tranverseTree(root: Tree) {
        //1
        let height = heightOfTree(root)
        var trees = [(tree: root, number: 1, height: height, depth: 0, placeHolderTree: false)]
        let placeHolderTree = Tree(key: 0)
        
        //2
        func appendTree(tree: Tree?, node: (tree: Tree, number: Int, height: Int, depth: Int, placeHolderTree: Bool)) {
            let number: Int
            if let last = trees.last {
                number = last.height == node.height - 1 ? last.number + 1 : 1
            } else {
                number = 1
            }
            if let t = tree {
                trees.append((tree: t,
                    number: number,
                    height: node.height - 1,
                    depth: node.depth + 1,
                    placeHolderTree: false))
            } else {
                trees.append((tree: placeHolderTree,
                    number: number,
                    height: node.height - 1,
                    depth: node.depth + 1,
                    placeHolderTree: true))
            }
        }
        
        //3
        while trees[0].height >= 0 {
            let node = trees.removeFirst()
            let space: Int
            if node.number == 1 {
                space = Int(pow(2.0, Double(node.height)))
            } else {
                space = Int(pow(2.0, Double(node.height + 1)))
            }
            for _ in 1..<space {
                print(" ", terminator: "")
            }
            if node.placeHolderTree {
                print(" ", terminator: "")
            } else {
                print(node.tree.key, terminator: "")
            }
            if node.number == Int(pow(2.0, Double(node.depth))) {
                print("")
            }
            
            //4
            appendTree(node.tree.leftTree, node: node)
            appendTree(node.tree.rightTree, node: node)
    
        }
        
    }
    

    整个遍历的思路与invertBinaryTreeWithoutRecursive完全一致。
    第1步:初始化一个数组,存储元组类型(tree: Tree, number: Int, height: Int, depth: Int, placeHolderTree: Bool);这个元组中tree表示一个树节点;number表示这个树节点在其所在层的位置,第一个number为1,第二个number为2,依次类推;height表示这个树节点的高度;depth表示这个树节点的深度;placeHolderTree表示这个树节点是否为占位树。为便于后续遍历,当树为非完全二叉树时,空缺的树节点全部用占位树填补,因此才有placeHolderTree
    元组元素,以标示占位树;
    第2步:这是一个utility函数,在第4步时调用;
    第3步:判断树是否到底,当没到底时,从数组中取出并删除第一个节点,然后计算并打印这个节点相应的空格数,然后打印节点key值,如果这个节点是同一层级最后一个节点,打印换行;
    第4步:将这个节点的左右子树连同相应信息存储到数组中,这时调用了第2步中的utility函数appendTree:node:,appendTree:node:
    中对传入的树节点进行了判断,当为空时,自动填补占位树。
    此函数遍历了所有树节点,因此总耗时Θ(n)。

    4.3 测试

    生成二叉树,并进行测试:

    let tree = Tree(key: 1)
    tree.leftTree = Tree(key: 2)
    tree.rightTree = Tree(key: 3)
    tree.rightTree?.leftTree = Tree(key: 4)
    print("------tree before inverting-------")
    tranverseTree(tree)
    print("------tree inverted-------")
    invertBinaryTreeWithRecursive(tree)
    tranverseTree(tree)
    

    输出结果为:

    ------tree before inverting-------
           1
       2       3
             4            
    ------tree inverted-------
           1
       3       2
          4      
    

    相关文章

      网友评论

          本文标题:Swift实践翻转二叉树

          本文链接:https://www.haomeiwen.com/subject/jkmyqxtx.html