Swift 算法实战二(二叉树、二分搜索)

前言

这是继Swift 算法实战一(集合,字典,链表,栈,队列等算法)之后的又一篇文章,算是所学知识总结,也算是之后找工作的敲门砖。笔者这月月底要离职,即使目前这家公司给我加薪的条件,也不再想继续留下了。

这篇文章主要涉及到二叉树、二分搜索等相关,具体请看文章内容。

一、二叉树

不得不承认实际开发中很少用到二叉树相关的知识,但是面试过程中却被问道的不少。

1.1 二叉树的认识

1.1.1 概念

二叉树是 n (n >= 0)个结构的有限集合,改集合或者为空集(称为空二叉树),或者有一个根节点和两棵互不相交的、分别称为根节点的左子树和右子树的二叉树组成。

public class ZWTreeNode {
  public var val: Int
  public var left: ZWTreeNode?
  public var right: ZWTreeNode?
  public init(val: Int) {
    self.val = val
  }
}
1.1.2 性质
  • 在二叉树的第 i 层上有至多 2^( i-1) 个结点 (i >= 1).
  • 深度为 k 的二叉树至多有 2^k - 1 个节点(k >= 1).
  • 具有n个结点的完全二叉树的深度为 [log (2) n] + 1 ([x] 表示不大于x的最大整数) .

1.2 判断是否为二叉查找树

二叉查找树的特点是:左子树节点值都小于根节点的值,而右子树节点值大于根节点值。那么问题就来了,给你一个二叉树,怎么通过最简单的方式,判断是否是二叉查找树。

对于解答上面这个问题,我们至少需要考虑到这两种情况。首先,二叉树但从定义上就能看出和递归有一定的关系,所以通常解决二叉树的问题,第一反应就是要和递归绑定在一起;其次,二叉树节点有为空的情况,所以一般针对空二叉树这种边界条件要做一些额外处理;

具体判断实现如下代码,代码中包含详细注释,以及调用实例展示。

    //根据根节点做判断
    func isValidTree(root:ZWTreeNode?) -> Bool{
        return self.helper(node: root, min: nil, max: nil)
    }
    
    func helper(node:ZWTreeNode?,min:Int?,max:Int?) -> Bool {
        //对于空节点的处理
        guard let node = node else { return true }
        //右子树要求大于根节点
        if let min = min ,node.val <= min {
            return false
        }
        //左节点要小于根节点
        if let max = max,node.val >= max {
            return false
        }
        //根据左右节点同时做判断
        return helper(node:node.left,min:min,max:node.val) && helper(node:node.right,min:node.val,max:max)
    }
//方法调用
let leftNode = ZWTreeNode(val: 1)
let rightNode = ZWTreeNode(val: 3)
let root = ZWTreeNode(val: 2)
root.left = leftNode
root.right = rightNode
print(isValidTree(root: root))//结果为:true

1.3 二叉树的深度

计算二叉树的深度,同样是只要知道根节点即可,同样也要借助递归实现。

//实现
func treeDepth(root:ZWTreeNode?) -> Int {
    guard let root = root else {
        return 0
    }
    return max(treeDepth(root:root.left), treeDepth(root: root.right)) + 1
 }
//调用形式
let leftNode = ZWTreeNode(val: 1)
let rightNode = ZWTreeNode(val: 3)
let root = ZWTreeNode(val: 2)
root.left = leftNode
root.right = rightNode
print(treeDepth(root: root))//结果为:2

1.4 二叉树的遍历

1.4.1 三种遍历方式

二叉树的遍历多种多样,,三种常用的遍历: 前序遍历、中序遍历、后续遍历(主要依据根节点遍历的先后顺序而定义的)。

前序遍历首先访问根结点,然后前序遍历左子树,再前序遍历右子树。


前序遍历

中序遍历首先遍历左子树,然后访问根结点,最后遍历右子树。在遍历左、右子树时,仍然先遍历左子树,再访问根结点,最后遍历右子树。


中序遍历

后序遍历首先遍历左子树,然后遍历右子树,最后访问根结点。在遍历左、右子树时,仍然先遍历左子树,然后遍历右子树,最后遍历根结点。


