二叉树必知必会-基础篇

前言:程序 = 数据结构 + 算法。本篇是二叉树基础,会介绍二叉树一些重要性质和概念,整理了前序、中序、后序遍历两种算法(递归和迭代)。本篇简书基于《大话数据结构》而写,非常推荐的入门书籍,如果能力较强可以直接去啃《数据结构和算法》。

一、学习二叉树的重要性

  • 工作面试时,经常出现二叉树相关算法
  • 二叉树是树核心和重点
  • 支持强大的搜索算法
  • 树、森林是一种非常复杂数据结构,但是树、森林可以转换成二叉树去处理(三者可以相互转换)

二、横扫基础知识

什么是二叉树?二叉树(Binary Tree)是 n (n >= 0)个节点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树的二叉树组成。这是官方定义,下面这棵树就是传说中的二叉树,不会出现超过二个分支。

二叉树示意图

特点:

  • 每个结点最多有两棵子树
  • 左子树和右子树是有顺序的不能任意颠倒,如下图就是五棵不同的二叉树。
二叉树特点

满二叉树:在一棵二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上,这样的二叉树为满二叉树。
这个定义看图就能明白,就是一棵非常完美的二叉树。

满二叉树

完全二叉树:对一棵具有 n 个结点的二叉树按层序编号,如果编号为 i (1<= i <= n)的结点与同样深度的满二叉树中编号 i 的结点在二叉树中位置完全相同,则这棵二叉树称为完全二叉树。

完全二叉树

这定义最好结合满二叉树图去理解,比如此图i=4或者i=10在满二叉树图中位置就是等于4和10。现在脑补一下,假设将此图i=5的左子树(也就是i=10这个结点),移动成i=7这个结点左子树,那么这棵树还是完全二叉树吗?答案肯定不是,因为满二叉树图i=7左子树对应的是i=14这个结点。

满二叉一定是一棵完全二叉树,但是完全二叉树不一定是满的。

作为两种特殊的二叉树,满二叉树和完全二叉树容易混淆,分不清也没关系,知道有这两棵树就OK了。

二叉树的五个性质:(前三个要求掌握)

  • 性质1:在二叉树的第 i 层上至多有 2^(i-1)个结点(i>=1)。

  • 性质2:深度为k的二叉树至多有 2^k - 1个结点(k>=1)。

  • 性质3:对任何一棵二叉树 T ,如果其终端结点数为 n0,度为2的结点数为n2,则n0=n2+1。

  • 性质4:具有n个结点的完全二叉树的深度为k的度为[log(2)n]+1([X]表示向下取整)。

  • 性质5:如果对一棵有n个结点的完全二叉树(其深度为[log(2)n]+1)的结点按层序编号,对任一结点 i(1<=i<=n)有:
    ①如果i=1,则结点 i 是二叉树的根,无双亲;如果i>1,则其双亲是结点[i/2]向下取整。
    ②如果2i>n,则结点i无左孩子(结点i为叶子结点);否则其左孩子是结点2i。
    ③如果2i+1>n,则结点i无右孩子;否则其右孩子是结点2i+1.

三、二叉树的遍历(重要)

前面都是铺垫,二叉树遍历才是主菜,重点肯定也是难点。
二叉树遍历原理:二叉树的遍历(traversing binary tree)是指从根结点出发,按照某种次序依次访问二叉树中所有结点,使得每个结点被访问一次且仅被访问一次。注意概念中的访问遍历两个关键词。

二叉树的遍历方式有很多,按从左到右遍历主要分为前序遍历、中序遍历、后序遍历和层序遍历。

  • 前序遍历
    前序遍历的规则是若二叉树为空,则空操作返回,否则先访问根结点,然后前序遍历左子树,再前序遍历右子树,如下图,遍历的顺序为:ABDGHCEIF


    前序遍历
  • 中序遍历

中序遍历是从根结点的左子树开始,然后访问根结点,最后中序遍历右子树,如下图,遍历的顺序为:GDHBAEICF


中序遍历
  • 后序遍历
    从左到右先叶子结点的方式遍历访问左右子树,最后访问根结点,如下图,遍历的顺序为:GHDBIEFCA


    后序遍历
  • 层序遍历
    从树的第一层根结点,从上而下遍历,同层从左到右访问,如下图,遍历顺序为:ABCDEFGHI


    层序遍历

四、实战

假设我们有如下这样一棵二叉树T。这树已经用二叉链表结构存储在内存当中。(之所以不使用原来的图,仅仅希望能多举一个例子,然后大家可以比较学习)

二叉树遍历.jpg

结点定义:

/*二叉链表结点结构定义*/
typedef int TElemType;
typedef struct BiTNode{//结点结构
  TElemType date;//结点数据
  struct BiTNode *lchild,*rchild;//左右孩子指针
} BiTNode,*BiTree;
  • 前序遍历(递归算法)
