iOS数据结构与算法实战 二叉树总结篇

树的基本概念篇

前言

因为咱们后面讲的一些结构有不少是树结构实现的好比堆,而后基于堆能够实现优先级队列,有界优先级队列等,因此咱们先讲述树结构,咱们可能常见到的是二叉树,可是还有一些其余的树的概念:好比二叉搜索树,AVL树,红黑树,B树,决策树等。以便于在特定场景下使用。node

树的一些应用场景

  • CFBinaryHeap 这个类在iOS中你可能会见到,这是一个二叉搜索算法实现的一个二叉堆,后面的priority queues这个结构就是用这个二叉堆实现的。还能够实现二叉搜索树。对高效率的搜索和排序有帮助。
  • iOS 中视图的层级结构就是一个很形象的树。以下图所示:添加顺序是A,B,C。先添加的在数组中的索引小。

ios View Tree

hit-test 逻辑:此方法经过hitTest:withEvent:从最后到第一个向其每一个子视图发送消息来遍历接收者的子树,直到其中一个返回非nil值。ios

代码:git

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    if (!self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01) {
        return nil;
    }
    if ([self pointInside:point withEvent:event]) {
        for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
            CGPoint convertedPoint = [subview convertPoint:point fromView:self];
            UIView *hitTestView = [subview hitTest:convertedPoint withEvent:event];
            if (hitTestView) {
                return hitTestView;
            }
        }
        return self;
    }
    return nil;
}

复制代码

采用reverse pre-order depth-first traversal algorithm遍历。首先访问根节点,而后从较高到较低的索引遍历其子树,这样作是为了快速遍历到咱们须要的节点,试想若是从低到高遍历这个View,层级不少的状况下岂不是要遍历不少节点。以下图所示:github

hitTest 顺序

好比“View A.2”和“View B.1”都是重叠的。但因为“View B”的子视图索引高于“View A”,所以“View B”及其子视图呈如今“View A”及其子视图上方。所以,当用户的手指在与“视图A.2”重叠的区域中触摸“视图B.1”时,应经过命中测试返回“视图B.1”。算法

一个小提示打印当前View下全部子View采用了递归遍历并打印,此刻看出来算法的重要性了吧。数据库

- (void)listSubviewsOfView:(UIView *)view {
    
    NSArray *subviews = [view subviews];
    
    if ([subviews count] == 0) return;
    
    for (UIView *subview in subviews)
    {
        
        NSLog(@"%@", subview);
        
        [self listSubviewsOfView:subview];
    }
}
复制代码
  • 其余好比人工智能下国际象棋采用决策树来解决。
  • 数据库中咱们须要高效的访问,插入删除等操做。为了下降磁盘IO操做开销,就用到了B树。
  • 用二叉树表示数学表达式咱们叫作表达式树。还记得以前咱们用栈结构结合后缀表达式来计算数学表达式吗?其实下图能够经过前序中序后序遍历方式获得先后中不一样的表达式。固然后缀表达式适合计算表达式,由于它很容易经过栈结构来计算。
    表达式树

上图后序遍历获得后缀表达式:( ((70 10 - )32 / ) (24 13 + ) X )数组

  • 堆排序,咱们利用二叉堆来实现堆排序,堆又是二叉树来实现的,近似于彻底二叉树的结构。
  • 霍夫曼编码是数据编码的的一种算法,用于JPEG和zip等压缩图像或者文件。该方法利用霍夫曼树来压缩一组数据,霍夫曼树是一颗二叉树。

二叉树介绍篇

一张图来描述Binary Tree

tree Des

二叉树的节点最大分支度是2,也说明每一个节点最多拥有2个子节点,范围是[0-2]。bash

Binary Tree的几个常见类型

  • A degenerate (or pathological) tree。(树的每一个节点只有一个子节点或者是右孩子或者是左孩子,这时候这个树就和链表性能差很少了。)

BSType1

  • Full Binary Tree (树的任何一个节点都有0或者2个孩子节点。或者这样定义树的任何一个非叶子节点都有两个孩子节点)

fullTreeType1

fullTreeType2

fullTreeType3

  • Complete Binary Tree(可能除了树的最后一层其它层级的每一个节点都有左右孩子节点,最后一层要么是满的要么节点都靠左边)
    fullTreeType1
    competitionType
  • Perfect Binary Tree (它是一个这样的二叉树,他全部的非叶子节点都有左右子节点,而且全部的叶子节点都在同一层级)

