先根,后子树;先左子树,后右子树
二叉树的根节点 a 入队
a 的子树,根节点 b 和 c 分别入队
然后 b 的子树的根节点入队(为空)
c 的子树的根节点入队
d 的子树的根节点入队(为空)
e 的子树的根节点入队
f 的子树的根节点入队(为空)
g的子树的根节点入队(为空)结束层序遍历,整个过程就是一层层的遍历,依靠一个队列来存放临时查找的结点。
二叉树线索化
问题的提出:当以二叉链表作为存储结构时,只能找到结点的左右孩子的信息,而不能直接找到结点的任一序列的前驱与后继信息,这种信息只有在遍历的动态过程中才能得到,为了能保存所需的信息,可增加标志域;
其中: ltag=0,lchild 域指示结点的左孩子,ltag= 1,lchild 域指示结点的前驱,rtag=0,rchild 域指示结点的右孩子,rtag=1 ,rchild 域指示结点的后驱。
以这种结构构成的二叉链表作为二叉树的存储结构,叫做线索链表。其中指向结点前驱与后继的指针叫做线索,加上线索的二叉树称之为线索二叉树。
对二叉树以某种次序遍历使其变为线索二叉树的过程叫线索化
模仿线性表的存储结构,在二叉树的线索链表上也添加一个头结点,令其lchild域的指针指向二叉树的根结点,其rchild域的指针指向中序遍历时访问的最后一个结点,同时,令二叉树中序序列中的第一个结点lchild域的指针和最后一个结点rchild域的指针均指向头结点;就像为二叉树建立了一个双向线索链表,就好比人在一个圆圈上走路,有可能存在好走的可能性。
中序遍历序列为:D B E A H F C G
中序线索二叉树, ltag=0,lchild 域指示结点的左孩子,ltag= 1,lchild 域指示结点的前驱,rtag=0,rchild 域指示结点的右孩子,rtag=1 ,rchild 域指示结点的后驱,二叉树的二叉线索存储表示:
复制代码
//二叉树的二叉线索存储表示:
typedef enum{
Link, Thread
} PointerTag;
//Link == 0:指针, Thread == 1:线索
typedef struct BiThrNode{
int data;
BiThrNode *lchild, *rchild;//左右孩子指针域
PointerTag LTag, Rtag;//左右指针或者线索的标记
} BiTreeNode, *BiThrTree;
复制代码
中序遍历序列为:D B E A H F C G
先序线索二叉树
先序序列: A B C D E F G H K
后续线索二叉树
后序序列: D C B H K G F E A
如何遍历线索二叉树(中序遍历为例)
结点的后继:若是叶子结点,其右标志是“1”,即右链是线索,指示其后继;否则遍历其右子树时访问的第一个结点,即右子树中最左下的结点。
结点的前趋:若其左标志为“1”,则左链为线索,指示其前驱,否则遍历左子树时的最后访问的一个结点(左子树最右下的结点)为其前驱。
什么时候采用线索链表做存储结构?
程序中所用二叉树需要经常遍历或查找结点在遍历所得线性序列中的前驱和后继时。
线索链表的遍历算法(中序找后继法)
模仿线性表的存储结构,在二叉树的线索链表上也添加一个头结点,令其lchild域的指针指向二叉树的根结点,其rchild域的指针指向中序遍历时访问的最后一个结点,同时,令二叉树中序序列中的第一个结点lchild域的指针和最后一个结点rchild域的指针均指向头结点;就像为二叉树建立了一个双向线索链表
复制代码
1 //二叉树的二叉线索存储表示:
2 typedef enum{
3 Link, Thread
4 } PointerTag;
5 //Link == 0:指针, Thread == 1:线索
6
7 //线索二叉树结点结构表示
8 typedef struct BiThrNode{
9 int data;
10 BiThrNode *lchild, *rchild;//左右孩子指针域
11 PointerTag LTag, RTag;//左右指针或者线索的标记
12 } BiTreeNode, *BiThrTree;
13
14 //访问结点
15 bool visit(int data)
16 {
17 //访问函数
18 return true;
19 }
20
21 //中序找后继法,线索二叉树的遍历
22 bool InOrderTraverse_Thr(BiThrTree T)
23 {
24 //二叉线索树的中序遍历,T 是头结点,令其lchild域的指针指向二叉树的根结点
25 BiTreeNode *p = T->lchild;
26 //当遍历完毕的结束条件
27 while (p != T)
28 {
29 //p 结点的左标记为指针(ltag=0)。则左指针指向左孩子
30 while (p->LTag == Link)
31 {
32 p = p->lchild;
33 }
34 //中序遍历
35 //访问
36 visit(p->data);
37 //左边没有了,访问右边,p 结点的右标记是线索, rtag == 1.且其右孩子指针没有指向根节点的时候
38 while (p->RTag == Thread && p->rchild != T)
39 {
40 //访问的是p 的后继(也就是根节点)
41 p = p->rchild;
42 //访问
43 visit(p -> data);
44 }
45 //最后找右子树
46 p = p->rchild;
47 }
48
49 return true;
50 }
复制代码
如何对二叉树进行线索化?
建立线索二叉树的过程,实质上就是在遍历的过程中,检查当前结点的左右指针是否为空,如果为空,将它们改为指向其前驱或后继的线索。
设指针pre始终指向刚刚访问过的结点,指针p指向当前正在访问的结点。即pre为p的前驱结点,p为pre的后继结点,则在线索化的过程中,访问结点p所做的处理如下(以中序线索化为例)
(1)建立p的前驱线索。若p->lchild为空,则将其左标志置1,并令p->lchild指向其前驱pre
(2)建立pre的后继线索。若pre->rchild为空,则将其右标志置1,并令pre->rchild指向其后继p
(3)将pre指向p刚刚访问过的结点,即pre=p
复制代码
//二叉树的二叉线索存储表示:
typedef enum{
Link, Thread
} PointerTag;
//Link == 0:指针, Thread == 1:线索
//线索二叉树结点结构表示
typedef struct BiThrNode{
int data;
BiThrNode *lchild, *rchild;//左右孩子指针域
PointerTag LTag, RTag;//左右指针或者线索的标记
} BiTreeNode, *BiThrTree;
//访问结点
bool visit(int data)
{
//访问函数
return true;
}
//对已经存在的二叉树进行中序线索化(递归方法)
void InThreading(BiThrTree p)
{
//指向上一个刚刚访问过的结点
BiTreeNode *pre;
if §
{
// 左子树线索化
InThreading(p->lchild);
//如果左子树为空,则加线索
if (!p->lchild)
{
//加线索
p->LTag = Thread;
//指向前驱结点
p->lchild = pre;
}
//如果右子树为空
if (!pre->rchild)
{
//加线索
pre->RTag = Thread;
//指向后继结点
pre->rchild = p;
}
// 保持 pre 指向 p 的前驱
pre = p;
// 右子树线索化
InThreading(p->rchild);
}
}
//新建一个线索二叉树
bool InOrderThreading(BiThrTree Thrt, BiThrTree T)
{
//指向上一个刚刚访问过的结点
BiTreeNode pre;
// 新建头结点
if (!(Thrt = (BiTreeNode)malloc(sizeof(BiThrNode))))
{
exit(1);
}
//头结点新建成功
Thrt->LTag = Link;
Thrt->RTag = Thread;
//右指针回指
Thrt->rchild = Thrt;
// 若二叉树空,则左指针回指
if (!T)
{
Thrt->lchild = Thrt;
}
else
{
Thrt->lchild = T;
pre = Thrt;
// 中序遍历进行中序线索化
InThreading(T);
pre->rchild = Thrt;
pre->RTag = Thread;
// 最后一个结点线索化
Thrt->rchild = pre;
}
return true;
}