Swift实践翻转二叉树

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      
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 198,932评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,554评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 145,894评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,442评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,347评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,899评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,325评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,980评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,196评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,163评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,085评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,826评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,389评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,501评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,753评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,171评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,616评论 2 339

推荐阅读更多精彩内容

  • 树的概述 树是一种非常常用的数据结构,树与前面介绍的线性表,栈,队列等线性结构不同,树是一种非线性结构 1.树的定...
    Jack921阅读 4,414评论 1 31
  • 本文转自 http://www.cnblogs.com/manji/p/4903990.html二叉树-****你...
    doublej_yjj阅读 675评论 0 8
  • 1.什么是二叉树? 在计算机科学中,二叉树是每个节点最多有两个子树的树结构。通常子树被称作“左子树”和“右子树”,...
    zcaaron阅读 1,214评论 2 15
  • 去年二叉树算法的事情闹的沸沸扬扬,起因是Homebrew 的作者 @Max Howell 在 twitter 上发...
    Masazumi柒阅读 1,566评论 0 8
  • 2017.1.16 武功山 前年,一行人计划四处漂流,却辗转多无定数。人生历...
    南方有南初阅读 516评论 0 5