树:一种一对多的非线性数据结构
树的结点数目可以是0,这种树称之为空树
树的定义是递归的,由唯一的根节点递归定义出互不相交的子树
树的基本概念
结点:略
结点的度:结点的子树个数 / 分支的个数
树的度:一个树中各个结点的最大结点度
叶子结点:终端结点,指度为0的结点
非终端结点:分支结点,不是叶子结点的结点,即度不为0的结点
孩子和双亲:略
兄弟:略
祖先:从根到某结点路径上所有的结点,都是该结点的祖先
子孙:略
层次:略
树的高度 / 深度:树中结点的最大层次
堂兄弟:双亲在同一层次的结点互为堂兄弟
森林:略
树的存储结构
树的顺序存储结构的双亲表示法:直接使用一个一维数组来表示树。
int tree[maxsize];
这种方式中,数组的下标表示树的结点,数组中的数据表示是该结点的双亲结点。
树的链式存储结构的孩子表示法:即邻接表存储结构。
// 枝干结构体定义
typedef struct Branch{
int index;
struct Branch *next;
}Branch;
// 结点结构体定义
typedef struct TNode{
int data;
Branch *first;
}TNode;
树的链式存储结构的孩子兄弟表示法:将会在树和二叉树的转换中重点讲解。
二叉树定义(重点)
二叉树:(1)每个结点最多有两根子树;(2)子树有左右顺序之分,不能颠倒。
满二叉树:所有的非叶子结点都有左右两个孩子,并且叶子结点全都在最下面满一层
完全二叉树:满二叉树从下至上,从右至左删除结点所得到的二叉树;满二叉树可以视作特殊的完全二叉树
二叉树的存储结构:
// 二叉树链式存储结构
typedef struct BTNode{
int data;
struct BTNode *lchild;
struct BTNODE *Rchild;
}BTNode;
二叉树性质(重点)
总分支数 = 总结点数 - 1
叶子结点为N0,单分支结点数为N1,双分支结点数为N2,则:
总结点数 = N0 + N1 + N2
总分支数 = N1 + 2N2
综上可推出:N0 = N2 + 1
二叉树的第i层上最多有2的(i - 1)次方个结点;
高度 / 深度为k的二叉树最多有2的k次方 - 1个结点;
二叉树的高度计算:略。
二叉树遍历算法(重点)
(1)先序遍历:根 -> 左 -> 右
void pre(BTNode *p){
visit(p);
pre(p->lchild);
pre(p->rchild);
}
(2)中序遍历:左 -> 根 -> 右
void in(BTNode *p){
in(p->lchild);
visit(p);
in(p->rchild);
}
(3)后序遍历:左 -> 右 -> 根
void post(BTNode *p){
post(p->lchild);
post(p->rchild);
visit(p);
}
(4)二叉树层次遍历
需要借助队列来实现二叉树的层次遍历
规则:
<1>从根结点开始入队,再出队,如果有左右孩子,则先入左,再入右;
<2>再次出队访问,检测有无左右孩子,有则继续重复上述步骤,直到全部遍历完成。
// 二叉树层次遍历代码
void level(BTNode *bt){
if(bt != NULL){
int front,rear;
BTNode *queue[maxsize];
front = rear = 0; // 初始化一个二叉树队列
BTNode *p;
rear = (rear + 1) % maxsize;
queue[rear] = bt;
while(front != rear){
front = (front + 1) % maxsize;
p = queue[front];
visit(p);
if(p -> lchild != NULL){
rear = (rear + 1) % maxsize;
queue[rear] = p->lchild;
}
if(p->rchild != NULL){
rear = (rear + 1) % maxsize;
queue[rear] = p->rchild;
}
}
}
}
(5)二叉树先序遍历非递归化实现
实现二叉树深度遍历非递归化,需要借助栈
规则:
<1>先入一个根结点,再出栈;
<2>先入右孩子结点,再入左孩子结点,随后出栈一个结点;
<3>出栈元素先入右孩子,再入左孩子,无孩子则不入栈,随后出栈一个元素;
<4>重复上述步骤,直到栈空和二叉树遍历完成则结束。
// 二叉树先序遍历非递归化代码
void pre_no(BTNode *bt){
BTNode *stack[maxsize];
int top = -1; // 初始化一个二叉树栈
BTNode *p = NULL;
stack[++top] = bt;
while(top != -1){
p = stack[top--];
visit(p);
if(p->rchild != NULL){
stack[++top] = p->rchild;
}
if(p->lchild != NULL){
stack[++top] = p->lchild;
}
}
}
(6)二叉树后序遍历非递归化实现
规则:只需要将先序规则中的“先入右孩子,再入左孩子”改为“先入左孩子,再入右孩子”,然后将整体得到的序列倒序输出即可。
这里需要再加一个辅助栈,将逆后序压入辅助栈再出栈即可得到正确的后序遍历。
// 二叉树后序遍历非递归化代码
void post_no(BTNode *bt){
if(bt !=NULL){
BTNode *stack1[maxsize];
BTNode *stack2[maxsize];
int top1 = -1;
int top2 = -1; // 初试化两个栈
BTNode *p = NULL;
stack1[++top1] = bt;
while(top1 != -1){
p = stack1[top1--];
stack2[++top2] = p; // 出栈元素顺序进入到辅助栈中
if(p->lchild != NULL){
stack1[++top1] = p->lchild;
}
if(p->rchild != NULL){
stack1[++top1] = p->rchild;
}
}
while(top2 != -1){ // 从辅助栈出栈,即可得后序遍历
p = stack2[top2--];
visit(p);
}
}
}
二叉树定义(重点)
二叉树:(1)每个结点最多有两根子树;(2)子树有左右顺序之分,不能颠倒。
满二叉树:所有的非叶子结点都有左右两个孩子,并且叶子结点全都在最下面满一层
完全二叉树:满二叉树从下至上,从右至左删除结点所得到的二叉树;满二叉树可以视作特殊的完全二叉树
二叉树的存储结构:
// 二叉树链式存储结构
typedef struct BTNode{
int data;
struct BTNode *lchild;
struct BTNODE *Rchild;
}BTNode;
二叉树性质(重点)
总分支数 = 总结点数 - 1
叶子结点为N0,单分支结点数为N1,双分支结点数为N2,则:
总结点数 = N0 + N1 + N2
总分支数 = N1 + 2N2
综上可推出:N0 = N2 + 1
二叉树的第i层上最多有2的(i - 1)次方个结点;
高度 / 深度为k的二叉树最多有2的k次方 - 1个结点;
二叉树的高度计算:略。
二叉树遍历算法(重点)
(1)先序遍历:根 -> 左 -> 右
void pre(BTNode *p){
visit(p);
pre(p->lchild);
pre(p->rchild);
}
(2)中序遍历:左 -> 根 -> 右
void in(BTNode *p){
in(p->lchild);
visit(p);
in(p->rchild);
}
(3)后序遍历:左 -> 右 -> 根
void post(BTNode *p){
post(p->lchild);
post(p->rchild);
visit(p);
}
(4)二叉树层次遍历
需要借助队列来实现二叉树的层次遍历
规则:
<1>从根结点开始入队,再出队,如果有左右孩子,则先入左,再入右;
<2>再次出队访问,检测有无左右孩子,有则继续重复上述步骤,直到全部遍历完成。
// 二叉树层次遍历代码
void level(BTNode *bt){
if(bt != NULL){
int front,rear;
BTNode *queue[maxsize];
front = rear = 0; // 初始化一个二叉树队列
BTNode *p;
rear = (rear + 1) % maxsize;
queue[rear] = bt;
while(front != rear){
front = (front + 1) % maxsize;
p = queue[front];
visit(p);
if(p -> lchild != NULL){
rear = (rear + 1) % maxsize;
queue[rear] = p->lchild;
}
if(p->rchild != NULL){
rear = (rear + 1) % maxsize;
queue[rear] = p->rchild;
}
}
}
}
(5)二叉树先序遍历非递归化实现
实现二叉树深度遍历非递归化,需要借助栈
规则:
<1>先入一个根结点,再出栈;
<2>先入右孩子结点,再入左孩子结点,随后出栈一个结点;
<3>出栈元素先入右孩子,再入左孩子,无孩子则不入栈,随后出栈一个元素;
<4>重复上述步骤,直到栈空和二叉树遍历完成则结束。
// 二叉树先序遍历非递归化代码
void pre_no(BTNode *bt){
BTNode *stack[maxsize];
int top = -1; // 初始化一个二叉树栈
BTNode *p = NULL;
stack[++top] = bt;
while(top != -1){
p = stack[top--];
visit(p);
if(p->rchild != NULL){
stack[++top] = p->rchild;
}
if(p->lchild != NULL){
stack[++top] = p->lchild;
}
}
}
(6)二叉树后序遍历非递归化实现
规则:只需要将先序规则中的“先入右孩子,再入左孩子”改为“先入左孩子,再入右孩子”,然后将整体得到的序列倒序输出即可。
这里需要再加一个辅助栈,将逆后序压入辅助栈再出栈即可得到正确的后序遍历。
// 二叉树后序遍历非递归化代码
void post_no(BTNode *bt){
if(bt !=NULL){
BTNode *stack1[maxsize];
BTNode *stack2[maxsize];
int top1 = -1;
int top2 = -1; // 初试化两个栈
BTNode *p = NULL;
stack1[++top1] = bt;
while(top1 != -1){
p = stack1[top1--];
stack2[++top2] = p; // 出栈元素顺序进入到辅助栈中
if(p->lchild != NULL){
stack1[++top1] = p->lchild;
}
if(p->rchild != NULL){
stack1[++top1] = p->rchild;
}
}
while(top2 != -1){ // 从辅助栈出栈,即可得后序遍历
p = stack2[top2--];
visit(p);
}
}
}
线索二叉树(重点)
可以理解为,线索化是将二叉树的遍历简单化,线性化,可以沿新增的指针方便的找到下一个结点
为了实现线索化,需要将原本二叉树的空指针加以改造,指向对应的下一结点
(1)中序线索二叉树
规则:
<1>叶结点有空左孩子时,左空指针指向前驱结点;
<2>叶结点有空右孩子时,右空指针指向后继结点。
// 线索二叉树存储结构
typedef struct TBTNode{
int data;
int ltag; // 左标记位,为0则没有线索化,为1则线索化
int rtag; // 右标记位,为0则没有线索化,为1则线索化
TBTNode *lchild;
TBTNode *rchild;
}TBTNode;
// 中序线索化代码
void in_t(TBTNode *p , TBTNode *&pre){ // pre指针始终指向p的前驱结点
if(p != NULL){
in_t(p->lchild , pre);
if(p->lchild == NULL){
p->lchild = pre;
p->ltag = 1;
}
if(pre != NULL && pre->rchild == NULL){
pre->rchild = p;
pre->rtag = 1;
}
pre = p; // 确保pre始终指向p的前驱结点
in_t(p->rchild , pre);
}
}
(2)先序线索二叉树
// 先序线索二叉树代码
void pre_t(TBTNode *p , TBTNode *&pre){
if(p != NULL){
if(p->lchild == NULL){
p->lchild = pre;
p->ltag = 1;
}
if(pre != NULL && pre->rchild == NULL){
pre->rchild = p;
pre->rtag = 1;
}
pre = p;
if(p->ltag == 0){
pre_t(p->lchild , pre);
}
if(p->rtag == 0){
pre_t(p->rchild , pre);
}
}
}
(3)后序线索二叉树
// 后序线索二叉树代码
void post_t(TBTNode *p , TBTNode *&pre){
if(p != NULL){
post_t(p->lchild , pre);
post_t(p->rchild , pre);
if(p->lchild == NULL){
p->lchild = pre;
p->ltag = 1;
}
if(pre != NULL && pre->rchild == NULL){
pre->rchild = p;
pre->rtag = 1;
}
pre = p;
}
}
总结:最能全面的找到特定结点的前驱元素和后继元素的是中序线索二叉树。
二叉树的确定(重点)
根据遍历序列,确定二叉树的构造:
(1)已知先序和中序?
(2)已知后序和中序?
(3)已知层次遍历序列和中序?
(4)已知先序和后序?
这种方式不能确定。
哈夫曼树(重点)
信息编码上用途广泛
特点:
<1>带权路径最短;
<2>权值越大,离根节点也就越远;
<3>树中没有度为1的结点,称这种树为正则二叉树;
路径:树中一个结点到另一个结点所经历的线路
路劲长度:路径上的分支数目
树的路径长度:从根结点到每个结点所有路径长度之和
带权路劲长度:路径长度乘以权值
树的带权路径长度WPL:树中所有叶子结点的带权路劲长度之和
哈夫曼编码中,任一编码串不可能是其他编码串的子串
带权路径长度计算:叶子结点值乘以路径之积的和 / 非叶子结点权值之和