后序遍历
1.4.2 前序遍历实现

这里主要看一下如何借助栈来实现前序遍历。实际上其他几种遍历方式笔者是没有怎么看的。

代码逻辑的思路就是先遍历节点的左子节点,当左子节点为空的时候,就进行pop操作,获取父节点,根据父节点获取右子节点。

    func formerSequenceTraversal(root:ZWTreeNode?) -> [Int] {
        var arr = [Int]()
        var stack = [ZWTreeNode]()
        var node = root
        //代码逻辑的思路就是先遍历节点的左子节点,当左子节点为空的时候,就进行pop操作,获取父节点,根据父节点获取右子节点。
        while stack.isEmpty == false || node != nil{
            if node != nil {
                arr.append(node!.val)
                stack.append(node!)//push操作,进入子节点
                node = node?.left
            }else{
                node = stack.removeLast().right//pop操作,返回上一节点
            }
        }
        return arr
    }

前序遍历调用形式。

        let subLeftLeftNode = ZWTreeNode(val: 4)
        let subLeftRightNode = ZWTreeNode(val: 5)
        let leftNode = ZWTreeNode(val: 1)
        leftNode.left = subLeftLeftNode
        leftNode.right = subLeftRightNode
        
        let rightNode = ZWTreeNode(val: 3)
        let root = ZWTreeNode(val: 2)
        root.left = leftNode
        root.right = rightNode
        print(formerSequenceTraversal(root: root))//打印结果为:[2, 1, 4, 5, 3]

二、二分搜索

2.1 概念

一个有序数组中,如果查找某个特定的元素。可以先从中间的元素开始寻找,如果中间元素是要找的元素,直接返回;如果中间元素小于目标元素,则目标原始在大于中间元素的那一边;如果中间元素大于目标元素,则目标元素在小于中间元素的那一边。按照上面的三种情况无限的循环下去,最终就能找到目标元素。算法的时间复杂度为O(logn)。

2.2 简单实现

接下来看看如果在一个 Int 型有升序数组中,检测是否存在给定的目标值。代码如下。

     //实现代码
    func binarySearch(arr: [Int], target: Int) -> Bool {
        var left = 0
        var mid = 0
        var right = arr.count - 1
        while left <= right {
            mid = (right - left) / 2 + left
            if arr[mid] == target {
                return true
            } else if arr[mid] < target {
                left = mid + 1
            } else {
                right = mid - 1
            }
        }
        return false
    }
//调用形式
let arr = [1,2,3,4,5,6,7,8]
 print(binarySearch(arr: arr, target: 2))//打印结果为:true

总的来说代码实现起来比较简单,但是要注意到一点绝对不写写成mid = (right + left) / 2,否则可能发生崩溃,因为当搜索结果在右边范围的时候可能出现越界的情况,最正确的写法应当是mid = (right - left) / 2 + left。如果想获取到目标元素的下表也很简单,只要简单改写一下即可。如下代码,其中如果返回值为 -1 ,则表示不存在目标元素。

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

推荐阅读更多精彩内容

  • 树的概述 树是一种非常常用的数据结构,树与前面介绍的线性表,栈,队列等线性结构不同,树是一种非线性结构 1.树的定...
    Jack921阅读 4,373评论 1 31
  • 数据结构和算法--二叉树的实现 几种二叉树 1、二叉树 和普通的树相比,二叉树有如下特点: 每个结点最多只有两棵子...
    sunhaiyu阅读 6,341评论 0 14
  • 四、树与二叉树 1. 二叉树的顺序存储结构 二叉树的顺序存储就是用数组存储二叉树。二叉树的每个结点在顺序存储中都有...
    MinoyJet阅读 1,453评论 0 7
  • B树的定义 一棵m阶的B树满足下列条件: 树中每个结点至多有m个孩子。 除根结点和叶子结点外,其它每个结点至少有m...
    文档随手记阅读 13,012评论 0 25
  • 目录 0.树0.1 一般树的定义0.2 二叉树的定义 1.查找树ADT 2.查找树的实现2.1 二叉查找树2.2 ...
    王侦阅读 6,991评论 0 3