六、树:
1.树:是n个结点的有限集;
有且仅有一个根结点;其余结点可分为m个互不相交的有限集,这些 有限集是子树;
2.度degree:结点拥有子树数;
度为0:叶结点leaf/终端结点;
分支结点:内部结点;
树的度:树内各节点度的最大值;
3.结点之间关系:
(1)孩子child:结点(该结点为双亲parent)的子树的根;
(2)兄弟sibling:同一个双亲的孩子之间;
(3)祖先:从根到该结点所经分支上所有结点;
(4)子孙:以某结点为根的子树中任一结点;
4.(1)节点的层次:根为第一层,根的孩子在第二层以此类推;
(2)深度(高度):树中结点的最大层次;
(3)各子树从左至右有序则为有序树,反之无序;
(4)森林forest:m棵互不相交的树的集合;(子树之间即为森林)
5.ADT:
6.树的存储结构:
(1)双亲表示法:每个结点设指示器指示双亲结点的位置;数据域data,指针域parent;
根节点双亲为-1;
a.结点为两个整型,一个数据,一个标记双亲在数组中位置;整个树为结点数组;
b.上面的方法得到祖先很容易,但要得到孩子需要遍历整个结构;
故引入长子域(结点最左边的孩子);没有孩子的结点长子域-1;
c.要研究兄弟之间关系 ——引入右兄弟域;
(2)孩子表示法:多重链表;结点有多个指针域,指向子树的根;
但每棵树的度不同,有两种方案:
a.各结点度相差不大时,用树的度即可;
b.建立度域;
c.孩子表示法:每个结点的孩子排列,以单链表存储;n个头指针组成线性表,采用顺序存储结构放入一维数组(所有结点存入数组中,用单链表表示它的孩子);即设置两种结点:孩子链表结点、表头数组结点;
(孩子节点(x为其双亲):数据域存储此孩子在表头的下标;指针域指向x的其他孩子)
(表头数组:将所有结点按顺序排列进来;数据域为结点数据信息,指针域指向第一个孩子,即存储了孩子链表的头指针)
这样方便研究孩子和兄弟关系,但不方便研究双亲;双亲孩子表示法:
(3)孩子兄弟表示法:树结点的第一个孩子和右兄弟唯一;故设置两指针表示;
(将复杂树变为二叉树)
7.二叉树(binary tree):
(1)二叉树:n个结点有限集,该集合的元素为空集和由一个根节点和两棵互不相交的左子树、右子树组成的二叉树;
(2)二叉树特点:最多两子树;左右有序(即使只有一个子树也需要区分);
(3)特殊二叉树:
a.斜树:所有结点都只有右树或左树;即线性表,结点数等于深度;
b.满二叉树:所有分支结点都有左右子树;叶子都在同层;(同深度下结点、叶子最多);
c.完全二叉树:n结点二叉树按层编号;i结点与同深度的满二叉树编号i的结点在二叉树中位置相同;(不满,但编号连续);
特点:叶子结点在最下两层,最下层叶子在左边连续,倒二层叶子在右边连续,结点度为1的话只有左结点;
同样结点数,完全二叉树深度最小;
(4)二叉树性质:
a. 第i 层有2^(i-1)个结点;
b. 深度为k的二叉树至多有2^k-1个结点;
c. 任何一棵二叉树T,叶子结点数n0,则度为2的结点数n0-1;(用数分枝线的方法);
d.具有n个结点的完全二叉树深度为(log2n)+1;
e.n结点完全二叉树,按层编号;
若i=1,无双亲;i>1,双亲是结点(i/2);
若2i>n,i无左孩子(叶子结点);否则左孩子是2i;
若2i+1>n,i无右孩子;否则右孩子是2i+1;
(5)二叉树存储结构:
a.二叉树顺序存储:用一维数组存储,下标即为编号,不存在的用null;一般用于完全二叉树,否则有大量空间浪费;
b.二叉链表:一个数据域两个指针域;
(6)二叉树遍历:从根结点出发,按某次序依次访问二叉树所有结点;每个结点有且仅访问一次;(每个都要有操作:树为空则空操作返回)
a. 前序遍历:先访问根,前序遍历左子树再前序遍历右子树;(从根开始蛇形遍历)
运用递归,先遍历访问、打印左树,直到没有左树或无孩子,执行打印右树指令,返回上一级执行打印右树;
b. 中序遍历:
从最左端元素开始蛇形遍历;
运用递归,先访问左树,直到某元素g无左树或者无孩子,打印这个元素,执行访问右树;如果无右树则返回打印上一级、访问右树(也按这个顺序),访问完右树跳回上上一级……;
(对比前序,实际上从先打印再访问变成了先访问再打印)
无左子树为止的元素打印,返回上一级(上一级已经访问过左子树了,直接打印它再访问右子树);返回上上级……
c.后序遍历:
从左到右先叶子后结点访问左右树,最后访问根结点;(从最左开始,有兄弟先打印兄弟(从兄弟的叶子开始),无兄弟时正常蛇形)
即先访问左树直到无左子树,访问这个元素右子树,打印;返回上一级(上一级是已经执行过左右子树访问的,直接打印);再返回上上级(上上级已经执行过访问左子树,则访问右子树,若有右子树则打印右子树再打印它自己,若无右子树则直接打印自己);
(前中后实际就是输出在三个语句中位置;中左右、左中右、左右中);
d.层序遍历:
从第一层开始从左到右遍历;
(7)二叉树的建立:
a.给每个结点的空指针引出虚结点,值为特定值如“#“,称为扩展二叉树;
b.如果为#则是空结点,如果不是#就生成新结点,递推构造左右子树;
若前序遍历AB#D##C##输入数据;
此程序为前序法;实际上就是把遍历中的打印操作改成了生成结点;
(8)线索二叉树:
a. n个结点的二叉链表,一共有2n个指针域;有n-1条分支,即有n+1个空指针,需要提高空间利用率;
b.指向前驱后继的指针称为线索;加上线索后叫线索链表(线索二叉树);
c. 按某种遍历规则,将空的右指针改为指向后继,空的左指针改为指向前驱;此时变为双向链表,称为线索化;
d.区分是否指向前驱后继,引入标志域ltag和rtag,存放0和1;线索是1;
e.线索二叉树结构:
线索化是在遍历的过程中修改空指针;
若为中序遍历:将打印的步骤变成线索化过程;
前驱:如果结点p左指针域为空,前驱结点刚刚访问过且赋值给了pre;故将pre赋值给p->lchild,修改标志域为1;
后继:p的后继还未访问到,则对前驱pre的右指针判断改变,指向p;
完成后需要后移,将p赋值给pre;
f. 线索二叉树的操作同双向链表:添加头结点,lc指向根,rc指向中序遍历时访问的最后一个结点,此时可以任意顺序遍历了;
即先循环到第一个位置打印,此时让它通过修改过的指针向它的后继访问;
8.树、森林、二叉树转换:
(1)树的孩子兄弟法可以将树用二叉链表存储——树转化为二叉树:
a.加线:所有兄弟结点之间加线;
b.去线:每个结点只保留与第一个孩子结点的连线,删除与其他孩子之间的;
c.层次调整:旋转角度;第一个孩子是左孩子,兄弟转化的是右孩子;
(2)森林转化二叉树:森林是若干棵树,每棵树都可以按照兄弟处理;
a.树转化二叉;
b.第一棵二叉树不动,从第二棵树开始,依次把后一棵二叉树根结点作为前一棵二叉树
根节点的右孩子;
(3)二叉树转化为树:
a.加线:某孩子的左孩子存在,将这个左孩子的n个右孩子都作为此结点的孩子;
b.去线:删除原二叉树中所有结点与右孩子的连线;
c.层次调整:使结构分明;
(4)二叉树转化森林:如果二叉的根结点有右孩子就是森林,无就是树;
a.从根开始,右孩子存在就把与右孩子的连线删除,分离的树右孩子存在也连线删除…直到所有右孩子连线删除,得到分离的二叉树,
b.二叉树转化成树;
(5)树与森林遍历:
a.树的遍历:先根遍历——先访问根节点,再从左到右每棵子树;
后根遍历——依次遍历每棵子树,最后根节点;
b.森林的遍历:前序遍历——先访问第一棵树根节点,先根遍历子树;
后序遍历——先访问第一棵树,后根遍历每棵子树,最后根节点;
c.森林前序遍历与二叉树前序一样,后续与二叉中序一样;