数据结构系列:Objective-C实现二叉树

二叉树

本篇是我在学习二叉树时做的总结,属于面向我这种小白的文章

摘自《维基百科》

计算机科学中,二叉树(英语:Binary tree)是每个节点最多只有两个分支(即不存在分支度大于2的节点)的树结构。通常分支被称作“左子树”或“右子树”。二叉树的分支具有左右次序,不能随意颠倒。

与普通树不同,普通树的节点个数至少为1,而二叉树的节点个数可以为0;普通树节点的最大分支度没有限制,而二叉树节点的最大分支度为2;普通树的节点无左、右次序之分,而二叉树的节点有左、右次序之分。

摘自《百度百科》

完全二叉树:对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。

满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树。

废话不多说,直接上源码。下面是Objective-C实现的核心代码以及调用源码。

@implementation  BinaryTree

/**
 添加节点

 @param item 节点根元素
 */
- (void)add:(NSInteger)item {
    
    Node *node = [[Node alloc] initWithItem: item];
    
    if (self.root == nil)
    {
        self.root =  node;
        
        return;
    }
    
    NSMutableArray *queue = [NSMutableArray array];
    
    [queue addObject: self.root];
    
    while (queue.count) {
        Node *curNode = queue.firstObject;

        [queue removeObjectAtIndex: 0];
        
        if (curNode.leftChild == nil)
        {
            curNode.leftChild = node;
            return;
        }
        else {
            [queue addObject: curNode.leftChild];
        }
        
        if (curNode.rightChild == nil)
        {
            curNode.rightChild = node;
            return;
        }
        else {
            [queue addObject: curNode.rightChild];
        }
    }
}

/**
 广度遍历
 */
- (void)breadthTraversal {
    if (self.root == nil) return;
    
    NSMutableArray *queue = [NSMutableArray array];
    
    [queue addObject: self.root];
    
    while (queue.count) {
        
        Node *curNode = queue.firstObject;
        [queue removeObjectAtIndex: 0];
        
        NSLog(@"%ld", curNode.element);
        
        if (curNode.leftChild != nil) [queue addObject: curNode.leftChild];
        
        if (curNode.rightChild != nil) [queue addObject: curNode.rightChild];
    }
}

/**
 深度遍历:前序遍历(先序遍历)

 @param node 遍历开始节点
 */
- (void)preorderTraversal:(Node *)node {
    if (node == nil) return;
    NSLog(@"%ld", node.element);
    [self preorderTraversal: node.leftChild];
    [self preorderTraversal: node.rightChild];
}

/**
 深度遍历:中序遍历

 @param node 遍历开始节点
 */
- (void)inorderTraversal:(Node *)node {
    if (node == nil) return;
    [self inorderTraversal: node.leftChild];
    NSLog(@"%ld", node.element);
    [self inorderTraversal: node.rightChild];
}

/**
 深度遍历:后序遍历

 @param node 遍历开始节点
 */
- (void)postorderTraversal:(Node *)node {
    if (node == nil) return;
    [self postorderTraversal: node.leftChild];
    [self postorderTraversal: node.rightChild];
    NSLog(@"%ld", node.element);
}

@end

实际使用案例


- (void)viewDidLoad {
    [super viewDidLoad];
    
    BinaryTree *tree = [[BinaryTree alloc] init];
    
    for (int i = 0; i < 10; ++i)
    {
        [tree add: i];
    }
    
    [tree breadthTraversal];              // 广度遍历:0 1 2 3 4 5 6 7 8 9
    
    [tree preorderTraversal: tree.root];  // 前序遍历:0 1 3 7 8 4 9 2 5 6

    [tree inorderTraversal: tree.root];   // 中序遍历:7 3 8 1 9 4 0 5 2 6

    [tree postorderTraversal: tree.root]; // 后序遍历:7 8 3 9 4 1 5 6 2 0
}


寡人的思路

由于我们是为了简单实现二叉树,所以节点的元素类型用最基础的NSInteger

添加方法:- (void)add:(NSInteger)item


添加的原则就是要让整个二叉树朝着满二叉树发展。

方法中的数组 queue 定义成可变数组,我们把它当做队列使用,添加和删除待处理的节点将按照先进先出的原则。在每次想要添加一个节点之前,都需要先把二叉树的根节点添加到数组的最前面,方便从头查找。