fullTreeType1
Perfect Binary Tree1

和Binary Tree有关的一些公式

  • 节点数和二叉树树Height的关系,假如h是树的Height,n是树节点个数。那么Min Nodes(n = h+1),Max Nodes(2h+1-1)。看下图例子,很容易推导出Min Nodes(n = h+1)。
    H NumbersN 1
    H NumbersN 2

下面咱们推导下Max Nodes。上图第三种状况h = 3,Max Nodes = 1 +2 + 22+ 23 = 15,也就是Max Nodes = 1 +2 + 22+ 23 + ....+ 2h= ,也就是等比数列求和,以下图:数据结构

等比数列
等比数列1
代入求和 Max Nodes = 1 +2 + 2 2+ 2 3 + ....+ 2 h=2 h+1-1

等比数列求和能够参考以下连接: zh.wikipedia.org/wiki/等比数列ide

反过来能够很容易推导出Min Height (h = Log2(n+1)-1),Max Height(h = n-1)。

  • 若是是full binary tree那么节点数和树Height的关系又是什么呢? 推导过程能够参考上面的步骤,Min Nodes(n = 2h+1),Max Nodes(2h+1-1),反过来能够很容易推导出Min Height (h = Log2(n+1)-1),Max Height(h = \frac{n-1}{2})。

  • 第i层至多拥有2i-1个节点,最少有1个节点。从下图能够很容易看出来,

    第I层个数

  • 度为0的节点数n1和度为2节点数n2的关系。n1 = n2 + 1。看下图

    deg(n)关系

二叉树的存储方式

  • Array Representation
  • Linked Representation

Array Representation

二叉树能够被以广度优先的顺序做为隐式数据结构存储在数组中。注意的是若是这个二叉树是complete binary tree,这些不会浪费空间,可是若是对于A degenerate (or pathological) tree这种高度很大的树就很浪费空间,能够参考后面根据这个存储方式判断这个树是否是complete binary tree的介绍。这种存储方法一般也用在binary heaps。

Array表示法

举例:找E的父节点,E的索引是5,那么Parent = i/2 = 5/2 = 2.5,向下取整就是2,对应的就是B。反之假如找A的左右孩子,A的索引是1,那么左孩子索引就是2对应B,右孩子索引就是3对应C。

注意:Parent的索引若是有存在小数状况是向下取整。

下面咱们看怎么根据这个表示方法判断是否是complete binary tree。

Array判断是否是彻底二叉树1
Array判断是否是彻底二叉树2
Array判断是否是彻底二叉树3
上三个图中1,2元素之间没有空白的空间是complete binary tree,图3元素之间有空白的空间说明不是complete binary tree。

Linked Representation

Linked表示法

@interface DSTreeNode : NSObject

@property (nonatomic, strong) NSObject   *object;
@property (nonatomic, strong) DSTreeNode *leftChild;
@property (nonatomic, strong) DSTreeNode *rightChild;
@property (nonatomic, strong) DSTreeNode *parent;
@property (nonatomic, assign) SEL         compareSelector;


- (void)printDescription;
//是不是左仍是结点
- (BOOL)isLeftChildOfParent;


@end
复制代码

这种存储二叉树方法浪费了很多内存,因为那些节点的左右指针(为null或者指向某些节点)。

二叉树的周游算法篇

二叉树的周游算法

  • 前序遍历:visit(node),preorder(left Subtree), preorder(right Subtree)。
  • 中序遍历:in-order(left Subtree),visit(node),in-order(right Subtree)。
  • 后序遍历:post-order(left Subtree),post-order(right Subtree),visit(node)。
  • 层级遍历:一层层访问每一个节点。

经过上述四种方式遍历二叉树的每一个节点。

练习周游算法的技巧 1

周游算法练习1

思路:通常咱们习惯 ,根节点-左节点-右节点,这样的模型,咱们就把例如上图A的左子树当作一个块,相似一个大节点用括号圈起来,一样的右子树也这样作。而后每一个块里作前中后遍历。

  • 前序遍历。A,(B,D,E),(C,F,G)。获得结果是 A,B,D,E,C,F,G 。

  • 中序遍历。(D,B,E),A,(F,C,G)。获得的结果是 D,B,E,A,F,C,G 。

  • 后序遍历。(D,E,B),(F,G,C),A。获得的结果是 D,E,B,F,G,C,A 。

  • 层级遍历。 A,B,C,D,E,F,G 。

