专题--树
一、树
1. 树的存储结构
树的存储结构有:顺序存储和链式存储
四种表示法:双亲表示法、孩子表示法、双亲孩子表示法和兄弟表示法
不同表示下的树结构定义:
1 /*树的存储结构有:顺序存储和链式存储*/ 2 /*三种表示法:双亲表示法、孩子表示法、双亲孩子表示法和兄弟表示法*/ 3 const int max_size=100; 4 5 //双亲表示法结构(顺序存储) 6 struct PTNode 7 { 8 int data; 9 int parent; 10 }; 11 struct PTree 12 { 13 PTNode nodes[max_size]; //节点数组 14 int r,n; //根的位置和节点数 15 }; 16 //孩子表示法结构(顺序存储+链式存储) 17 struct CTNode //孩子节点 18 { 19 int child; 20 CTNode *next; 21 }; 22 typedef CTNode *childPtr; 23 struct CTBox //表头节点 24 { 25 int data; 26 childPtr firstChild; 27 }; 28 struct CTree //树结构 29 { 30 CTBox nodes[max_size]; //节点数组 31 int r,n; //根的位置和节点数 32 }; 33 //双亲孩子表示法(顺序存储+链式存储) 34 struct PCTNode //孩子节点 35 { 36 int child; 37 PCTNode *next; 38 }; 39 typedef PCTNode *childPtr; 40 struct PCTBox //表头节点 41 { 42 int data; 43 int parent; //只需在表头节点中增加一个parent数据 44 childPtr firstChild; 45 }; 46 struct PCTree //树结构 47 { 48 PCTBox nodes[max_size]; //节点数组 49 int r,n; //根的位置和节点数 50 }; 51 //孩子兄弟表示法(链式存储) 52 struct CSNode 53 { 54 int data; 55 CSNode *firstChild,*rightSib; //两指针:指向第一个孩子和右兄弟 56 }; 57 typedef CSNode *CSTree;
二、二叉树
2. 二叉树的定义
如上所述的 "孩子兄弟表示法" 的最大用处就是,它可以把一颗复杂的树变成一棵二叉树。
2.1 二叉树的特点
二叉树的特点有:
1) 每个节点最多有两棵子树,所以二叉树中不存在读大于2的节点;
2) 左子树和右子树是有顺序,次序不能任意颠倒;
3) 即使树中只有一棵子树,也要区分它是左子树还是右子树。
二叉树有五种形态:空二叉树;只有一个根节点、根节点只有左子树/右子树、根节点既有左子树又有右子树。
2.2 特殊二叉树:
1) 斜树:所有的节点都只有左子树/只有右子树。
2) 满二叉树:所有分支节点都存在左子树和右子树,并且所有叶子都在同一层上。
3) 完全二叉树:对一棵具有n个节点的二叉树按层序编号,如果i 的节点与同样深度的满二叉树中编号为i 的节点在二叉树中位置完全相同,则这棵二叉树称为完全二叉树。
满二叉树一定是一棵完全二叉树,但完全二叉树不一定是满的。
2.3 完全二叉树的特点:
1) 叶子节点只能出现在最下两层;
2) 最下层的叶子一定集中在左部连续位置;
3) 倒数第二层,若有叶子节点,一定都在右部连续位置;
4) 如果节点度为1,则该节点只有左孩子,即不存在只有右子树的情况;
5) 同样节点数(n)的二叉树,完全二叉树的深度最小。
3. 二叉树的性质
五大性质(见《大话数据结构》P169-P171).
4. 二叉树的存储结构
顺序存储和链式存储,这里主要介绍“二叉链表”:
//二叉链表结构 struct BiNode { int data; //数据域 BiNode *lchild,*rchild; //指针域 }; typedef BiNode *BiTree;
“二叉链表” 结构与 “孩子兄弟表示法” 基本一致,在此基础上还可以增加一个指向其双亲的指针域,那样就称之为“三叉链表”。
5. 遍历二叉树
遍历:从根节点出发,按次序一次访问树中所有节点一次。
关键词:访问和次序
三种遍历算法:前序遍历、中序遍历和后序遍历。
代码实现:
1 //前序遍历 2 void PreTraverse(BiTree T) 3 { 4 if(T==nullptr) 5 return; 6 cout<<T->data<<endl; //显式节点数据,可以更改为其他对节点的操作 7 PreTraverse(T->lchild); //先序遍历左子树 8 PreTraverse(T->rchild); //先序遍历右子树 9 } 10 //中序遍历 11 void InTraverse(BiTree T) 12 { 13 if(T==nullptr) 14 return; 15 InTraverse(T->lchild); 16 cout<<T->data<<endl; 17 InTraverse(T->rchild); 18 } 19 //后序遍历 20 void PostTraverse(BiTree T) 21 { 22 if(T==nullptr) 23 return; 24 PostTraverse(T->lchild); 25 PostTraverse(T->rchild); 26 cout<<T->data<<endl; 27 }
5.1 推导遍历结果
已知前序和中序遍历序列,可以唯一确定一棵二叉树;已知后序和中序遍历序列,也可唯一确定一棵二叉树。
例1:通过前序和中序序列,确定二叉树:
1) 根据 "前序序列第一个节点是根节点",确定根节点;
2) 根据根节点的data ,在中序序列中找到根节点,“位于位于中序序列根节点左边的序列为根节点的左子树,右边的序列为右子树”;
3) 递归进入左子树和右子树。
6. 树、森林与二叉树的转换
见《大话数据结构》P195-199.
7. 赫夫曼树及其应用
7.1 赫夫曼树的定义与原理
从树中一个结点到另一个结点之间的分支构成两个节点之间的路径,路径上的分支数目称作路径长度。
树的路径长度就是从树根到每一个结点的路径长度之和。
考虑带权的结点,结点的带权的路径长度为从该结点到树根之间的路径长度与结点上权的乘积。
树的带权路径长度为树中所有叶子节点的带权路径长度之和。
带权路径长度WPL最小的二叉树称作赫夫曼树(最优二叉树)。
构造赫夫曼树的赫夫曼算法:
1) 根据给定的n个权值{w1,w2,...wn} 构成n棵二叉树的集合F={T1,T2,...,Tn},其中每棵二叉树Ti 中只有一个带权为wi 根节点,其左右子树均为空。
2) 在F 中选取两棵根结点的权值最小的树作为左右子树构造一棵新的二叉树,且置新的二叉树根结点的权值为其左右子树上根结点的权值之和。
3) 在F 中删除这两棵树,同时将新得到的二叉树加入F 中。
4) 重复2和3步骤,直到F 中只含一棵树为止。这棵树便是赫夫曼树。
7.2 赫夫曼编码
设需要编码的字符集为{d1,d2,...,dn},各个字符在电文中出现的次数或频率集合为{w1,w2,...,wn},以d1,d2,...dn 作为叶子结点,以w1,w2,w3,...wn 作为相应叶子结点的权值来构造一棵赫夫曼树。规定赫夫曼树的左分支代表0,右分支代表1,则从根节点到叶子结点所经过的路径分支组成的0和1的序列变为该结点对应字符的编码,这就是赫夫曼编码。
更多“赫夫曼编码”知识,见《算法导论》P245.