每次都从数组中取出第一个元素,然后将这个元素从数组中删除,即处理一个就出队列一个。如果取出的这个节点的左子树不为空,说明这个节点有左子树,那就把其左子树放到待处理队列(queue数组)中。如果这个节点左子树为空,说明这个节点没有左子树,那就把要添加的这个 node 赋值上去,添加动作至此结束。右子树同理。

广度遍历:- (void)breadthTraversal

逐层打印元素的值

前序遍历:- (void)preorderTraversal:(Node *)node

先看一下前序遍历的原理图:


前序遍历的原则是“根→左→右”。要把整个二叉树看做一个整体,先去处理根,就是打印根元素。然后处理整个二叉树的“左”,等到整个左子树全部处理打印完,再去处理整个二叉树的“右”。

处理整个二叉树的“左”和整个二叉树的“右”的时候依然要按照“根→左→右”的原则。这时就要把整个二叉树的“左”和“右”分别重新看做一个整体。先处理这个整体的根,然后处理左子树,最后处理右子树。同理逐层向下。

图中的红色箭头就是先序遍历的处理顺序。
根据“根→左→右”的原则:

  • 整个二叉树的“根”是 节点:0第一个打印 0。这时“根”处理完,该处理“左”。

  • 整个二叉树的“左”是以 节点:1 为根的二叉树,包括 节点:3、4、7、8、9 。到了“左”这一部分,仍然要按照“根→左→右”的原则去处理。这部分的“根”是 节点:1第二个打印 1

    • “根”处理完之后,要处理这部分的“左”,就是以 节点:3 为根的二叉树,包括 节点:7、8 。这部分还是按照“根→左→右”的原则,先处理这部分的“根”,也就是说第三个打印 3
    • “根”处理完之后,处理“左”。这个“左”就是 节点:7 ,到了 节点:7 这里就再无子树了,所以第四个打印 7之后就说明“左”处理完了。接下来应该处理“右”,同样这个“右”即 节点:8 也再无子树,在第五个打印 8之后就算处理完“右”。
    • 至此打印了 0、1、3、7、8 之后,我们刚刚处理好以 节点 3 为根的二叉树也就是以 节点 1 为根的二叉树的“左”。
    • 接下来就要处理以 节点 1 为根的二叉树的“右”,这个“右”是一个以 节点:4 为根的二叉树。先处理这个“右”的“根”,第六个打印 4,接下来处理以 节点: 4 为根的二叉树的“左”,第七个打印 9
    • 到这里我们打印了 0、1、3、7、8、4、9 ,我们处理完了以 节点: 4 为根的二叉树也就是以 节点: 1 为根的二叉树的“右”,那么以 节点: 1 为根的二叉树也已全部处理完。同时我们也已处理完以 节点: 0 为根的二叉树的“左”。接下来同样按照这个方式去处理以 节点: 1 为根的二叉树的“右”。
  • 整个二叉树的“右”是以 节点:2 为根的二叉树,包括 节点:5、6 。道理一样,就不再赘述了。

说实话,这么分析是真的累

中序遍历:- (void)inorderTraversal:(Node *)node

下面是中序遍历的原理图:

图中灰色箭头是用来帮助查找应该处理的“真凶”的幕后路线,红色箭头就是表面的查找路线。

中序遍历的原则是“左→根→右”,所以我们要先找到“左”。

先整体再局部。整体是以 节点:0 为根的二叉树,逐层向下查找“左”依次是:以 节点:1 为根的二叉树、以 节点:3 为根的二叉树、节点:7。所以到 节点:7 这里才真正找到我们要最先处理的“左”,即第一个打印 7

打印了 7 之后,我们就处理完了以 节点:3 为根的二叉树的“左”。接下来要处理它的“根”,所以第二个打印 3。然后要处理它的“右”,第三个打印 8

打印了 7、3、8 之后就处理完了以 节点:1 为根的二叉树的“左”。接下来无限循环的找下去。。。 我不想再继续下去了,快要迷糊了


后序遍历:- (void)postorderTraversal:(Node *)node

下面是后序遍历的原理图:

各位这个后序遍历我就不BB了,要不然显得我在凑字数了,虽然这段话也是在凑字数


Github完整源码

GCBinaryTree


参考资料

二叉树-维基百科,自由的百科全书
完全二叉树_百度百科
满二叉树_百度百科

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

推荐阅读更多精彩内容