练习周游算法的技巧 2

周游算法练习2

前序遍历思路:每一个节点从左边画线一直到底部这个线,而后按照从左到右的顺序读取节点。 结果是:A,B,D,E,C,F,G 。

周游算法练习3

中序遍历思路:每一个节点从中间画线到底部这个线,而后按照从左到右的顺序读取节点。 结果是 D,B,E,A,F,C,G 。

周游算法练习4

后序遍历思路:每一个节点从右边画线到底部这条线,而后从左到右的顺序读取节点。 结果是 D,E,B,F,G,C,A 。

练习周游算法的技巧 3

周游算法练习5

前序遍历思路:从每一个节点左边画出一个线,而后从根结点开始转一圈,通过每一个节点和树的分支,包裹这个树。通过这些短线的顺序就是结果。A,B,D,E,C,F,G 。

周游算法练习6

中序遍历思路:从每一个节点底部边画出一个线,而后从根结点开始转一圈,通过每一个节点和树的分支,包裹这个树。通过这些短线的顺序就是结果。D,B,E,A,F,C,G 。

周游算法练习7

后序遍历思路:从每一个节点右边画出一个线,而后从根结点开始转一圈,通过每一个节点和树的分支,包裹这个树。通过这些短线的顺序就是结果。D,E,B,F,G,C,A 。

周游算法延伸

  • 前序遍历,中序遍历,后续遍历的思想是按照深度优先的顺序遍历的。层级遍历的思想是按照广度优先的顺序遍历的。
  • 因为要遍历树的每一个节点所以时间复杂度是O(n)。
  • 广度优先遍历思想的层级遍历须要的额外的空间是O(w),w是这个二叉树的最大的宽,好比Perfect Binary Tree这种状况下最大节点在最后一层,第i层至多拥有2i-1个节点,所以须要额外空间O(Ceil(n/2));深度优先遍历思想的其余三种方式须要额外空间是O(h),这个h是二叉树的最大高度,好比一个平衡树h是Log2(n) ,可是对于极不平衡的左倾斜或者右倾斜树来讲h就是n 。因此在最坏的状况下,二者所需的额外空间是O(n)。但最坏的状况发生在不一样类型的树木上,所以针对不一样种类不一样性质的树须要的额外空间有不尽相同。从以上能够明显看出,当树更平衡时,广度优先遍历思想的层级遍历所需的额外空间可能更多,而且当树不太平衡时,深度优先遍历思想的其余三种遍历方式的额外空间可能更多。

这节主要介绍二叉树的代码实现,咱们讲述Linked Representation的实现,主要包含下面几个操做。

  • 构建
  • 插入
  • 查找
  • 前序,中序,后续,层级遍历

二叉树的实现篇

节点类

从上图能够看出,每一个节点除了自己之外,还得有一个父子以及左右孩子节点信息。所以须要一个节点类。主要代码实现以下:

@interface DSTreeNode : NSObject

@property (nonatomic, strong) NSObject   *object;
@property (nonatomic, strong) DSTreeNode *leftChild;
@property (nonatomic, strong) DSTreeNode *rightChild;
@property (nonatomic, strong) DSTreeNode *parent;
@property (nonatomic, assign) SEL         compareSelector;


- (void)printDescription;
//是不是左仍是结点
- (BOOL)isLeftChildOfParent;


@end
复制代码

构建

对于二叉树的建立咱们初始化一个根节点的方式建立,以下代码实现:

- (instancetype)initWithObject:(NSObject *)object
{
    if (self = [super init]) {
        _root            = [[DSTreeNode alloc] init];
        self.root.object = object;
    }
    
    return self;
}
复制代码

插入

以插入节点的方式构建整个二叉树以下代码:

//插入结点
- (BOOL)insertNode:(NSObject *)node parent:(NSObject *)parent isLeftChild:(BOOL)value
{
    DSTreeNode *treeNode = [[DSTreeNode alloc] init];
    treeNode.object = node;
    DSTreeNode *parentNode = [self find:parent];
    //1
    if (value == true && parentNode.leftChild == nil) {
        //2
        treeNode.parent = parentNode;
        //3
        parentNode.leftChild = treeNode;
    }
    //4
    else if (parentNode.rightChild == nil) {
        treeNode.parent = parentNode;
        parentNode.rightChild = treeNode;
    }
    //5
    else {
        NSAssert(parentNode.leftChild != nil || parentNode.rightChild != nil, @"Can't insert into parent node!");
        return false;
    }
    return true;
}

