1 第六章 第六章 树和二叉树 树和二叉树 基本概念 二叉树 二叉树的遍历和线索树 树 赫夫曼树 数 据 结 构 之 树 和 二 叉 树 2 只有根结点的树 一般树 层次 1 2 3 4 A DCB E G F K L H I J M A 2 数 据 结 构 之 树 和 二 叉 树 3 6.1 树的基本概念 ? 树的基本概念 1. 树:是 n(n>=0)个结点的有限集。 n=0 称空树。 在任一非空树 (n>0)中 : (1) 有且仅有一个称为根的结点; (2) 其余结点可分为 m(m>=0)个互不相交的 有限集 T1, T2, ……, Tm,其中,每个集 合本身又是一棵树,并且称为根的子树。 2. 结点的度:树的结点所拥有的子树数; 3. 树的度:是树内各结点的度的最大值; 4. 度为 0的结点称为叶结点或终端结点 数 据 结 构 之 树 和 二 叉 树 4 5. 孩子结点、双亲结点、兄弟结点、堂兄弟 结点、祖先结点、子孙结点 …… 6. 结点的层次从根开始,根为第一层,根的 孩子为第二层;若某结点在第 L层,则其 子树的根就在第 L+1层。 7. 树的深度或高度:树中结点的最大层次。 8. 有序树:如果将树中结点的各子树看成是 从左至右有次序的;反之,则是无序树。 9. 森林:是 m棵互不相交的树的集合。 3 数 据 结 构 之 树 和 二 叉 树 5 例: 9个根结点的树: A是根,其他结点为三 个互不相交的有限集, T 1 = {B, E, F, G, I}, T 2 ={C},T 3 ={D,H},T 1-3 都是根 A的子树,而本 身又都是一棵树。 A B C D E F G H I 数 据 结 构 之 树 和 二 叉 树 6 ? 树的表示法 ? 分支图表示法 ? 嵌套集合表示法 ? 广义表表示法 (A(B(D),C)) A B C D A B C D 4 数 据 结 构 之 树 和 二 叉 树 7 ? 树的基本操作 ? 初始化操作INITIATE(T):创建一棵空树。 ? 求根函数ROOT(T):求树T的根;ROOT(X):求 结点x所在树的根。 ? 求双亲函数PARENT(T,x):在树T中求x的双亲。 ? 求第i个孩子函数CHILD(T,x,i):在树T中求 结点x的第i个孩子。 ? 求兄弟函数: ?LSIBLING(T,x):在T中求x的左兄弟 ?RSIBLING(T,x):在树T中求x的右兄弟 数 据 结 构 之 树 和 二 叉 树 8 ? 建树函数CRT-TREE(x,F):建立以结点x为 根,森林F为子树的树。 ? 插入子树操作INS-CHILD(y,i,x):将x作为 y的第i棵子树。 ? 删除子树操作DEL-CHILD(x,i):删除x的第 i棵子树。 ? 遍历树操作TRAVERSE(T):按顺序访问树T 中各个结点。 ? 置空树操作CLEAR(T):将树T置为空树。 5 数 据 结 构 之 树 和 二 叉 树 9 6. 2 二叉树 ? 二叉树的概念 1、特点:每个结点至多只有两棵子树,左右子树次 序不能改变。 2、满二叉树:一个深度为 k,且有 2 -1个结点的二 叉树。特点:树中没有度为 1的结点。 3、完全二叉树:当树中每个结点都与相同深度的满 二叉树中编号从 1至 n的结点一一对应。 一 般 二 叉 树 满 二 叉 树 完 全 二 叉 树 k 数 据 结 构 之 树 和 二 叉 树 10 例:有5种类型的二叉树如下 A A B A B A B C 空 只一个根结点 只有左子树 只有右子树 左右子树都有 6 数 据 结 构 之 树 和 二 叉 树 11 ? 二叉树的性质 1、在二叉树的第 i 层上至多有 2 个结点 (i>=1); 2、深度为 k的二叉树至多有 2 -1个结点 (k>=1); 3、对任何一棵二叉树 T,如果其终端结点数为 n0,度为 2的结点数为 n2,则 n0=n2+1。 4、具有 n个结点的完全二叉树的深度为 : ?log2 n? +1 k i-1 数 据 结 构 之 树 和 二 叉 树 12 5、如果对一棵有 n个结点的完全二叉树的结 点按层序编号(每层从左到右),则对任 一结点 i有 (1)可求得其双亲结点的序号为 ? i / 2 ? (i>1),i=1 时为二叉树的根结点 ,无双亲结点; (2)如果存在的话,可求得其左右孩子结点的 序号 :Rchild(i)=2i+1;Lchild(i)=2i; 7 数 据 结 构 之 树 和 二 叉 树 13 ? 二叉树的存储结构 1、顺序存储结构:用一组连续的 存储单元 Bt(1:n)存储二叉树的 数据元素。按完全二叉树的规 则对二叉树中的结点进行编号, 相应的空位也编上号,将二叉 树中编号为 i的结点的数据元 素存放在 Bt[i]中,若编号为 j 的结点为空,则 Bt[j]=0. 1 2 3 4 5 6 7 8 9 10 A B C 0 D E F 0 0 G 数 据 结 构 之 树 和 二 叉 树 14 ?二叉树顺序存储的算法描述 ? 初始化二叉树 #define Max_Size 100 typedef int TElemType; typedef TElemType SqBT[Max_Size+1]; void InitBT(SqBT bt){//设置空树 int i; for(i=1;i<=Max_Size;i++) bt[i]=0; } 8 数 据 结 构 之 树 和 二 叉 树 15 ? 插入一个元素的算法 void Insert(SqBT bt){ int k ,done=1; TElemType e; while (done) { printf("\n Number,data\n"); scanf("%d,%d",&k,&e); if(k<=Max_Size && bt[k/2]) { bt[k]=e; done=0; } else printf("\n\tERROR, input again."); } } 数 据 结 构 之 树 和 二 叉 树 16 ?创建二叉树的算法 void CreateBT(SqBT bt) { int n,i,k; TElemType e; printf("\n Please Input n="); scanf("%d",&n); bt[0]=1; printf("\n Number,data\n"); for(i=1;i<=n;i++) Insert(bt); } 9 数 据 结 构 之 树 和 二 叉 树 17 ?前序遍历顺序二叉树算法 void PreBT(SqBT bt,int i){ if(i>=Max_Size||!bt[i]) return; printf("%3d ",bt[i]); PreBT(bt,2*i); PreBT(bt,2*i+1); } 数 据 结 构 之 树 和 二 叉 树 18 ?中序打印二叉树的树形算法 void InBT(SqBT bt,int i,int k){ int j; if(i>Max_Size||!bt[i]) return; InBT(bt,2*i+1,k+10); for(j=0;j<k;j++) printf(" "); printf("%3d\n",bt[i]); InBT(bt,2*i,k+10); } 10 数 据 结 构 之 树 和 二 叉 树 19 ?后序遍历顺序二叉树算法 void PostBT(SqBT bt,int i){ if(i>Max_Size||!bt[i]) return; PostBT(bt,2*i); PostBT(bt,2*i+1); printf("%3d ",bt[i]); } 数 据 结 构 之 树 和 二 叉 树 20 ? 两棵顺序二叉树的比较算法 ?形状和元素值完全相同 Status EqualSq(SqBT bt, SqBT bt1){ for(i=1;i<=Max_Size;i++) if(bt[i]!=bt1[i]) return FALSE; return TRUE;} ?形状相同 Status SampleSq(SqBT bt, SqBT bt1){ for(i=1;i<=Max_Size &&((bt[i]&&bt1[i])||!(bt[i]||bt1[i]));i++); if(i<Max_Size) return FALSE; return TRUE;} 11 数 据 结 构 之 树 和 二 叉 树 21 ? 形状比较的递归算法 Status Compare(SqBT bt,SqBT bt1,int i){ Status k; if(bt[i] && bt1[i]) {if((k=Compare(bt, bt1, 2*i))) k=Compare(bt, bt1, 2*i+1);} else if(bt[i]==0 && bt1[i]==0) k=TRUE; else k=FALSE; return k; } 数 据 结 构 之 树 和 二 叉 树 22 ? 求元素 e在二叉树中层次 int FindNode(SqBT bt,int i,TElemType e){ static k; int k1; k++; if(i<Max_Size && bt[i] ){ if(e==bt[i]) k1=k; else if(!(k1=FindNode(bt,2*i,e))) k1=FindNode(bt,2*i+1,e); } else k1=0; k--; return k1; } 12 数 据 结 构 之 树 和 二 叉 树 23 ? 求二叉树 T的深度 int DepthBT(SqBT bt,int i){ int k1,k2; if(i>=Max_Size||!bt[i]) return 0; k1=DepthBT(bt,2*i); k2=DepthBT(bt,2*i+1); return(1+((k1>=k2) ? k1 : k2)); } 数 据 结 构 之 树 和 二 叉 树 24 ?前序打印树中所有结点的层次 void PrintNode(SqBT bt,int i){ static k; k++; if(i<Max_Size&&bt[i]){ printf("(%d,%3d) ",k,bt[i]); PrintNode(bt,2*i); PrintNode(bt,2*i+1);} k- -; } 13 数 据 结 构 之 树 和 二 叉 树 25 ?打印一维数组 void printSq(SqBT bt){ int i; printf("\nSeqArray:"); for(i=1;i<=Max_Size;i++) printf("%3d ",bt[i]); } 数 据 结 构 之 树 和 二 叉 树 26 MenuList(){ /*显示菜单 */ printf("\n\t........................................"); printf("\n\t\t1. Insert or change One ElemData"); printf("\n\t\t2. Pre, In and Post Order Traverse"); printf("\n\t\t3. Compare two btree"); printf("\n\t\t4. In Order Traverse Print Tree"); printf("\n\t\t5. Print SqList"); printf("\n\t\t6. Count the Depth of a Tree"); printf("\n\t\t7. Pre Order Print the Step of a Node"); 14 数 据 结 构 之 树 和 二 叉 树 27 printf("\n\t\t8. Locate a Elememt"); printf("\n\t\t9. Find Depth of a Element"); printf("\n\t\t10.Init Empty Tree"); printf("\n\t\t11.Create one tree"); printf("\n\t\t0. Exit"); printf("\n\t............................................... ....");} 数 据 结 构 之 树 和 二 叉 树 28 main(){ int choice; SqBT BT; TElemType e; static SqBT BT1={1,12,34,56,0,23,45,77,0,0, 12,34,89,12,0,0,0,0,0,0,1,22,0,3,45}; InitBT(BT); CreateBT(BT,BT1); while(1){MenuList(); printf("\Please Press your choice(0~10):"); scanf("%d",&choice); switch(choice){ case 1: Insert(BT);break; case 2: printf("Pre Order:");PreBT(BT,1); printf("\nIn Order:");InOrderBT(BT,1); printf("\nPost Order:"); PostBT(BT,1);break; 15 数 据 结 构 之 树 和 二 叉 树 29 case 3: printf("BT compare BT1 is %s", (Compare(BT,BT1,1)==True?"Sample":"No t Sample"));break; case 5: printSq(BT);break; case 4: printf("");InBT(BT,1,20); case 6: printf("\nThe depth of the tree is %d",DepthBT(BT,1));break; case 7: printf("\n");PrintNode(BT,1);break; 数 据 结 构 之 树 和 二 叉 树 30 case 8: case 9: printf("\n Input a data:"); scanf("%d",&e); printf("\nDepth(%d)=%d",e,FindNode(BT,1, e));break; case 10: InitBT(BT);break; case 11: CreateBT(BT,BT1);break; case 0: return; } } /* while(1)*/ } /*end of main*/ 16 数 据 结 构 之 树 和 二 叉 树 31 ?链式存储结构(二叉链表) ? 二叉树中的结点包含一个 数据元素和分别指向其左、 右子树的两个分支(指 针); ? C语言类型定义如下: typedef struct Btn{ TElemType data; struct Btn *lchild , *rchild; }BTN ,*BT; D 左子树 右子树 lchild data rchild 数 据 结 构 之 树 和 二 叉 树 32 ? 例:用二叉链表表示下面的二叉树。 A B C D E F G A ^ B ^ C ^ D ^ E ^ F ^ ^ g ^ 17 数 据 结 构 之 树 和 二 叉 树 33 二叉链表中指针域的情况 ? 在含有 n个结点的二叉链表中: ( 1)共有 2n个指针域,其中, ( 2)有 n+1个空链域, ( 3)实际使用了 n-1个链指针; 数 据 结 构 之 树 和 二 叉 树 34 ? 二叉树的基本操作 ?CreateBT(BT &T)//构造二叉链表表示的二叉树 T ?PreOrderT(BT T ) //前序遍历二叉树 T:打印每 个结点的数据域一次且仅一次 ?InOrderT(BT T) //中序遍历二叉树 T:打印每个 结点的数据域一次且仅一次 ?PostOrderT(BT T) //后序遍历二叉树 T:打印每 个结点的数据域一次且仅一次 ?LevelOrderT(BT T) //层序遍历二叉树 T:打印每 个结点的数据域一次且仅一次 18 数 据 结 构 之 树 和 二 叉 树 35 6.3 二叉树的遍历 ? 定义:二叉树的遍 历就是按一定的规 则访问树中的结点, 使得每个结点被访 问且仅被访问一次。 ? 前序遍历: 对非空二叉树: (1) 访问根结点; (2) 前序遍历左子树; (3) 前序遍历右子树。 前序遍历: A , B , D , C , E , F 数 据 结 构 之 树 和 二 叉 树 36 ? 中序遍历: 对非空二叉树 (1) 中序遍历左子树 ; (2) 访问根结点; (3)中序遍历右子树。 ? 后序遍历: 对非空二叉树: (1) 后序遍历左子树; (2) 后序遍历右子树 ; (3) 访问根结点。 中序遍历: D , B , A , E , C , F 后序遍历: D , B , E , F , C , A 19 数 据 结 构 之 树 和 二 叉 树 37 a c d e f / -b × + - + 遍历结果 : 中序 : a+b × c - d - e /f 后序 : abcd -× +ef / - 前序 : - +a × b- cd /ef ?例:表达式  a+b × (c-d)-e/f 数 据 结 构 之 树 和 二 叉 树 38 T A B C D E F T rchild lchild ?前序遍历递归算法 PreOrderT(BT T){ if(T){ printf(“ %d ,”,T→data ); PreOrderT(T→lchild); PreOrderT(T→rchild); }} 20 数 据 结 构 之 树 和 二 叉 树 39 ? 中序遍历递归算法 InOrderT(BT T){ if(T){ InOrderT(T→lchild); printf(“%d ,”,T→data ); InOrderT(T→rchild); } } 后序遍历递归算法 PostOrderT(BT T){ if(T){ PostOrderT(T→lchild); PostOrderT(T→rchild); printf(“%d ,”,T→data ); } } 数 据 结 构 之 树 和 二 叉 树 40 ?三种遍历过程示意图例 a c b × - a b * c - ba * - c a b * c - - × a b c ? 虚线表示执行过程 : 向下表示更深层的递归调用 ; 向上表示递归调用返回 ; ? 沿虚线记下各类符号 ,便得到遍历的结果 。 21 数 据 结 构 之 树 和 二 叉 树 41 前序 遍历 的非 递归 算法 + * E / D A ** B C 栈 +*/A**BCDE 输出 设立一个后 进先出栈, 用以存放结 点信息。 top=0 + + * / A + * / top=1 + * ** top=4 top=3 top=3 + * ** B top=4 + * C top=3 do{ while(p<>NULL) { printf(p->data); push(S,p) ;p=p->Lchild;} if(top<>0) { pop(S,p); p=p->rchild; } }while((top==0)&&(p== NULL)); 数 据 结 构 之 树 和 二 叉 树 42 ? 算法描述: 1、当前指针指向根结点。 2、若当前指针不为空,则访问该结点。 3、当前指针指向该结点的左孩子,并将该结点 指针入栈; 4、循环 2, 3步,直到当前指针为空; 5、若栈顶指针不为空,则弹出栈顶元素; 6、访问右孩子; 7、循环 2~6步,直到栈顶指针为空,或者当前 指针为空。 22 数 据 结 构 之 树 和 二 叉 树 43 void Preorder( BT T) { int BT p; p=T; initstack(S); do{ while(p<>NULL) { printf(“Results of Preorder is: %c ”, p->data); //访问当前结 点 push(S,p) //当前结点指针入栈 p=p->Lchild; //当前指针指向左孩子 } if(top<>0) { //若栈顶指针不为 0 pop(S,p); //弹出栈顶元素 p=p->rchild; } }while((top==0)&&(p== NULL)); //直到栈顶或当前指针为空 } 数 据 结 构 之 树 和 二 叉 树 44 中序遍历的非递归算法 过程: 先沿着树向左下方移动,直到不能再走为 止,然后访问结点,打印信息,往右移动一 个结点,再继续下去,如无法再往右移动, 则再后退一个结点,重新开始。 23 数 据 结 构 之 树 和 二 叉 树 45 + * ** top=3 + * ** B top=4 + * C top=3 + D top=2 E top=1 + * E / D A ** B C A/B**C*D+E 输出 栈 top=0 + + * / A + * / top=1 top=4 top=3 数 据 结 构 之 树 和 二 叉 树 46 InOrderT(BT T){ if(T) { Initstack(S); //建立结点指针型堆栈; p=T; do{ if ( p ) { Push( S, p ); p=p→ lchild; } else { Pop( S, p ) ; printf(“ %d “,p→ data); p=p → rchild; } } while ( 堆栈不空 || p ); } 24 数 据 结 构 之 树 和 二 叉 树 47 建 立 二 叉 树 链 式 存 储 的 算 法 描 述 利 用 完 全 二 叉 树 的 性 质 建 立 链 表 CreateBT( BT &T, int k, int m){ // BT nar[50]; 用于存储各结点的地址 n=pow(2,k)-1; for(i=1;i<=n;i++)nar[i]=^; for(i=1;i<=m;i++){ 输入结点的数据域值 x 和编号 j ; p=(BT)malloc(sizeof(BTN)); nar[j]=p ; p→ data=x ; p→ lchild=^; p→rchild=^; if(j==1) T=p ; else{ parent=(int)(j/2); q=nar[parent]; if( j % 2) q → rchild=p; else q → lchild=p; } } }//时间复杂度 O(m),空间复杂度 O(2 ) k 数 据 结 构 之 树 和 二 叉 树 48 建立链表 示意图 nar 123456789 10 ^^^ 2 A 3 ^ B 5 6 C 7 10 D ^ ^ E ^ ^ F ^ ^ G ^ 25 数 据 结 构 之 树 和 二 叉 树 49 按层次遍历二叉树的算法 void Levelorder(BT T){ if(T){ 建立结点指针型队列 Q; EnQueue(Q,T); while(!EmptyQueue(Q)){ DeQueue(Q,p); printf(p->data); if(p->lchild) EnQueue(Q,p->lchild); if(p->rchild) EnQueue(Q,p->rchile); } //while }//if }//时间复杂度 O(n),空间复杂度 O( 2 ) k 数 据 结 构 之 树 和 二 叉 树 50 按层次遍历二叉树的递归算法 void Levelorder(BT T){ if(T){ printf(T->data); if(T->lchild) EnQueue(Q,T->lchild); if(T->rchild) EnQueue(Q,T->rchile); DeQueue(Q,T); Levelorder(T); }//if }//时间复杂度 O(n),空间复杂度 O( 2 ) 空间复杂度包括栈和队列 k 26 数 据 结 构 之 树 和 二 叉 树 51 用层次遍历统计每层的结点数 void LeveNum(BT T , int A[ ]){ if(T){ 建立结点指针型队列 Q; EnQueue(Q,T);k=1;i=1; while(!EmptyQueue(Q)) { printf( i , k ); A[ i ]=k; n=0; for(j=0;j<k; j++) { DeQueue(Q,p); if(p->lchild) {EnQueue(Q,p->lchild);n++;} if(p->rchild) {EnQueue(Q,p->rchile);n++;} } //for k=n; i++; }//while A[ 0 ]=i-1; }//if }// 数 据 结 构 之 树 和 二 叉 树 52 用前序递归统计每层的结点数 void ProLeveNum(BT T , int A[ ] ){ // 数组 A的初值为全 0, A[0]记录树的高度,其余元素 记录每层的结点数 static k; // k为递归层次,也就是结点 T在树中的层次 k++; if(T){ A[k]++; if(k>A[ 0 ])A[ 0 ]=k; ProLeveNum(T->lchild, A); ProLeveNum(T->rchild,A); } k--; }// 27 数 据 结 构 之 树 和 二 叉 树 53 6.4 线索二叉树 充分利用二叉树中 n+1个空链域 ? 若结点有左子树,则其 lchild域指示其左 孩子,否则,令 lchild指示其前驱; ? 若结点有右子树,则其 rchild域指示其 右孩子,否则,令 rchild指示其后继; ? 修改结点结构: ltag lchild data rchild rtag 数 据 结 构 之 树 和 二 叉 树 54 0 lchild域指示结点的左孩子 ltag = 1 lchild域指示遍历时的结点前驱 0 rchild域指示结点的右孩子 rtag= 1 rchild域指示遍历时的结点后继 前序线索二叉树、中序线索二叉树、 后序线索二叉树 28 数 据 结 构 之 树 和 二 叉 树 55 ? 二叉树的二叉线索存储表示 typedef enum{Link,Thread}PointerTag; typedef struct Bthn{ TElemType data; struct Bthn *lchild,*rchile; PointerTag Ltag , Rtag ; }BTHN , *BTHT ; ? 指向结点前驱和后继的指针叫做线索; ? 加上线索的二叉树称为线索二叉树。 ? 对二叉树以某种次序遍历将其变为线索 二叉树的过程叫做线索化。 (参见 Page 134~135 算法 ) 数 据 结 构 之 树 和 二 叉 树 56 ? 中序线索二叉树 ? 查找结点p的后继: 所有叶子结点的右链直接指示 了后继,所有非终端结点的后继应是其右子树中第一 个中序遍历的结点 。 程序实现如下: void next(p,var,x) { q=p->rchild; if(p->rtag= =0){ // 若 rchild指向右孩子 do { //循环直到找到最左边的孩子 q=q->lchild; } while(q->ltag = =0); } //否则 rchild指向后继 x=q; } - + / a * e + b - c d NULL NULL 29 数 据 结 构 之 树 和 二 叉 树 57 ? 中序线索二叉树 ? 查找结点p的前驱 特点: 若其左标志为 “1”, 则左链为线索,指示其前驱, 其前驱为左子树上最后遍历 的一个结点。 - + / a * e + b - c d NULL NULL 程序实现如下: void pre(p,var,x) { q=p->lchild; if(p->ltag= =0){ // 若 lchild指向左孩子 do { //找到左孩子最右边的结点 q=q->rchild; } while(q->rtag = =0); } //否则 rchild指向前驱 x=q; } 数 据 结 构 之 树 和 二 叉 树 58 ? 中序线索链表的中序遍历 void InOrderTraverseThr( BiThrTree T) { BiThrTree p; p=T->lchild; // 指针 p初始化 while (p!=T) { while (p->ltag= =Link) p=p->lchild; printf(“Result of preorder is %c ”, p->data); //访问左 子树为空的结点 while(p->rtag= =Thread && p->rchild!=T) { p=p->rchild; printf(“Result of preorder is %c ”, p->data); // 访 问后继结点 } p=p->rchild; } } 30 数 据 结 构 之 树 和 二 叉 树 59 ? 中序线索化 线索化的实质是将二叉链表中的空指针该 为指向前驱或后继的线索,而前驱或后继的信 息只有在遍历时才能得到,因此线索化的过程 即为在遍历的过程中修改空指针的过程。 数 据 结 构 之 树 和 二 叉 树 60 Status InorderThreading( BiThrTree *Thrt, BiThrTree T) { if(!(Thrt=(BiThrTree)malloc(sizeof(BiThrNode)))) exit(OVERFLOW); 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 OK; } 31 数 据 结 构 之 树 和 二 叉 树 61 Void InThreading(BiThrTree p) { if(p){ 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); //右子树线索化 } } 数 据 结 构 之 树 和 二 叉 树 62 6.5 树和森林 ?树的存储结构 ? 双亲表示法:树中的结点由数据元素值和 指向其双亲结点的指针。 特点:求结 点的双亲很 容易,但求 结点的孩子 需要遍历整 个向量。 1 3 4 5 6 7 8 9 2 结点号 data parent 9 59 8 58 7 57 6 36 5 25 4 24 3 13 2 12 1 01 双亲表示法 32 数 据 结 构 之 树 和 二 叉 树 63 ? 孩子表示法:是把每个结点的孩子 结点链接形成单链表, n个结点有 n 个孩子链表(叶子 结点的孩子链表 为空)。 1 3 4 5 6 7 8 9 2 孩子链表 1 2 3 ^4 5 ^6 ^7 ^8 ^9 2 3 ^ 6 ^ 7 9 ^8 4 5 ^ 1 2 3 4 5 6 7 8 9 数 据 结 构 之 树 和 二 叉 树 64 ? 孩子兄弟表示法:又称二叉树表示法。 结点中的两个指针分别指向“第一个 孩子结点”和“下一个兄弟结点” 1 ^ 2 4^ 5 ^ 6 ^^ 7^ 8^ 9 ^^ 3 ^ 孩子兄弟表示法 1 3 4 5 6 7 8 9 2 33 数 据 结 构 之 树 和 二 叉 树 65 ? 树与二叉树之间的转换 ? 一般树转换成二叉树:加线、抹线、旋转。 ? 转换成的二叉树有两个特点: ?根结点没有右子树; ?转换生成的二叉树中各结点的右孩子是原来树中该 结点的兄弟,而该结点的左孩子还是原来树中该结 点的孩子。 ? 二叉树还原成一般树:加线、抹线、规整化。 ? 思考题:已知一棵一般树 T 的二叉链表存储, 写出求树中结点 q (q是结点指针 ) ?有多少孩子结点的算法; ?有多少兄弟结点的算法。 数 据 结 构 之 树 和 二 叉 树 66 D E F I B C G H J A D E F I B C G H J A A B C DE F G H I J 二叉树 例:将下面的树转换成二叉树。 34 数 据 结 构 之 树 和 二 叉 树 67 6.6 哈夫曼树及哈夫曼编码 ? 概念: ? 结点之间的路径:由从树中一个结点 到另一个结点之间的分支构成。 ? 路径长度:路径上的分支数目。 ? 树的路径长度:是从树根到每个结点 的路径长度之和。 问题:对结点数相同的树来说,路径 长度最短的是 二叉树。 数 据 结 构 之 树 和 二 叉 树 68 ? 叶子结点的带权路径长度:从该 结点到树根 之 间的路径长度 Lk与结点上的权值 Wk的乘积 Lk*Wk ? 树的带权路径长度:为树中所有叶子结点的带 n 权路径之和 WPL=∑ Wk*Lk k=1 ? 最优二叉树或哈夫曼树:假设有 n个权值 { w1, w2 , w3 , ... , wn } , 构造一棵有 n个叶子结点的二 叉树,每个叶子结点带一个权值,则其中带权 路径长度 WPL最小的二叉树称做最优二叉树或 哈夫曼树。 35 数 据 结 构 之 树 和 二 叉 树 69 例如:有一组权值为 ( 7, 5, 2, 3 ) ? 电文总长为 18个字符, a, b , c , d 出现的次数分别 为( 7, 5, 2, 3),制定各字符的编码。 a(00) b(01) c(10) d(11) 电报码长为 34 2*(7+5+2+3) a(010) b(011) c(1) d(00) 电报码长为 44 3*(7+5)+1*2+2*3 a(0) b(10) c(110) d(111) 电报码长为 32 1*7+2*5+3*(2+3) 7 7 7 5 2 3 3 5 2 3 5 2 01 01 01 01 01 01 01 01 01 数 据 结 构 之 树 和 二 叉 树 70 ? 构造哈夫曼树的步骤 ? 根据给定的 n个权值 { w1, w2 , w3 , ... , wn } , 构成 n棵二叉树的 集合 F ={ T1, T2 , T3 , ... , Tn } ,其中,每棵二叉树 Ti 中只有一个带权 为 wi 的根结点,其左右子树均空; ? 在 F 中选取两棵根结点的权值最小的树作为左 右子树构造一棵新的二叉树,且置新的二叉树 的根结点的权值为其左、右子树上根结点的权 值之和; ? 在 F 中删除这两棵子树,同时将新得到的二叉 树加入 F中; ? 重复 2 和 3 ,直到 F 只含一棵子树为止。 这棵树便是哈夫曼树 36 数 据 结 构 之 树 和 二 叉 树 71 例:根据下面的权值分布,试设计一棵具有最小 权值的二叉树。 a 7 b 5 c 2 d 4 树的生成过程 a b d 7 5 2 4 c c d 2 4 6 b c d 2 4 6 5 11 数 据 结 构 之 树 和 二 叉 树 72 ? 哈夫曼树的存储表示 一棵有 n个叶子结点的 Haffman 树共有 2n-1个结点,可以存储在一个大小为 2n-1 的一维 HTN型 数组中。(下标链) typedef struct { unsigned int weight ; unsigned int parent; unsigned int lchild, rchild ; } HTN , *HFT; 37 数 据 结 构 之 树 和 二 叉 树 73 ? 构造哈夫曼树的算法描述 void HaffmTree(HFT &HT , int w[ ] , int n ){ if(n<=1) return; m=2*n-1; /* n=n 2 +1, 0下标不用 HT=(HFT)malloc((m+1)*sizeof(HTN)); for(k=1;k<=n;k++) HT[k]={w[k],0,0,0 }; for(; k <=m;k++) HT[k]={0,0,0,0}; for(k=n+1;k<=m;k++){ Select(HT , k-1,s1,s2); HT[s1].parent=k; HT[s2].parent=k; HT[k].lchild=s1; HT[k].rchild=s2; HT[k].weight=HT[s1].weight+HT[s2].weight; } } 数 据 结 构 之 树 和 二 叉 树 74 ? 哈夫曼编码哈夫曼编码 ? 前缀编码:在设计长短不等的编码时,必 须是任一个字符的编码都不是另一个字符 的编码的前缀。 ? 可以用二叉树设计二进制的前缀编码。其 中,哈夫曼树的编码(哈夫曼编码)最短。 ? 哈夫曼树是严格的二叉树,即哈夫曼树中 没有度为 1的结点。 38 数 据 结 构 之 树 和 二 叉 树 75 例:根据下面字母出现的概率,采用哈夫曼 编码方法,设计每个字母的编码。 P(A)=0.25, P(B)=0.3, P(C) =0.15, P(D)=0.3。 解:根据每个字母出现的概率,按大到小的顺序排 列如下: A的编码: 11 B的编码: 10 C的编码: 01 D的编码: 00 P( D) =0.3 P( B) =0.3 P( A) =0.25 P( C) =0.15 0 1 0 1 0 1 数 据 结 构 之 树 和 二 叉 树 76 ? 从叶子到根逆向求哈夫曼编码 void HaffmCode(HFT &HT , char *HC[ ] , int n ){ if(n<=1) return; cd=(char*)malloc(n*sizeof(char)); cd[n-1]=‘\0’; for(k=1;k<=n;k++){ start=n-1; // start从后往前移动 for( c=k,f=HT[k].parent; f!=0 ; c=f,f=HT[f].parent) if(HT[f].lchild==c) cd[--start]=‘0’; else cd[--start]=‘1’; HC[k]=(char*)malloc((n-start)*sizeof(char)); strcpy(HC[k],&cd[start]); } free(cd); } 39 数 据 结 构 之 树 和 二 叉 树 77 ?本章重点 1、二叉树的基本概念和性质; 2、二叉树的存储方式:顺序和链式; 3、二叉树的遍历:先序、中序及后序遍历; 4、与二叉树有关的运算及程序设计; 5、 二叉树的线索化:先序、中序及后序线索化的存储和表示; 6、树的存储方式,树与二叉树的相互转换 ; 7、最优二叉树及哈夫曼编码。