理论基础
递归不要太想细节, 想大方向比较好 .( 树这多想递归 )
树 一般是有向 ↓ ( 从根到叶子 ) 无序 ←→
线形结构 | 树形结构 |
---|---|
第一个元素无前驱 | 根无前驱 |
最后一个元素无后继 | 多个叶子结点无后继 |
其他元素一个前驱一个后继 | 其他结点一个前驱多个后继 |
树是从家族的族谱来的 , ( 双亲 , 儿子 )
定义 : 根 和 子树 , 子树之间互不相交 . ( 递归定义 ) 树 = ( 根 , 子树 )
基本概念 :
树的结点 : 包含一个数据元素及若干指向其子树的分支。
结点的度 : 结点拥有的子树( 直接 ) 数
叶子 : 度为 0 ( 也叫终端结点 )
分支结点 : 度不为 0 ( 也叫非终端结点 ) ( 根除外 )
树的度 : 树内各个结点的度的最大值
结点的子树的根称为该结点的 孩子 , 该结点成为孩子的 双亲 ,同一个双亲的孩子之间互称 兄弟
结点的祖先 是从根到该结点所经分支上的所有结点 , 以某结点为根的子树中的任一结点都称为该结点的 子孙
结点的层次 : 从根开始定义起 , 根为第一层 , 根的孩子为第二层 , 依此类推
双亲在同一层的结点互为 堂兄弟
树中结点的最大层次称为 树的深度
树中结点的各个子树看成是从左到右有次序的 , 成为有序树 , 否则为无序树 ( 一般都是无序树 )
森林 是 m( m>=0 ) 棵互不相交的书的集合
树的存储方式 :
1. 双亲表示
typedef struct PTNode{
ElemTYpe data ; /* 存储数据 */
int parent ; /* 在数组中的位序*/
}PT ;
整个树的结构是以上结点的集合 :
typedef struct {
PTNode nodes[ MAX_TREE_SIZE ];
int r, n ; /* 根结点所在位置, 结点个数*/
}PTree ; /* 双亲表示法 */
( n 实际为 7 , 图上一点点错误 )
2. 孩子链表
孩子结点结构
typedef struct CTNode {
int child ;
struct CTNode *next ;
} * ChildPtr ;
双亲节点结构
typedef struct CTBox {
ElemType data ;
ChildPtr firstChild ; /* 第一个孩子的指针, 之后顺序向后找到所有的孩子*/
};
整个树的结构是双亲节点的集合
typedef struct {
CTBox nodes[ Max_node_size] ;
int n , r ; /* n结点数和根的位置*/
}CTree ;
( n =7 )
3. 孩子兄弟链表 ( 可以实现树的操作转换为二叉树 )
typedef struct CSNode {
ElemType data ;
struct CSNode *filrstchild, *nextsibling ; /*左孩子, 右兄弟*/
} CSNode, *CSTree ;
森林和二叉树的对应转换关系 : ( 递归 )
设森林 F = ( T1, T2, T...... ) ( 除 T1外, 其余的为最后二叉树的右子树 )
T1 = ( root , t11, t12, ... )其中 root 为 T1 这棵子树的根 , 其余的为子树森林 ( root 为最后二叉树的根, 子树为最后二叉树的左子树 )
二叉树 B = ( LBT, Node(root) , RBT ) ;
二叉树转变为森林 : ( 递归, 上边倒过来了 )
由 Node(root) 对应得到 ROOT( T1) 二叉树的根变成森林第一棵树的根
由 LBT 得到 ( t1, t2, t... ) 二叉树的左子树变成第一棵树的子树森林
由 RBT 得到 ( T2, T3, T...) 二叉树的右子树得到其他子树
树和森林的遍历 :
树的遍历 :
1. 先序遍历 ( 根 , 第一子树 , 其他子树 )
2. 后序遍历 ( 左孩子-》第一棵子树,其他子树, 根 )
3. 按照层次
树的先根遍历对应森林的先序遍历对应二叉树的先序遍历
树的后根遍历对应森林的中序遍历对应二叉树的中序遍历
森林遍历 :
1. 先序遍历 ( 根 , 第一子树森林 , 其他子树 )
2. 中序遍历 ( 左孩子-》第一棵子树森林, 根, 其他子树 )
3. 按照层次
二叉树 : 每个结点至多只有两棵子树 ( 二叉树中不存在度大于2的结点 ) ,二叉树的子树有左右之分 , 其次序不能任意颠倒 . ( 有序树 )
二叉树的 5 种状态 : 空二叉书 , 仅有根结点的二叉树 , 右子树为空的二叉树 , 左,右子树非空的二叉树 , 左子树为空的二叉树
二叉树的性质 :
a. 在二叉树的第i 层上至多有 2 i – 1 个结点 ( i >=1 )
b. 深度为 k 的二叉树至多有 2k - 1 个结点 ( k >= 1)
c. 对任何一棵二叉树 T , 如果其终端结点数为n0 , 度为 2 的结点数为 n2 则 n0 = n2 + 1
满二叉树 : 深度为 k 且有 2k– 1 个结点 ( 除叶子结点 , 全都是度为 2 的结点 ) , 编号的 满二叉树 为完全二叉书 ( 编号顺序 ↓ → )
完全二叉树的特性 :
a. 具有 n 个结点的完全二叉树的深度为 log2n + 1 ( log2n 向下取整 ).
b. 完全二叉树按层次编号 i 层
如果 i =1 则为二叉树的根 , 无双亲 , 否则双亲为 i/2 .
如果 2i > n ( 结点总数 ), 则 结点 i 无左孩子( 结点 i 为叶子结点 ) 否则 左孩子为 2i
如果 2i + 1 > n , 则结点 i 无右孩子 , 否则右孩子为 2i + 1
二叉树的存储结构
1. 顺序存储结构 ( 参照完全二叉树存储 , 参照完全二叉树的编号 )
#define MAX_TREE_SIZE 100
typedef TElemType SqBiTree[ MAX_TREE_SIZE ] ;
SqBiTree bt ;
例如 :
a | b | c | d | e | 0 | 0 | 0 | 0 | f | g |
如上 : 顺序存储会浪费很多空间 ( 除非是完全二叉树 ) , 所以不推荐使用
2. 链式存储结构 ( 二叉链表 , 三叉链表 )
二叉链表 : lchild - data - rchild ( 结点包含三个域 )
三叉链表 : lchild - data - parent - rchild ( 结点包含四个域 )
链表的头指针指向二叉树的 根 结点
定义 :
typedef struct BiTNode {
TElemType data ; // 例如 char型
struct BiTNode *lchild , *rchild ; //左右孩子指针
} BiTNode , *BiTree ; // 二叉链表
遍历二叉树 : 需要找到一种规律 , 使二叉树上的结点排列在一个线性队列上 , 从而便于遍历
先序遍历 : 根 左 右
中序遍历 : 左 根 右
后序遍历 : 左 右 根
表达式求值中的 : 逆波兰式 ( 先序 , 中序 ,后序 ) 正好对应 .
二叉链表基本操作 :
/* The Basic operate for 二叉树,采用二叉链表存储方式
* Author : Kevin
* Date : 2012.03.06
* 树这会用到很多递归
* */
#include"stdio.h"
#include"stdlib.h"
#include"alloc.h"
/*树中结点存储结构*/
typedef struct BiTNode{
char data; /*存储数据*/
struct BiTNode *lchild , *rchild; /*左右孩子*/
}BiTNode,*BiTree;
/*销毁二叉树*/
void DestoryBiTree(BiTree t){
if(!t->lchild && !t->rchild){
free(t);
}else{
while(t->lchild){
DestoryBiTree(t->lchild);
}
while(t->rchild){
DestoryBiTree(t->lchild);
}
}
}
/*清空二叉树*/
void ClearBiTree(BiTree t){
t->data = '';
if(t->lchild){
ClearBiTree(t->lchild);
}
if(t->rchild){
ClearBiTree(t->rchild);
}
}
/*判空二叉树*/
int BiTreeEmpty(BiTree t){
if(t == NULL){
return 1;
}
else
return 0;
}
/*求二叉树深度*/
int BiTreeDepth(BiTree t){
int suml = 0; /*左子树深度*/
int sumr = 0; /*右子树深度*/
if(suml >= sumr){
return suml + 1;
}else
return sumr + 1;
}
/*返回二叉树的根*/
char Root(BiTree t){
return t->data ;
}
/*插入结点,p为双亲结点指针,lr为左右孩子标记,c为值*/
void InsertChild(BiTree t, BiTree p, int lr, char c){
if(t == p){
if(lr ==0){
p->lchild = (BiTree)malloc(sizeof(BiTNode));
p->lchild->lchild = NULL;
p->lchild->rchild = NULL;
p->lchild->data = c;
}else{
p->rchild = (BiTree)malloc(sizeof(BiTNode));
p->rchild->rchild = NULL;
p->rchild->rchild = NULL;
p->rchild->data = c;
}
}else{
if(t->lchild)
InsertChild(t->lchild, p, lr, c);
if(t->rchild)
InsertChild(t->rchild, p, lr, c);
}
}
/*删除结点,p为双亲结点,lr为左右孩子标记*/
void DeleteChild(BiTree t, BiTree p, int lr){
if(t == p){
if(lr ==0){
free(p->lchild);
}else{
free(p->rchild);
}
}else{
if(t->lchild)
DeleteChild(t->lchild, p, lr);
if(t->rchild)
DeleteChild(t->rchild, p, lr);
}
}
遍历操作 :
/* 6.1 遍历二叉树 , 先序 , 中序 , 后序 ( 递归实现 )
** Author : Kevin
** Date : 2011/11/30
*/
#include"stdio.h"
#include"alloc.h"
#include"stdlib.h"
#define ELEM int
#define Status int
typedef struct MyTree{
ELEM myNode ; /*节点定义*/
struct myTree *leftChild ; /*左子树*/
struct myTree *rightChild ; /*右子树*/
}MyTree , *MyLTree ;
/*按照定义递归构造二叉数*/
void CreateTree( MyLTree mt , int level ){
int du ;
int i ;
int j ;
int leftOrRight ;
MyLTree p = NULL ;
MyLTree q = NULL ;
q = mt ;
if( !mt->leftChild || !mt->rightChild ){
mt->leftChild = NULL ;
mt->rightChild = NULL ;
}
printf("&& Please input this %d nodes du ;\n", level);
scanf("%d",&du) ;
if(du > 2 || du < 0){
printf("Error occur , du is error !\n");
return 0 ;
}
else if( du == 1){
printf("Please goon input the left or right , 1 left , 2 right !\n");
scanf("%d",&leftOrRight);
if(leftOrRight == 1){
for(j=0; j<du; j++){
p = (MyLTree)malloc(sizeof(MyTree));
p->leftChild = NULL ;
p->rightChild = NULL ;
printf("please input %d left child value \n",level );
scanf("%d",&p->myNode);
q->leftChild = p ;
CreateTree(p,p->myNode);
}
}
else{
for(j=0; j<du; j++){
p = (MyLTree)malloc(sizeof(MyTree));
p->leftChild = NULL ;
p->rightChild = NULL ;
printf("please input %d right child value \n",level);
scanf("%d",&p->myNode);
q->rightChild = p ;
CreateTree(p,p->myNode);
}
}
}
else if ( du == 2){
for(j=0; j<du; j++){
if( j==0 ){
p = (MyLTree)malloc(sizeof(MyTree));
p->leftChild = NULL ;
p->rightChild = NULL ;
printf("please input the %d left child value \n",level);
scanf("%d",&p->myNode);
q->leftChild = p ;
CreateTree(p,p->myNode);
}else{
p = (MyLTree)malloc(sizeof(MyTree));
p->leftChild = NULL ;
p->rightChild = NULL ;
printf("please input the %d right child value \n",level);
scanf("%d",&p->myNode);
q->rightChild = p ;
CreateTree(p,p->myNode);
}
}
}
else{
printf("this is the leaf node\n ");
}
}
/*先序*/
void preOrderTree(MyLTree mt){
printf(" %d\n ",mt->myNode);
if(mt->leftChild){
preOrderTree(mt->leftChild) ;
}
if(mt->rightChild){
preOrderTree(mt->rightChild);
}
}
/*中序*/
void inOrderTree(MyLTree mt){
if(mt->leftChild){
inOrderTree(mt->leftChild);
}
printf(" %d\n ",mt->myNode);
if(mt->rightChild){
inOrderTree(mt->rightChild);
}
}
/*后序*/
void posOrderTree(MyLTree mt){
if(mt->leftChild){
posOrderTree(mt->leftChild);
}
if(mt->rightChild){
posOrderTree(mt->rightChild);
}
printf(" %d\n ",mt->myNode);
}
int main(){
MyLTree pm ;
int t = 1 ;
pm = (MyLTree)malloc(sizeof(MyTree));
pm->leftChild = NULL ;
pm->rightChild = NULL ;
printf("*************Please input the root node value : ****************\n");
scanf("%d",&pm->myNode);
CreateTree( pm , t ) ;
printf("\n");
/*先序遍历*/
printf("The pre as below :\n");
preOrderTree( pm );
printf("\n");
printf("The in as below :\n");
/*中顺遍历*/
inOrderTree( pm );
printf("\n");
printf("The pos as below :\n");
/*后顺遍历*/
posOrderTree( pm );
printf("\n");
printf("Please input any key to continue (^_^)\n ");
getchar() ;
getchar() ;
}
按照定义创建二叉树
Status CreateBiTree(BiTree &T){
scanf(&ch);
if(ch == '')
T = NULL;
else{
if(!(T=(BiTNode *)malloc(sizeof(BiTNode)))) exit(OVERFLOW);
T->data = ch;
CreateBiTree(T->lchild);
CreateBiTree(T->rchild);
}
}
输入 ABC___D_E__ ( 下划线代表空格 )
利用二叉树存储表达式 ( 表达市求值 )
一般情况 : 左子树( 存储第一操作数 ) , 右子树 ( 存储第二操作数 ) , 根 ( 存储运算符 ) . 如果第一操作数或者第二操作数还是个表达式 , 则递归之 .
例如 : ( a+ b ) *c - d / e , 按照先缀表示法为 : - * + a b c / d e , 那么如果根据先缀生成一棵二叉树 ( 按照先序遍历 ) , 由于此操作树比为满二叉树, 另外形式为 根是运算符,两个儿子分别是第一操作数和第二操作树, 同样后序也可以生成一个二叉树, 只不过需要后序生成 .操作数必为一个叶子结点 . 运算符的左右子树都不空,
前缀和后缀表达式, 都可以建树 .
原表达式建树 : 原表达式 -> 后缀 -> 树 ( 一起形成 )
建立两个栈(分别存运算符和操作数), 当形成一个小的子树时, 保存子树的根指针,依此类推.
线索二叉树
遍历二叉树的结果是求得结点的一个线性序列, 指向该线性序列的"前驱"和"后继"的指针, 称为"线索".
作用 : 当以二叉树作为存储结构时, 只能找到结点的左右孩子, 不能找到它在线性序列中的前驱和后继, 所以就形成了线索二叉树.
线索二叉树存储结构 :
左子树为空, 加前驱. 右子树为空, 加后继.( 这里要注意, 线索二叉树的前驱与后继是要根据遍历的顺序决定的 )
typedef enum PointerTag{Link, Thread } PointerTag ; // link ==0 指针 , Trhread == 1 线索
typedef struct BiThrNode{
ElemType data ;
struct BiThrNode *lchild , *rcihild ; // 左右孩子
PointerTag LTag, RTag ; //左右孩子标记, 指针还是线索
} BiThrNode, *BiThrTree ;
赫夫曼树 ( 最优二叉树 )
构造赫夫曼树 : 每次拿最小的两个权值的节点, 组成一棵子树 ( 子树权值等于这两棵子树的和 ) , 删除集合中的这两棵子树, 并同时把这个树加入到集合中, 重复以上的步骤。
赫夫曼编码 : 将以下编码的字符存储在二叉树的叶子结点上, 并且权值为编码字符出现的次数.
前缀编码 : 任一字符的编码都不是另一个字符的编码的前缀.
存储结构 :
typedef strcut{
unsigned int weight ; //权值
unsigned int parment , lchild , rchild ;
} HTNode , * HuffmanCode ;