复制代码

代码解释:

  1. 若是插入的位置是当前节点的左孩子而且左孩子结点不存在能够插入。
  2. 被插入的节点的parent指针指向当前节点,此处是必须的,否则这个树分支就断了,也就不能构成完整的树。
  3. 当前节点左孩子指针指向被插入的节点,此处是必须的,和第二步缘由同样。
  4. 不然插入的是右孩子节点。
  5. 若是某个节点的左右孩子节点都存在则提示不能插入的信息。

查找

查找某个节点

- (DSTreeNode *)find:(NSObject *)object
{
    //1 
    DSQueue*queue = [[DSQueue alloc] init];
    [queue enqueue:self.root];
    DSTreeNode *node;
    //2
    while (![queue isEmpty]) {
        node = [queue dequeue];
        if ([node.object isEqualTo:object]) {
            return node;
        }
        if (node.leftChild) {
            [queue enqueue:node.leftChild];
        }
        if (node.rightChild) {
            [queue enqueue:node.rightChild];
        }
    }
    return nil;
}
复制代码
  • 利用队列先进先出特性遍历每一个结点
  • 注意这个遍历的顺序是层级遍历顺序

前序,中序,后续,层级遍历

层级遍历的思路和上述查找的思路相似。前中后序遍历的思路利用递归的思路实现,而后按照以前介绍二叉树遍历算法的思路就能够实现了。前序遍历的代码以下:

//若是当前根结点存在则前序遍历这个树
- (void)preOrderTraversal
{
    if (self.root) {
        [DSBinaryTree preOrderTraversalRecursive:self.root];
    }
}

//递归的遍历并打印树 顺序是根 左 右
+ (void)preOrderTraversalRecursive:(DSTreeNode *)node
{
    if (node) {
        NSLog(@"%@",node.object);
        [DSBinaryTree preOrderTraversalRecursive:node.leftChild];
        [DSBinaryTree preOrderTraversalRecursive:node.rightChild];
    }
}
复制代码

二叉树算法实战篇

题目大意

Given a binary tree, return all root-to-leaf paths.

For example, given the following binary tree:


   1
 /   \
2     3
 \
  5
  
All root-to-leaf paths are:

["1->2->5", "1->3"]
复制代码

灵感思路

给咱们一个二叉树,让咱们返回全部根到叶节点的路径。咱们能够采用递归的思路,不停的DFS到叶结点,若是遇到叶结点的时候,那么此时一条完整的路径已经造成,咱们加上当前的叶结点后变成的完整路径放到数组中。

须要注意的是对空节点的判断,以及递归函数回溯时候对一些对象的影响。

主要代码

- (void)printPathsRecurTreeNode:(DSTreeNode *)treeNode path:(NSString *)path results:(NSMutableArray <NSString *>*)results
{

    //1
    if (treeNode == nil) {
        return;
    }
    //2
    if (treeNode.leftChild == nil && treeNode.rightChild == nil)
    {
        NSString *resultsStr = [NSString stringWithFormat:@"%@%@",path,treeNode.object];
        [results addObject:resultsStr];

    }
    else
    {
        //3
        if (treeNode.leftChild != nil)
        {
            NSString *resultsStr = [NSString stringWithFormat:@"%@%@",path,[NSString stringWithFormat:@"%@->",treeNode.object]];

            [self printPathsRecurTreeNode:treeNode.leftChild path:resultsStr results:results];
        }
        //4
        if (treeNode.rightChild != nil )
        {
            NSString *resultsStr = [NSString stringWithFormat:@"%@%@",path,[NSString stringWithFormat:@"%@->",treeNode.object]];
            [self printPathsRecurTreeNode:treeNode.rightChild path:resultsStr results:results];
        }
    }
    

}
复制代码

代码解释

  1. 若是节点是空则返回。
  2. 若是当前节点是叶子节点则把这个完整路径加到数组里。
  3. 若是当前节点存在左孩子节点,则继续DFS直到叶子节点。
  4. 若是当前节点存在右孩子节点,则继续DFS直到叶子节点。

GitHubDemo:

github.com/renmoqiqi/1…

github.com/renmoqiqi/1…

参考连接:

smnh.me/hit-testing…

zh.wikipedia.org/wiki/等比数列

相关文章
相关标签/搜索