第七章 树形结构
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
根结点:D 左先序:空 左中序:空 右先序:G 右中序:G
根结点:E 左先序:空 左中序:空 右先序:空 右中序:空
根结点:F 左先序:空 左中序:空 右先序:空 右中序:空
根结点:G 左先序:空 左中序:空 右先序:空 右中序:空 18
例如,已知中序序列为DGBAECF,后序序列为 GDBEFCA。对应的构造二叉树的过程如下所示。
2
中序遍历 (Inorder Traversal)
中序遍历二叉树算法的定义:
若二叉树为空,则空操作; 否则 中序遍历左子树 (L); 访问根结点 (V); 中序遍历右子树 (R)。
遍历结果 a+b*c-d-e/f
-
+
a b c
/
* e
d
f
3
中序遍历二叉树的递归算法 void InOrder ( BinTreeNode *T ) { if ( T != NULL ) { InOrder ( T->leftChild ); printf(“%c”, T->data); InOrder ( T->rightChild ); } }
27
中序二叉树线索化算法
• 建立头指针,中序遍历过程中建立线索
– p总是指向当前被线索化的结点,pre指向刚 刚访问过的结点(即*pre是*p的前驱结 点,*p是*pre的后继结点。) – CreaThread(b)算法将以二叉链存储的二叉 树b进行中序线索化,并返回线索化后头结点 的指针root。 – Thread(p)算法用于对于以*p为根结点的二 叉树中序线索化。
11
if (p->lchild!=NULL) /*有左孩子时将其进队*/ { rear=(rear+1)%MaxSize; qu[rear]=p->lchild; } if (p->rchild!=NULL) /*有右孩子时将其进队*/ { rear=(rear+1)%MaxSize; qu[rear]=p->rchild; } }
指向头结点的线索,并将头结点的rchild指针域线索化
为指向最后一个结点(由于线索化直到p等于NULL为 止,所以最后一个结点为*pre)。
29
TBTNode *CreaThread(TBTNode *b) /*中序线索化二叉树*/ { TBTNode *root; root=(TBTNode *)malloc(sizeof(TBTNode)); /*创建头结点*/ root->ltag=0;root->rtag=1; root->rchild=b; if (b==NULL) root->lchild=root; /*空二叉树*/ else { root->lchild=b; pre=root; /*pre是*p的前驱结点,供加线索用*/ Thread(b); /*中序遍历线索化二叉树*/ pre->rchild=root; /*最后处理,加入指向头结点的线索*/ pre->rtag=1; root->rchild=pre; /*头结点右线索化*/ } return root; 30 }
} TBTNode;
/*线索树结点类型定义*/
25
root 0 1
root 0 1
0 A 0
0 A 0
0 B 1
0 C 0
0 B 1
0 C 0
1 D 0
1 E 1
1 F 1
1 D 0
1 E 1
1 F 1
1 G 1
1 G 1
(a) 中序线索树
root 0 1
(b) 先序线索树
0 A 0
0 B 1
0 C 0
6
后序遍历二叉树算法的定义:
若二叉树为空,则空操作; 否则 后序遍历左子树 (L); 后序遍历右子树 (R); 访问根结点 (V)。
遍历结果 abcd-*+ef/-
后序遍历 (Postorder Traversal)
-
+
a
b
/
* e
-
f
c
d
7
后序遍历二叉树的递归算法:
void PostOrder ( BinTreeNode * T ) { if ( T != NULL ) { PostOrder ( T->leftChild ); PostOrder ( T->rightChild ); printf(“%c”, T->data); } }
1 D 0
1 E 1
1 F 1
1 G 1
26
(c) 后序线索树
线索二叉树的建立
• 建立线索二叉树(二叉树线索化),实质上就是 遍历一棵二叉树,在遍历的过程中,检查当前结 点的左、右指针域是否为空。如果为空,将它们 改为指向前驱结点或后继结点的线索。另外,在 对一棵二叉树添加线索时,创建一个头结点,并 建立头结点与二叉树的根结点的线索。对二叉 树线索化后,还须建立最后一个结点与头结点之 间的线索。 • 下面以中序线索二叉树为例,讨论建立线索 二叉树的算法。
8
A
B ∧
C
先序序列:ABDGCEF 中序序列:DGBAECF
后序序列:GDBEFCA
∧ D
∧ E ∧
∧ F ∧
∧ G ∧
图1.二叉树存储结构
9
层次遍历 其过程是: 若二叉树非空(假设其高度为h),则: (1)访问根结点(第1层); (2)从左到右访问第2层的所有结点; (3)从左到右访问第3层的所有结点、…、第 h层的所有结点。
含两个指针域的结点结构 lChild 增加两个标志位的结点结构
ltag lChild data rChild rtag
data
rChild
左标志ltag ltag 右标志rtag rtag
=0表示lchild指向左孩子结点 =1表示lchild指向前驱结点 =0表示rchild指向右孩子结点 =1表示rchild指向后继结点
根结点:A 左中序:DGB 左根:B 右中序:ECF 右根:C
根结点:B 左中序:DG 左根:D 右中序:空 右根:空
根结点:C 左中序:E 左根:E 右中序:F 右根:F
根结点:D 左中序:空 左根:空 右中序:G 右根:G
根结点:E 左中序:空 左根:空 右中序:空 右根:空
根结点:F 左中序:空 左根:空 右中序:空 右根:空
4
前序遍历 (Preorder Traversal)
前序遍历二叉树算法的定义:
若二叉树为空,则空操作; 否则 访问根结点 (V); 前序遍历左子树 (L); 前序遍历右子树 (R)。
遍历结果 -+a*b-cd/ef
-
+
a b c
/
*
-
e d
f
5
前序遍历二叉树的递归算法
void PreOrder ( BinTreeNode *T ) { if ( T != NULL ) { printf(“%c”, T->data); PreOrder ( T->leftChild ); PreOrder ( T->rightChild ); } }
/*全局变量*/ /*对二叉树b进行中序线索化*/ /*左子树线索化*/
ቤተ መጻሕፍቲ ባይዱ
if (p->lchild==NULL)
else p->ltag=0;
/*前驱线索*/
{ p->lchild=pre; p->ltag=1;} /*建立当前结点的前驱线索*/
if (pre->rchild==NULL)
else pre->rtag=0;
14
15
16
17
例如,已知先序序列为ABDGCEF,中序序列为 DGBAECF,则构造二叉树的过程如下所示。
根结点:A 左先序:BDG 左中序:DGB 右先序:CEF 右中序:ECF
根结点:B 左先序:DG 左中序:DG 右先序:空 右中序:空
根结点:C 左先序:E 左中序:E 右先序:F 右中序:F
28
CreaThread(b)算法思路是:先创建头结点*root, 其lchild域为链指针,rchild域为线索。将rchild指针指 向*b,如果b二叉树为空,则将其lchild指向自身。否则将
*root的lchild指向*b结点,p指向*b结点,pre指向*root结
点。再调用Thread(b)对整个二叉树线索化。最后加入
}
12
递归问题的非递归算法
13
二叉树的构造
• 同一棵二叉树具有唯一先序序列、中序序列和 后序序列,但不同的二叉树可能具有相同的先序 序列、中序序列和后序序列。 • 例如,如教材中图7.11所示的5棵二叉树,先序序列 都为ABC。如图7.12所示的5棵二叉树,中序序列 都为ACB。如图7.13所示的5棵二叉树,后序序列 都为CBA。 • 显然,仅由一个先序序列(或中序序列、后序序列), 无法确定这棵二叉树的树形。但是,如果同时知 道一棵二叉树的先序序列和中序序列,或者同时 知道中序序列和后序序列,就能确定这棵二叉树
24
修改后的二叉树结点的类型定义如下:
typedef struct node
{ ElemType data;/*结点数据域*/ int ltag,rtag; struct node *lchild; struct node *rchild; /*增加的线索标记*/ /*左孩子或线索指针*/ /*右孩子或线索指针*/
第七章 树形结构
树的概念和常用术语 树的存储 二叉树概念与性质 森林、树与二叉树的转换 二叉树遍历 线索二叉树 哈夫曼树
1
二叉树遍历
树的遍历就是按某种次序访问树中的结 点,要求每个结点访问一次且仅访问一次。 设访问根结点记作 V 遍历根的左子树记作 L 遍历根的右子树记作 R 则可能的遍历次序有 前序 VLR V 中序 LVR L R 后序 LRV
Thread(p) 算法思路是:类似于中序遍历的递归算
法 ,在 p指针不为 NULL时 ,先对*p结点的左子树线索化; 若 *p 结点没有左孩子结点 ,则将其 lchild 指针线索化为 指向其前驱结点 *pre,将其 ltag置为 1,否则表示 lchild指 向其左孩子结点;若 *pre 结点的 rchild 指针为 NULL,
21
回顾:树的左子女-右兄弟表示 (二叉链表表示) 结点结构
A B C D data firstChild nextSibling
A
E
F
G
E
B
F
C G
D
22
空链域n+1个
线索二叉树 (Threaded Binary Tree)
• 什么是线索二叉树?
• 线索二叉树的构造
23
如何区分孩子和线索?
将其 rchild 指针线索化为指向其后继结点 *p, 将其 rtag
置为1,否则rchild表示指向其右孩子结点, 再将pre指向 *p;最后对*p结点的右子树线索化。
31
TBTNode *pre; void Thread(TBTNode *&p) { if (p!=NULL) { Thread(p->lchild);
根结点:G 左中序:空 左根:空 右中序:空 右根:空 19
A
B ∧
C
先序序列:ABDGCEF 中序序列:DGBAECF
后序序列:GDBEFCA
∧ D
∧ E ∧
∧ F ∧
∧ G ∧
图1.二叉树存储结构
20
• 遍历二叉树是以一定规则将二叉树中结点排列 成一个线性序列,实质上对一个非线性结构进 行线性化的操作,使每个节点(除去第一个和 最后一个)在这些线性序列中有且仅有一个直 接前驱和直接后继。 • 二叉链表作为存储结构,只能找到节点的左右 孩子的信息,而不能直接得到节点在序列中的 前驱和后继信息,我们只有在遍历的动态过程 中才能得到。 • 如何保存这种遍历过程中得到的信息? • 一个最简单的办法是在每个节点上增加两个指 针,分别指示节点在依任一次遍历得到的前驱 和后继信息。
/*后继线索*/
{ pre->rchild=p;pre->rtag=1;}/*建立前驱结点的后继线索*/
pre=p;
Thread(p->rchild); } } /*递归调用右子树线索化*/ 中序遍历(递归)
A B C A
B F
∧
∧
C
层次遍历序列: A、B、C、D、 E、F、G
∧
D G
E
D
∧
E
∧
F
∧
∧
G
∧
(a)
(b)
10
void LevelOrder(BTNode *b) { BTNode *p; BTNode *qu[MaxSize]; /*定义环形队列,存放结点指针*/ int front,rear; /*定义队头和队尾指针*/ front=rear=-1; /*置队列为空队列*/ rear++; qu[rear]=b; /*根结点指针进入队列*/ while (front!=rear) /*队列不为空*/ { front=(front+1)%MaxSize; p=qu[front]; /*队头出队列*/ printf("%c ",p->data); /*访问结点*/