目录
一、定义
树是一种常用的一种结构,下面看看有哪些关于树的定义。
根结点、孩子结点、兄弟结点、叶子结点、内部结点、节结的度、树的度,如下图:
满二叉树:一棵深度为k且有个结点的二叉树称为满二叉树。其特点:1、每一层上的结点数都是最大结点数(即每层都满),2、叶子结点全部在最底层。如下图:
完全二叉树:深度为k的具有n个结点的二叉树,当且仅当其每一个结点都与深度为k的满二叉树中编号为1~n的结点一一对应时,称之为完全二叉树。如下图:
二、性质(二叉树性质)
二叉树(每个节点最多有两个孩子节点)是常用的一种树结构, 有如下些性质:
性质1:在二叉树的第i层上至多有个节点(i1)。
性质2:深度为k的二叉树至多有个节点(k1)。
性质3:对任意一颗二叉树T,如果其叶子节点个数为,度为2的节点个数为,则 = + 1。
证明,利用两种计算边的方法。每个节点都和一个父节点有条边相连,度为二的节点有两个边分别与左右孩子节点相连,度为1的节点则有一条边和孩子节点相连,两者计算结果相等。如下图:
性质4:具有n个结点的完全二叉树的深度为 + 1。称为x的低,表示不大于x的最大整数。
三、树的存储(二叉树存储)
1、二叉树的顺序存储
实现:按照满二叉树的结点层次编号,依次存放二叉树中的数据元素。如下几张图:
代码上,使用数组或者顺序表都可以,需要注意的是,没有结点的编号一般使用#代替,表示其孩子结点为NULL。
2、二叉树的链式存储
二叉树的链式定义如下:
typedef struct Tree{ // 二叉树链式存储
char data;
struct Tree * lchild; // 左孩子指针
struct Tree * rchild; // 右孩子指针
}TNode;
四、树的遍历(二叉树的遍历,采用链式存储)
二叉树可以按照先序遍历、中序遍历、后续遍历和层次遍历方式遍历。
1、二叉树先序遍历(递归函数实现)
void dfs_xian(TNode * tree){ // 递归先序访问二叉树
if(tree == NULL)
return ;
printf("%c\n",tree->data);
dfs_xian(tree->lchild); // 递归访问该结点的左孩子结点
dfs_xian(tree->rchild); // 递归访问该结点的右孩子结点
}
2、二叉树中序遍历(递归函数实现)
void dfs_zhong(TNode * tree){ // 递归中序访问二叉树
if(tree == NULL)
return ;
dfs_zhong(tree->lchild); // 递归访问该结点的左孩子结点
printf("%c\n",tree->data);
dfs_zhong(tree->rchild); // 递归访问该结点的右孩子结点
}
3、二叉树后序遍历(递归函数实现)
void dfs_hou(TNode * tree){ // 递归后序访问二叉树
if(tree == NULL)
return ;
dfs_hou(tree->lchild); // 递归访问该结点的左孩子结点
dfs_hou(tree->rchild); // 递归访问该结点的右孩子结点
printf("%c\n",tree->data);
}
4、二叉树层次遍历(顺序队列实现)
关于队列这篇文章有详细介绍和代码:数据结构与算法 队列_JAVA5120162041的博客-CSDN博客
#define MAXSIZE 100
typedef struct{ // 顺序循环队列
TNode * data[MAXSIZE]; // 静态开辟空间
int front; // 队头指针
int rear; // 队尾指针
}SqQueue;
bool QueueEmpty(SqQueue Q){ // 判断队列是否为空
return Q.front == Q.rear;
}
void InQueue(SqQueue &Q,TNode * e){ // 循环队列入队
if((Q.rear + 1) % MAXSIZE == Q.front) // 队满退出函数
return ;
Q.data[Q.rear] = e; // 进入队尾排队
Q.rear = (Q.rear + 1) % MAXSIZE; // 队尾指针加一
}
void OutQueue(SqQueue &Q,TNode * &e){ // 循环队列出队
if(Q.front == Q.rear) // 队空退出函数
return ;
e = Q.data[Q.front]; // 出队到引用e
Q.front = (Q.front + 1) % MAXSIZE; // 头指针往后移
}
void bfs(TNode * T){ // 二叉树的层次遍历函数
TNode * p;
SqQueue Q;
InQueue(Q,T); // 根节点先指针入队列
while(!QueueEmpty(Q)){ // 队列不为空时
OutQueue(Q,p); // 队头元素出队列到p
printf("%c",p->data); // 访问p节点
if(p->lchild != NULL)
InQueue(Q,p->lchild); // 左孩子入队
if(p->rchild != NULL)
InQueue(Q,p->rchild); // 右孩子入队
}
}
五、树的建立(二叉树的建立,采用链式存储)
由二叉树的先序序列和中序序列,或由二叉树的后序序列和中序序列可以确定唯一棵二叉树。下面就分别用代码实现一下。
1、由先序遍历结果建立二叉树(递归实现)
按理说还需要中序序列才能确定一颗二叉树,但是如果在空结处以#代替,采用递归是可以建立该二叉树的。代码如下:
char ch;
void CreTree(TNode * &T){ // 利用先序遍历结果建立二叉树
scanf("%c",&ch);
if(ch == '#')
T = NULL;
else{
T = (TNode *)malloc(sizeof(TNode));
T->data = ch; // 生成根节点
CreTree(T->lchild); // 递归建立左子树
CreTree(T->rchild); // 递归建立右子树
}
}
六、树的其他操作
1、统计二叉树叶子结点数目
int LeafCount(TNode * T){ // 统计二叉树叶子节点
if(T == NULL) // 为空返回0
return 0;
if(T->lchild == NULL && T->rchild == NULL) // 左右孩子结点都为空时返回1
return 1;
else
return LeafCount(T->lchild) + LeafCount(T->rchild); // 否则返回孩子结点的叶子结点的和
}
2、计算二叉树的结点总数
int NodeCount(TNode * T){ // 计算二叉树的节点总数
if(T == NULL) // 为空返回0
return 0;
else // 否则返回孩子结点数目 + 1(自己)
return NodeCount(T->lchild) + NodeCount(T->rchild) + 1;
}
3、求二叉树的深度
int Depth1(TNode *T){ // 求二叉树的深度 方法一
if(T == NULL) // 空返回0
return 0;
else{ // 否则返回孩子结点中最深的深度
int m = Depth1(T->lchild);
int n = Depth1(T->rchild);
return m > n ? m + 1 : n + 1;
}
}
int depth = 0;
int max = 0;
void Depth2(TNode * T){ // 求二叉树的深度 方法二
if(T == NULL)
if(max < depth)
max = depth;
else{
depth ++; // 往下遍历时深度加一
Depth2(T->lchild); // 递归左孩子结点
Depth2(T->rchild); // 递归右孩子结点
depth --; // 往回回溯时,此刻深度减一
}
}
4、复制一颗二叉树
void CopyTree(TNode * T, TNode * &newT){ // 复制二叉树,新树以newT为根结点
if(T == NULL)
newT = NULL;
else{
newT = (TNode *)malloc(sizeof(TNode));
newT->data = T->data; // 复制数据域
CopyTree(T->lchild, newT->lchild); // 递归复制左子树
CopyTree(T->rchild, newT->rchild); // 递归复制右子树
}
}
七、其他
这部分内容以介绍为主,应用的比前面相对少一些,如果感兴趣可以继续往下看。
1、线索二叉树
利用二叉链表中的空指针域:
如果某个结点的左孩子为空,则将空的左孩子指针域改为指向其前驱; 如果某结点的右孩子为空,则将空的右孩子指针域改为指向其后继。这种改变指向的指针称为“线索"。加上线索的二叉树称为线索二叉树。对二叉树按某种遍历次序使其变为二叉树的过程叫线索化。下图为一颗线索二叉树。
为了区分lchild和rchild指针到底是指向孩子的指针还是指向前驱或者后续的指针,对二叉链表中每个结点增设两个标志域ltag和rtag,并约定:
ltag = 0 lchild指向该结点的左孩子 ltag = 1 lchild指向该结点的前驱 rtag = 0 rchild指向该结点的右孩子 rtag = 1 rchild指向该结点的后续
其结构定义如下:
下面看一个例子:
2、树的存储结构
3、树与二叉树之间的转换
4、森林与二叉树之间的转换
八、哈夫曼树与哈夫曼编码
1、相关概念的理解
2、哈夫曼树的构造
3、哈夫曼构树造算法的实现
伪代码如下:
4、哈夫曼编码
根据哈夫曼表求哈夫曼树: