这是我观看浙大数据结构慕课的笔记。帮助自己理清思路,记住知识点。
3.1树与树的表示
为什么要有树结构
静态查找:没有插入删除操作,元素位置固定不变。
动态查找:有插入、删除操作。
二分法:适用于对数组进行操作,并且先对数据做排序,时间复杂度为O(logN)。
树形结构:二分法可以用树表示,查找次数就是改元素所在的树的层数。所以树的深度为logN向下取整+1。树形结构适用于动态查找,分层,使时间复杂度基本相当于二分法。
研究树==研究二叉树
一个N节点树有N-1条边。
树的实现:结构有一个数据域,两个指针域。指针一个指向左孩子,一个指向下一个兄弟节点。
数旋转45°,就是一颗二叉树。所以学会二叉树,就学会了一般树。
例:树的集合称为森林。是否也可以使用“儿子-兄弟”表示法存储森林?如何实现?
先把森林中每一棵树按照 孩子-兄弟 转化为二叉树,显然这些二叉树都是没有右子树的。接下来把第一棵树的根节点定做总的根节点,其左子树作为总的左子树。最后把其他的树作为右子树,加入到根节点中。于是就转化成了二叉树。
3.2二叉树及存储结构
一个二叉树i层最多有2i-1个节点
一个深度为k的二叉树总结点数最多有2k-1个。
n0=n2+1。
证:(向上,总结点数-1)n0+n1+n2-1=向下:0+n1+2n2
数组存储:父节点i/2向下取整,左孩子2i,右孩子2i+1。适用于完全二叉树,或相对完全二叉树空缺较少的二叉树(用空补齐)。
链表存储:存储的数据+两个分别指向左孩子和右孩子的指针域。
例:设深度为d(只有一个根结点时,d为1)的二叉树只有度为0和2的结点,则此类二叉树的结点数至少为2d-1。
3.3二叉树的遍历
使用链式存储,
先序、中序、后序遍历递归算法的本质是堆栈。所以考虑采用堆栈进行先序、中序非递归遍历算法。后序遍历可能要用两个堆栈。
1.中序遍历非递归算法:
(1)链式存储,并开一个栈。
(2)遇到一个结点,push,按其左指针一路把左孩子push。
(3)直到左孩子为空时,pop栈顶元素并访问,按其右指针把右孩子push。把右孩子作为当前结点。
不断循环(2)(3)while(树不空或堆栈不空)
2.(转)后序遍历非递归算法:
先序的访问顺序是root, left, right 假设将先序左右对调,则顺序变成root, right, left,暂定称之为“反序”。
后序遍历的访问顺序为left, right,root ,刚好是“反序”结果的逆向输出。于是方法如下:
(1)反序遍历二叉树,具体方法为:将先序遍历代码中的left 和right 对调即可。
数据存在堆栈S中。
(2)在先序遍历过程中,每次Push节点后紧接着print结点。
对应的,在反序遍历时,将print结点改为把当前结点 PUSH到堆栈Q中。
(3)反序遍历完成后,堆栈Q的压栈顺序即为反序遍历的输出结果。
此时再将堆栈Q中的结果pop并print,即为“反序”结果的逆向,也就是后序遍历的结果。
缺点是堆栈Q的深度等于数的结点数,空间占用较大。
3.层序遍历:
二叉树遍历的根本在于,只有知道父节点,才能找到孩子,而一个父节点有两个孩子,所以要解决这个矛盾,才能做到把二维树结构变一维链结构。层序遍历,使用队列,根节点出队,他的左右孩子分别入队,队首元素出队,他的左右孩子入队,继续循环下去。
例:先序和后序无法唯一确定一个二叉树,因为 根左右 左右根 左右子树间没有明显的分界线。
但中序和先序,中序和后序可唯一确定一个二叉树。例:由先序遍历第一个元素找到根节点。在中序遍历中由根节点划分出左右子树。在先序遍历中找到左子树的元素,其中第一个为左子树的根,由此递归得到二叉树。
例:判断树的同构:
主要解决三个问题:
1.二叉树的构建:即输入时的格式。
2.二叉树存储:用结构数组的存储结构(静态链表)来表示二叉树。
注:C语言中NULL=0,而数组下标为0的地方有结点时,要#define Null=-1,用Null来表示空结点。
3.在静态链表中找根节点:没有元素指向的那一个元素。