数据结构 第五章 树与二叉树

一、树的基本概念

分支结点(非终端结点);叶子节点(终端节点)

树是一种递归定义的数据结构。

树的每棵子树之间互不相交。

eg电脑里的文件系统 使用到树。

祖先节点 从该结点到根节点路径上的 多个结点

子孙结点

双亲结点 孩子结点 兄弟结点

有序树(例如族谱树:同一行要按出生顺序从左往右写);无序树;

树的边都是有向边,只能从上往下。

两个结点之间的路径:只能从上往下。

路径长度:经过几条边。

森林

  

★常考,易错!

树的度:树中结点的度的最大值。(树的度=孩子最多的那个结点的度)

常考题型:度为4的树,给出度分别为4、3、2、1的结点个数,求叶子结点个数。

原理:

1)度为4的树,其结点的度的最大值为4:只有度为4、3、2、1、0的结点。

2)树的结点数量=所有结点的度之和+1(除根结点以外的结点数量+1个根结点)

步骤:

1)求出所有结点的度之和=20×4+10×3+1×2+10×1=122

2)求出树的结点数量=所有结点的度之和+1=123个

3)已知除叶子结点以外,有20+10+1+10=41个非叶子结点,所以叶子结点个数=123-41=82个

 树的路径长度是指:树根到每个结点的路径长的总和


树的常考性质

 

 

 

 

 


二、二叉树的概念

二叉树可以是  1)空二叉树  2)根结点和两个互不相交的左子树和右子树。左子树和右子树又分别是一棵二叉树。

二叉树中每个结点至多有两棵子树。

★注意:左右子树不能颠倒。二叉树是有序树。

★区别二叉树和度为2的树:

度为2的树——至少有一个结点有两棵子树。

二叉树——每个结点至多有两棵子树。


特殊的二叉树

1)满二叉树

根结点编号1,按曾依次往下编号,层数越高,结点的编号越大。

2)完全二叉树

每个结点的编号都与满二叉树对应。

3)二叉排序树

4)平衡二叉树


二叉树的性质:

●二叉树n0=n2+1

●二叉树第i层至多有几个结点?

 

●高度为h的二叉树至多有几个结点?


完全二叉树的性质

●具有n个结点的完全二叉树,高度h为多少?

 

●完全二叉树最多只有一个度为1的结点,n1=0 /n1=1。

对于完全二叉树,可以由结点总数n推出度为0、1、2的结点个数。

 小结:

二叉树的存储结构

顺序存储:定长数组,从上至下,从左至右依次存储各个结点。

二叉树的顺序存储结构只适合存储完全二叉树。实际应用中很少用顺序存储方式存储一棵二叉树。

如果不是完全二叉树,将不能进行顺序存储——无法从结点编号反映出结点间的逻辑关系。

如何找结点i 的左、右孩子、父结点的编号?

如何判断结点i是否为叶子结点?

顺序存储的最坏情况:浪费许多空间


链式存储——二叉链表。二叉树的每一个结点BiTNode有一个data域和两个指针:*lchild、*rchild分别指向它的左孩子、右孩子。

按理说一棵二叉树共有2n个指针域。

除了根结点以外,剩下n-1个结点 每个结点头上都连着一个指针。即:n个结点的二叉树有n-1个指针域非空。

★n个结点的二叉链表共有2n-(n-1)= n+1个空指针域。→可以利用起来构造线索二叉树。

二叉树的链式存储(二叉链表)构建过程:

1、定义一棵空树:

2、用malloc函数申请一个根结点,在根结点中存入数字1,根结点的左右指针此时指向NULL 

3、用同样方式malloc申请一个新的结点,在结点中存入数字2,结点的左右指针此时指向NULL。

然后将根结点的左孩子指针lchild指向当前结点P。

4、再用同样的方法插入其他的结点。

二叉树的链式存储(二叉链表):

找到指定结点P的左/右孩子:很简单——看指针指向哪

找到指定结点P的父节点:麻烦,只能从根结点root开始遍历寻找——看哪个结点的左/右孩子指针指向P结点

如果应用场景中经常需要逆向找到父结点,可以在结构体中再定义一个指针:父结点指针*parent,指向它的父节点(每个结点总共有三个指针,称为“三叉链表”)


三、二叉树的遍历、线索二叉树

按理说一棵二叉树共有2n个指针域。

★除了根结点以外,剩下n-1个结点 每个结点头上都连着一个指针。

即:n个结点的二叉树有n-1个指针域非空。

★n个结点的二叉链表共有2n-(n-1)= n+1个空指针域。→可以利用起来构造线索二叉树。

线索二叉树分为三种类型:中序线索二叉树、先序线索二叉树、后续线索二叉树。

线索二叉树的结点定义和二叉树的结点有什么区别?数据结构不同,线索二叉树的结点多了两个标志域ltag和rtag当tag=0指向孩子,当tag=1表示线索指向前驱/后继

注意:初始化ltag=rtag=0(表示此时指针指向的都是孩子,并没有线索化)第一个遍历的结点没有(中序/先序/后序)前驱,令lchild指针域为前驱线索指向NULL最后一个遍历的结点没有(中序/先序/后序)后继,令rchild指针域为后继线索指向NULL。

考题:手算将二叉树画成线索二叉树。

步骤1、写出相应遍历序列(中序/先序/后序) ,给二叉树的各个结点标上它们被访问的次序(序号从1开始)

2、确定各个结点前驱和后继的关系,将n+1个空链域连上前驱和后继。(另外n-1个非空指针域不用管,它们已经连上结点的左/右孩子)

将二叉树的某一结点线索化:就是把结点的空链域(左孩子指针or右孩子指针)连上它的前驱or后继。(记得将ltag/rtag设为1表示线索)

建立线索的目的:为了更方便的从一个结点开始找到它的前驱/后继结点(根据中序/先序/后序序列)。


机算代码

eg中序线索化(机算) 

上面代码都跑完以后,最后指针pre和指针q会都指向最后一个被遍历的结点C。此时C的右rtag=0还未线索化。还要加上特别处理 if(pre->rchild==NULL){pre->rtag=1;}


eg先序线索化(机算) 

注意避免爱的魔力转圈圈问题——在visit完以后,通过增加判断条件ltag是否等于0,来决定是否需要遍历该结点的左子树


eg后序线索化(机算) 


小结


在线索二叉树中怎么找前驱/后继

 

 优化:利用线索,非递归的实现二叉树中序遍历,空间复杂度O(1)

for循环

1初始化,给循环变量赋初值:找到根结点为p的子树的最左下结点(作为中序遍历的开始结点)

2循环条件,条件成立时执行中间循环体:结点!=NULL

  这里的中间循环体是:访问该结点 visit(p)

(执行的中间循环体可以为一个语句,也可以为多个语句,当中间循环体只有一个语句时,其大括号{}可以省略,执行完中间循环体后接着执行末尾循环体。)

3末尾循环体,每执行完一次中间循环体以后执行:找到线索二叉树中p结点的后继

(执行末尾循环体后将再次进行条件判断,若条件还成立,则继续重复上述循环,当条件不成立时则跳出当下for循环。)

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值