void PreOrderTraverse(BiTree T){
  if(T==null)
        return;
   printf("%c",T->data);/*显示结点数据,可以更改为其他对结点操作*/
   PreOrderTraverse(T->lchild);/*遍历左子树*/
   PreOrderTraverse(T->rchild);/*遍历右子树*/
}

看到这里也许你会和我一样失望,说好的前序遍历呢,为什么这么几行代码就把前序遍历解决了,不错你没看错,别小看这几行代码,其实这几行代码才是无穷无尽的,这就是递归算法!(网上说递归算法是神写的,迭代算法才是人写的=_=)。不了解递归算法的小伙伴赶快去看这篇文章《Java算法》

分析:
①当调用PreOrderTraverse(T),T的根结点不为null,所以执行printf,打印字母A。
②接下来调用PreOrderTravers(T->lchild);访问了A结点的左孩子,不为null,执行printf显示字母B。
③在这里可以很清楚的看到打印B之后,会继续执行PreOrderTravers(T->lchild);访问B结点的左孩子,执行printf显示字母D。
④再次递归调用PreOrderTravers(T->lchild),打印字母H。(一直到这里 PreOrderTraverse(T->rchild);方法从来没有执行过,只有当左孩子PreOrderTraverse(T->lchild);递归完成才会执行右孩子的递归)
⑤再次递归调用PreOrderTravers(T->lchild);访问了H孩子的左孩子,此时因为H结点无左孩子,所以T==null,返回此函数。直到此时PreOrderTravers(T->lchild);H这个结点的左孩子递归执行完毕 。此时递归调用PreOrderTravers(T->rchild);访问了H结点的右孩子,打印显示字母K。
⑥再次递归调用PreOrderTravers(T->lchild);访问K的左孩子,没有,则返回。调用PreOrderTravers(T->rchild);访问K的右孩子也没有,则返回。于是此函数执行完毕,返回到上一级递归的函数(即打印H结点时的函数 ),也执行完毕,返回到打印结点D时的函数,访问了D的函数,发现D结点没有右孩子,继续返回上一层递归函数,打印E。
⑦由于E没有左右孩子,返回打印B时的递归函数,递归执行完毕,返回到最初的PreOrderTravers,调用PreOrderTravers(T->rchild);访问结点A的右孩子,打印字母C。依次类推,前序遍历二叉树的结点顺序是:ABDHKECFIGJ。

如果还是不能理解,可以一边看代码一边画图。能用递归算法实现的遍历按照常理来说使用迭代算法也是可以实现的,并且在面试中容易被问到遍历迭代算法应注意什么。

迭代算法:
(迭代算法请看补充篇,链接在文章末尾。)

  • 中序遍历(递归算法)
void InOrderTraverse(BiTree T){
  if(T==null)
        return;
   InOrderTraverse(T->lchild);/*遍历左子树*/
   printf("%c",T->data);/*显示结点数据,可以更改为其他对结点操作*/
   InOrderTraverse(T->rchild);/*遍历右子树*/
}

分析:
①调用InOrderTraverse(T),T的根结点不为null,于是调用 InOrderTraverse(T->lchild);访问结点B。当前指针不为null,继续调用 InOrderTraverse(T->lchild);一直继续调用;直到访问结点H的左孩子,发现当前指针为null,于是返回。打印当前结点H。
②然后调用 InOrderTraverse(T->rchild);访问结点H的右孩子K,因为结点K没有左孩子,所以打印K。
③因为结点K没有右孩子,所以返回。打印结点H函数执行完毕,返回。打印D。
④D无右孩子,此函数执行完毕,返回。打印B。
⑤后面都是相同的道理了,最终打印结点顺序是:HKDBEAIFCGJ

迭代算法:
(迭代算法请看补充篇,链接在文章末尾。)

  • 后序遍历(递归算法)
    如果前序遍历和中序遍历弄明白了,后序遍历很容易想到如何写,打印的结果是:KHDEBIFJGCA。
void PostOrderTraverse(BiTree T){
  if(T==null)
        return;
   PostOrderTraverse(T->lchild);/*遍历左子树*/
   PostOrderTraverse(T->rchild);/*遍历右子树*/
   printf("%c",T->data);/*显示结点数据,可以更改为其他对结点操作*/
}

迭代算法:
(迭代算法请看补充篇,链接在文章末尾。)

五、总结:

树和二叉树的内容还有很多,想了解更多细节的同学建议买本书好好研究。我学习数据结构挺迟的,虽然走了很多弯路,但是相比于有些人说这么难的东西,工作中又用不到,干嘛学呢,自己能认识其重要性还是很幸运的。我承认以后参加工作,尤其是从事应用层开发的人员,几乎遇不到要求你来写数据结构和算法。但是有些事物不能因为你感受不到它带给你的直接好处就不去做,就像阅读,也许现在并不能给你带来直接的利益,但是却在潜移默化影响着你的思想。

二叉树必知必会-基础篇(补充)

每月更新两篇,质量保证,点击关注!
听说点喜欢的人运气会一直很好!!!

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

推荐阅读更多精彩内容