二叉树遍历
前序
-
根,左,右
中序
-
左,根,右
-
在二叉查找树中可以将数字从小到大输出,若输出至一个数组中,还可以找到第i大的数字
后序
-
左,右,根
ps
这三种遍历都是以根节点作为根本的,注意遍历和访问的区别,第三种可以看作是先遍历了根节点,方式是先访问左孩子,再访问右孩子,最后访问根结点。
递归算法
递归的第一层是很容易看懂的。当我们假设一个递归函数中所有的调用函数都直接遇到了边界条件,直接返回了,那么便是最后的情况。
正如许多个细胞在一起便构成了我们一个人,一片森林由许多颗树构成,一颗树由许多个枝桠构成。我们总是认为我们很难弄懂一个人,但是如果我们弄懂了每一个细胞,离弄懂一整个人还会远吗?
我们弄懂了一个最简单的递归,离弄懂整个递归还会远吗?我们没有必要真正将整所有递归全部写出,正如我们没有必要研究许多细胞,我们只需要将最底层,能够体现代码思想的递归弄懂,将一种种特殊类型的细胞弄懂,就可以窥一斑而知全豹了。
//preorder printf("root data"); printf("left data"); printf("right data");
可以将left data本身当作一个数组,它里面存储了它下面的所有数据
(二叉)树定义
-
树:表示不同层级,由一个根节点和若干子树构成,类似Linux系统目录,“/”就是根节点,之下有许多子树。
-
二叉树:树中结点的孩子结点只有第1个,第2个,第3个的区别;而二叉树中的孩子结点的中带你是左,右之分
-
⚠️满二叉树和完全二叉树的区别
-
扩展二叉树:用于使一个遍历序列能够确定一棵二叉树
-
二叉树基本性质
-
叶子结点(度为0)的个数 == 度为2的结点总个数+1
-
二叉树度为0,1,2的结点数量加在一起就是二叉树结点的总个数
-
二叉树度为1的结点造成的分支只有1个,度为2的结点造成的分支有两个,所有的分支数是是除去根节点外所有结点的个数
-
-
具有n个结点的满二叉树的深度是多少?
-
完全二叉树呢?
-
二叉链表
一个树的结点中存储着左孩子和右孩子
拓展 三叉链表
多增加了一个父母结点的空间开销
二叉树的存储结构
顺序存储
数组存储,有点第i个结点的左孩子结点是(if any)2i了,类似完全二叉树的存储。
链式存储
-
二叉链,三叉链
-
线索链表
-
指向前驱或后继结点的指针为线索
-
二叉树中有一些链表的指针指向的是NULL,为了将其充分利用,我们可以将其用来反向指代,也就是将它指向前面的父结点
-
为了区分该结点是往孩子方向指,还是往父母方向指,就要增加一定的空间开销来区分
-
规定左 这三条是初读时的误解,要不然就和三叉表类似了,还是“画蛇添足”了
-
线索链表的目的是为了能够按照此链表的顺序直接遍历一个二叉树
-
为此每一个结点都要给出它的下一个结点的信息
-
如果一个结点有左右孩子,那么它的下一个结点就是确定的
-
但是当该结点没有左右孩子的或者缺少一个的话就要充分利用它的原本空间了
-
如果两个孩子都没有的话,可以将它原本指向左孩子的结点指向它在遍历时的上一个结点,原本指向右孩子的结点指向它在遍历时的下一个结点
-
如果只有一个孩子,也按照上面方法补全
-
-
为此就需要增加空间来区分一个结点的两个指针有什么作用了
-
将其称为左tag,右tag
-
为0时,指针则是原来的作用,为1时,它就起到了线索的作用
-
此处又可以想到它作为线索的一个作用,因为在没有线索时,我们到了缺少一个孩子的结点时,往往会因为缺少信息而无法进行下一步操作,此处的线索就是为了帮助我们更好的遍历
-
-
-
二叉树的建立算法
建立就可以根据前面的存储结构来建立了
-
输入对应的一串数字,按照各种遍历的顺序就可以生成一棵二叉树(当然需要信息足够)
-
还可以利用顺序存储的想法,类似扩展二叉树
-
只有前序或后序或只有这两者都无法唯一确定一棵二叉树
-
也可以借用线索二叉树
-
-
或者一个一个输入,先指定它的根节点,再指定它是左孩子还是右孩子
树,森林,二叉树之间的转化
-
有点可逆过程的感觉
-
将一棵树的根结点去掉,这颗树就可能会变成森林
-
将所有森林都用一个根结点连起来,这些森林就会变成树
-
-
如何转换成二叉树
-
树->
-
左孩子右兄弟
-
-
森林->
-
先将森林中的树转换为二叉树
-
再将各个树的根节点连接为兄弟
-
重复树->
-
-
-
哈夫曼树及哈夫曼编码
-
最优二叉树
-
将常用的字符在编码时,编的代号较短
-
不等长编码中解码会出现困难,但是哈夫曼编码则不会
拓展:二叉树遍历的非递归算法
参考资料
-
王红梅 皮德常编著 《数据结构——从概念到C实现》 2017年2月第一次印刷清华大学出版社 ISBN:978-7-302-45149-5