850 likes | 1.02k Views
第六章 树和二叉树. 树的定义和术语 二叉树 遍历二叉树 树和森林 霍夫曼树. 树的定义和基本术语. 树( Tree) 的递归定义 树是 n (n 0) 个结点的有限集。如果 n = 0 ,称为空树;如果 n > 0 ,则 有且仅有一个特定的称之为 根 (Root) 的结点,它只有直接后继,但没有直接前驱; 当 n > 1 ,除根以外的其它结点划分为 m (m >0) 个互不相交的有限集 T 1 , T 2 ,…, T m ,其中每个集合本身又是一棵树,并且称为根的 子树 (SubTree) 。. 根. 子树. A. A. B.
E N D
第六章 树和二叉树 • 树的定义和术语 • 二叉树 • 遍历二叉树 • 树和森林 • 霍夫曼树
树的定义和基本术语 • 树(Tree)的递归定义 • 树是n (n 0) 个结点的有限集。如果 n = 0,称为空树;如果 n > 0,则 • 有且仅有一个特定的称之为根(Root)的结点,它只有直接后继,但没有直接前驱; • 当n > 1,除根以外的其它结点划分为 m (m >0) 个互不相交的有限集 T1, T2 ,…, Tm,其中每个集合本身又是一棵树,并且称为根的子树(SubTree)。
根 子树 A A B C D 只有根结点的树 E F G H I J K L M 有13个结点的树 • 树的常见表示方法: • 倒置树 • 嵌套集合 • 广义表 • 凹入表
1层 A 2层 B C D height = 4 3层 E F G H I J 4层 K L M 树的基本术语 • 结点(node) • 结点的度(degree) • 叶结点(leaf) • 分支结点 • 树的度 • 孩子(child) • 双亲(parent) • 兄弟(sibling) • 祖先(ancestor) • 子孙(descendant) • 结点层次(level) • 树的深度(高度) • 有序树/无序树 • 森林(forest)
树的ADT定义 ADT Tree{ …. 基本操作P: InitTree(&T); …. Value(T,cur_e); Assign(T,cur_e,value); Parent(T,cur_e); LeftChild(T,cur_e); …. InsertChild(&T,&p,i,c); //插入c为T中p所指结点的第i棵子树 DeleteChild(&T,&p,i); //删除T中p所指结点的第i棵子树 TraverseTree(T,Visit()); //树的遍历 }ADT Tree
L R L R 二叉树 (Binary Tree) 定义: 一棵二叉树是结点的一个有限集,该集合或者为空,或者是由一个根结点加上两棵分别称为左子树和右子树的互不相交的二叉树组成。 特点: 每个结点至多只有两棵子树(二叉树中不存在度大于2的结点) 五种形态::
二叉树的ADT定义 ADT BinaryTree{ …. 基本操作P: InitBiTree(&T); …. Value(T,e); Assign(T,&e,value); Parent(T,e); LeftChild(T,e); …. InsertChild(T,p,LR,c); 初始条件:二叉树T存在,p指向T中某个结点,LR为0和1,非空二叉 树c与T不相交且右子树为空 操作结果:根据LR为0或1,插入c为T中p所指结点的左或右子树。 P所指结点的原有左或右子树则成为c的右子树
DeleteChild(T,p,LR); PreOrderTraverse(T,Visit()); //先序遍历 InorderTraverse(T,Visit()); //中序遍历 PostOrderTraverse(T,Visit()) //后序遍历 LevelOrderTraverse(T,Visit()) //层序遍历 }ADT BinaryTree
二叉树的性质 性质1在二叉树的第 i 层上至多有 2i -1个结点。(i 1) [证明用归纳法] 证明:当i=1时,只有根结点,2 i-1=2 0=1。 假设对所有j,i>j1,命题成立,即第j层上至多有2 j-1个结点。 由归纳假设第i-1层上至多有 2i -2个结点。 由于二叉树的每个结点的度至多为2,故在第i层上的最大结点数为第i-1层上的最大结点数的2倍,即2* 2i -2= 2 i-1。
= =20 + 21 + … + 2 k-1 = 2 k-1 性质2深度为 k 的二叉树至多有 2 k-1个结点(k 1)。 证明: 由性质1可见,深度为k的二叉树的最大结点数为
性质3对任何一棵二叉树T, 如果其叶结点数为 n0, 度为2的结点数为 n2,则n0=n2+1. 证明:若度为1的结点有 n1个,总结点个数为 n,总边数为 e,则根据二叉树的定义, n = n0 + n1 + n2(结点总度数计算) e = 2n2 + n1 = n – 1 (边数=结点数减一) 因此,有 2n2 + n1 = n0 + n1 + n2- 1 n2 = n0- 1 n0 = n2 + 1
1 3 2 4 5 7 6 满二叉树 8 9 10 11 12 13 14 15 两种特殊形态的二叉树 定义1满二叉树 (Full Binary Tree) 一棵深度为k且有2 k-1个结点的二叉树称为满二叉树。
完全二叉树 满二叉树 1 1 3 3 2 2 4 5 7 6 4 5 7 6 8 9 10 11 12 8 9 10 11 12 13 14 15 定义2完全二叉树 (Complete Binary Tree) 若设二叉树的高度为h,则共有h层。除第 h 层外,其它各层 (1 h-1) 的结点数都达到最大个数,第 h 层从右向左连续缺若干结点,这就是完全二叉树。
1 1 3 3 2 2 4 5 4 5 6 6 7 非完全二叉树 • 特点: • 若对结点按从上到下,自左至右连续编号,则完全二叉树每个结点和相同高度满二叉树的编号结点一一对应。 • 叶结点只可能在层次最大的两层上出现。 • 任一结点,若其右分支下的子孙的最大层次为l,则其左分支下的子孙的最大层次必为l或l+1。
性质4具有 n (n 0) 个结点的完全二叉树的深度为log2(n) +1。 证明: 设完全二叉树的深度为 h,则根据性质2和完全二叉树的定义有 2h-1 - 1 < n 2h- 1或 2h-1 n < 2h 取对数 h-1 log2n < h,又h是整数, 因此有 h = log2(n) +1
1 2 3 4 5 7 6 8 9 8 9 10 • 性质5如将一棵有n个结点的完全二叉树自顶向下,同一层自左向右连续给结点编号 1, 2, …, n,则有以下关系: • 若i = 1, 则 i 无双亲; 若i > 1, 则 i 的双亲为i /2; • 若2i> n, 则 i 无左孩子;否则,i的左孩子为2i; • 若2i+1 > n, 则 i无右孩子;否则,i的右孩子为2i+1;
1 1 1 2 3 2 3 4 5 6 7 4 5 6 8 9 7 8 9 10 10 9 1 2 3 4 5 6 7 8 910 1 2 3 4 0 5 6 7 8 0 0 910 完全二叉树 的顺序表示 一般二叉树的顺序表示 • 顺序存储结构 二叉树的存储结构 存储效率太低了点吧!
lChild data rChild 含两个指针域的结点结构 data rChild lChild 二叉链表 链式存储结构 • 结点包含数据域和左、右孩子结点的指针域 Note:含有n个结点的二叉链表中有n+1个空链域。
lChild data parent rChild parent 含三个指针域的结点结构 data rChild lChild 三叉链表 • 为便于找到结点的双亲,可引入parent指针域!
root root A A A B B B D D C D C C E F E F E F 三叉链表 二叉树 二叉链表 二叉树链表表示的示例 root
二叉链表的定义 typedefstruct BiTnode { //结点定义 TElemType data; struct BiTnode * lchild, * rchild; } BiTNode,*BiTree;
D L R 遍历二叉树 定义:树的遍历就是按某种次序访问树中的结点,要求每个结点访问一次且仅访问一次。 • 设访问根结点记作 D 遍历根的左子树记作 L 遍历根的右子树记作 R 则可能的遍历次序有 先序 DLR 中序 LDR 后序 LRD
- + / * a e f b - c d 中序遍历 (InOrder Traversal) • 中序遍历二叉树算法的定义: 若二叉树为空,则空操作; 否则 • 中序遍历左子树 (L); • 访问根结点 (D); • 中序遍历右子树 (R)。 遍历结果 a + b * c - d - e / f
中序遍历二叉树的递归算法 Status InOrder ( BiTree T ,Status(* Visit)(TElemType e)) { if (T) { InOrder ( T->lchild ); Visit(T->data); InOrder ( T->rchild ); } elsereturn OK; }
- + / * e a f b - c d 先序遍历 (PreOrder Traversal) • 先序遍历二叉树算法的定义: 若二叉树为空,则空操作; 否则 • 访问根结点 (D); • 先序遍历左子树 (L); • 先序遍历右子树 (R)。 遍历结果 ----波兰式(前缀表示): - + a * b - c d / e f
先序遍历二叉树的递归算法 • Status PreOrder ( BiTree T ,Status(* Visit)(TElemType e)) { • if (T) { • Visit(T->data); • PreOrder ( T->lchild ); • PreOrder ( T->rchild ); • } elsereturn OK; • }
- + / * a e f b - c d 后序遍历 (PostOrder Traversal) • 后序遍历二叉树算法的定义: 若二叉树为空,则空操作; 否则 • 后序遍历左子树 (L); • 后序遍历右子树 (R); • 访问根结点 (D)。 遍历结果----逆波兰式(后缀表示): a b c d - * + e f / -
后序遍历二叉树的递归算法: • Status PostOrder ( BiTree T ,Status(* Visit)(TElemType e)) { • if (T) { • PostOrder ( T->lchild ); • PostOrder ( T->rchild ); • Visit(T->data); • } elsereturn OK; • }
二叉树遍历应用 • 按先序建立二叉树(递归算法) • 以递归方式建立二叉树。 • 输入结点值的顺序必须对应二叉树结点先序遍历的顺序。并约定以输入序列中不可能出现的值作为空结点的值以结束递归, 此值在RefValue中。例如用“@”或用“”表示字符序列或正整数序列空结点。
如图所示的二叉树的先序遍历顺序为 A B C @ @ D E @ G @ @ F @ @ @ A @ B C D E F @ @ @ @ @ G @ @
Status CreateBiTree (BiTree &T ) { scanf(&ch); if ( ch == RefValue ) T =NULL; //递归出口 else { if ( !(T = (BiTNode *) malloc(sizeof( BiTNode)) )) exit (OVERFLOW); T->data = ch; //生成根结点 CreateBiTree(T->lchild ); //构造左子树 CreateBiTree(T->rchild ); //构造右子树 } return ok; }
2. 计算二叉树结点个数(递归算法) int Count ( BiTree T ) { if ( T == NULL ) return 0; elsereturn 1 + Count ( T->lchild ) + Count ( T->rchild ); }
3. 求二叉树高度(递归算法) int Height ( BiTree T ) { if ( T == NULL ) return -1; else { int m = Height ( T->lchild ); int n = Height ( T->rchild ) ; return (m > n) ? m+1 : n+1; }
a d a c b a a e d c 中序遍历二叉树(非递归算法)用栈实现 b a b退栈 访问 d退栈 访问 d入栈 a b入栈 e c c退栈 访问 a退栈 访问 c e入栈 e 退栈 访问 栈空
中序遍历时递归工作栈的状态变化 • 工作记录中包含两项:返回地址和指向根结点的指针。当栈顶记录中的指针非空时,应遍历左子树,即指向左子树根的指针进栈; • 若栈顶纪录中指针为空,则应退至上层: • 若是从左子树返回,则应访问当前层即栈顶记录中指针所指的根结点; • 若是从右子树返回,则应继续退栈。
a c b e d StatusInOrderTraverse ( BiTree T, Status (* Visit)(TElemType e)){ InitStack( S ); Push(S,T) //根指针进栈 while ( !StackEmpty(S) ) { while (GetTop(S,p) && p) Push(S,p->lchild);//向左走到尽头 Pop(S,p); //空指针退栈 if ( !StackEmpty(S) ) { //栈非空 Pop(S, p); Visit(p->data); //退栈访问 Push(S, p->rChild) ; //向右一步 } } return ok; } 算法 6.2
StatusInOrderTraverse ( BiTree T, Status (* Visit) (TElemType e)){ InitStack( S ); p =T; while ( p ||!StackEmpty(S) ) { if (p) {Push(S,p);p=p->lchild;} //根指针进栈,遍历左子树 else {//根指针退栈,访问根结点,遍历右子树 Pop(S, p); Visit(p->data); //退栈,访问 p =p->rChild ; //向右一步 } } return ok; } 算法 6.3
先序遍历二叉树(非递归算法)用栈实现 a a d c c b c c e d 访问 a 进栈 c 左进 b 访问 b 进栈 d 左进 空 退栈 d 访问 d 左进 空 退栈 c 访问 c 左进 e 访问 e 左进 空 退栈 结束
a c b e d Status PreOrder( BiTree T ) { InitStack(S); //递归工作栈 p = T; Push (S, NULL); while ( p != NULL ) { visit( p) ; //先访问根结点 if ( p->rchild != NULL ) Push ( S, p->rchild ); //记下右子树的根 if ( p->lchild != NULL ) p = p->lchild; //先序遍历左子树 else Pop( S, p ); } return ok; }
ptr tag{L,R} 后序遍历二叉树(非递归算法)用栈实现 后序遍历时使用的栈的结点定义 typedef struct { BinTreeNode *ptr; //结点指针 enum tag{ L, R }; //该结点退栈标记 } StackNode; 根结点的 tag = L,表示从左子树退出, 访问右子树。 tag = R, 表示从右子树退出, 访问根。
void PostOrder ( BinTree T ) { • stack S; InitStack(&S); StackNode w; • BinTreeNode * p = T; • do { • while ( p != NULL ) { //向左子树走 • w.ptr = p; w.tag = L; Push (&S, w); • p = p->leftChild; } • int continue = 1; //继续循环标记
while ( continue && !StackEmpty(&S) ) { • Pop (&S, w); p = w.ptr; • switch ( w.tag ) { //判断栈顶tag标记 • case L : w.tag = R; Push (&S, w); • continue = 0; • p = p->rightChild; break; • case R : visit( p); • } • } • } while ( p != NULL || !StackEmpty(&S) ); • }
递归/非递归算法举例 交换二叉树各结点的左、右子树(递归算法) void unknown ( BinTreeNode * T ) { BinTreeNode *p = T, *temp; if ( p != NULL ) { temp = p->leftChild; p->leftChild = p->rightChild; p->rightChild = temp; unknown ( p->leftChild ); unknown ( p->rightChild ); } }
不用栈消去递归算法中的第二个递归语句 • void unknown ( BinTreeNode * T ) { • BinTreeNode *p = T, *temp; • while ( p != NULL ) { • temp = p->leftChild; • p->leftChild = p->rightChild; • p->rightChild = temp; • unknown ( p->leftChild ); • p = p->rightChild; • } • }
使用栈消去递归算法中的两个递归语句 • void unknown ( BinTreeNode * T ) { • BinTreeNode *p, *temp; • stack S; InitEmpty (&S); • if ( T != NULL ) { • push(&S, T); • while ( ! StackEmpty(&S) ) { • Pop(&S, p); //栈中退出一个结点 • temp = p->leftChild; //交换子女 • p->leftChild = p->rightChild; • p->rightChild = temp;
if ( p->rightChild != NULL ) • push (&S, p->rightChild ); • if ( p->leftChild != NULL ) • push (&S, p->leftChild ); • } • } • }
作 业 7 思考题: • 二叉树的先序及后序遍历的非递归算法(习题集6.37,6.38)。 作业:习题集 • 6.27 (page 41) , • 6.47 (page 43),
A A HBDF EKCG B EKCG H DF • 由二叉树的前序序列和中序序列可唯一地确定一棵二叉树。(6.31) 例, 前序序列 { ABHFDECKG } 和中序序列 { HBDFAEKCG }, 构造二叉树过程如下:
A A A B EKCG B EKCG B E H DF H F H F KCG D D A B E H F C K G D
树和森林 • 树的存储结构 • 森林与二叉树的转换 • 树和森林的遍历