0.二叉树结点的链式存储结构
#include<stdio.h>
#include<stdlib.h>
typedef char TElemType;//树中元素基本类型为char类型
#define bool int
#define true 1
#define false 0
//二叉树结点链式存储结构(二叉链表)
typedef struct BiNode
{
TElemType data;//数据域
struct BiNode* lchild, * rchild;//左,右孩子指针
}BiNode,*BiTree;
//BiNode:用来定义结点类型
//BiTree:用来定义树类型
1.遍历的相关特性
- 遍历定义------- 顺着某一条搜索路径巡访二叉树中的结点,使得每个结点均被访问一次,而且仅被访问一次(又称周游)。
- “访问”:可以是对结点作各种处理,如:输出结点的信息、修改结点的数据值等,但要求这种访问不破坏原来的数据结构。
- 遍历目的:得到树中所有结点的一个线性排列。
- 遍历用途:它是树结构插入、删除、修改、查找和排序运算的前提,是二叉树一切运算的基础和核心。
2.遍历方法
2.1方法概览
假设:
- L:遍历左子树
- D:访问根结点
- R:遍历右子树
- 则遍历整个二叉树的方案共有6种:
- 若规定先左后右,则只有前三种情况:
- 算法描述如下:
由二叉树的递归定义可知,遍历左子树和遍历右子树可如同遍历二又树一样“递归”进行。
2.2先序遍历二叉树----DLR
2.2.1方法详解:根左右
核心:子树为空时,结束操作;子树不为空,重复先序遍历的过程遍历子树
2.2.2手写遍历顺序实例分析:
- (1)较简单的例子
- (2)较复杂的例子
2.2.3先序递归遍历算法
- 简单示例的分析:
- 实例在程序中运行的过程
- 算法步骤:
注意:传入指向根结点的一级指针,因为遍历并不改变数据结构
bool PreOrderTraverse(BiTree T)
(1)返回条件:子树为空,则返回true;
(2)访问根结点
(3)递归遍历左子树
(4)递归遍历右子树
//1.先序遍历二叉树(根左右)
//注意:传入指向根结点的一级指针,因为遍历并不改变数据结构
bool PreOrderTraverse(BiTree T)
{
//[1]返回条件:子树为空,则返回true;
if (T == NULL)
return true;
//[2]访问根结点
printf("%c", T->data);
//[3]递归遍历左子树
PreOrderTraverse(T->lchild);
//[4]递归遍历右子树
PreOrderTraverse(T->rchild);
}
2.3中序遍历二叉树----LDR
2.3.1方法详解:左根右
2.3.2手写遍历顺序实例分析:
-
(1)较简单的例子
-
(2)较复杂的例子
2.3.3中序递归遍历算法
- 简单示例的分析:
- 算法步骤:
bool InOrderTraverse(BiTree T)
注意:传入指向根结点的一级指针,因为遍历并不改变数据结构
(1)返回条件:子树为空,则返回true;;
(2)递归遍历左子树
(3)访问根结点
(4)递归遍历右子树
//2.中序遍历二叉树(左根右)
//注意:传入指向根结点的一级指针,因为遍历并不改变数据结构
bool InOrderTraverse(BiTree T)
{
//[1]返回条件:子树为空,则返回true;
if (T == NULL)
return true;
//[2]递归遍历左子树
InOrderTraverse(T->lchild);
//[3]访问根结点
printf("%c", T->data);
//[4]递归遍历右子树
InOrderTraverse(T->rchild);
}
2.4后序遍历二叉树----LRD
2.4.1方法详解:左右根
2.4.2手写遍历顺序实例分析:
-
(1)较简单的例子
-
(2)较复杂的例子
2.3.3后序递归遍历算法
- 简单示例的分析:
- 算法步骤:
bool PostOrderTraverse(BiTree T)
注意:传入指向根结点的一级指针,因为遍历并不改变数据结构
(1)返回条件:子树为空,则返回true;;
(2)递归遍历左子树
(3)递归遍历右子树
(4)访问根结点
//3.后序遍历二叉树(左右根)
//注意:传入指向根结点的一级指针,因为遍历并不改变数据结构
bool PostOrderTraverse(BiTree T)
{
//[1]返回条件:子树为空,则返回true;
if (T == NULL)
return true;
//[2]递归遍历左子树
PostOrderTraverse(T->lchild);
//[3]递归遍历右子树
PostOrderTraverse(T->rchild);
//[4]访问根结点
printf("%c", T->data);
}
2.5运算式树的实例:
可以自己练习一下
2.6遍历算法的分析
- (1)如果去掉输出语句,从递归的角度看,三种算法是完全相同的,或说这三种算法的访问路径是相同的,只是访问结点的时机不同。
- (2)算法的效率分析
- 时间效率: O(n) 每个结点只访问一次
- 空间效率: O(n) 栈占用的最大辅助空间
2.7由遍历序列确定二叉树(核心先后定根,中分左右)
2.7.1遍历二叉树的特点
- 若二叉树中各结点的值均不相同,则二叉树结点的先序序列、中序序列、后序序列都是唯一的。
- 由二叉树的先序序列和中序序列,或由二叉树的后序序列和中序序列可以确定唯一一棵二叉树
2.7.2已知先序和中序序列求二叉树
回顾先序特点:根左右
回顾中序特点:左根右
实例分析:
(1)
- 先序根左右,故A为根
- 中序左根右,故CDBFE为左子树;---------------IHGJ为右子树
------即先序中的BCDEF序列---------即先序中的GHIJ序列
(2)
- 先序根左右,故左子树中B为根;右子树中G为根
- 中序左根右,
-B为根:---- CD为左子树;---------------FE为右子树
即先序中的CD序列---------即先序中的EF序列 - G为根:------IH为左子树;---------------J为右子树
即先序中的HI序列---------即先序中的J序列
(3) - 先序根左右,故左子树中C为根,E为根;右子树中H为根,J为叶子结点
- 中序左根右,
-C为根:---- D为右子树
由中序中的CD序列根右得到 - E为根:------F为左子树
由中序中的FE序列左根得到 - H为根:------I为左子树
由中序中的IH序列左根得到
通过以上三步得到对应的二叉树
2.6.3已知后序和中序序列求二叉树
(1)
- 后序左右根,故A为根
- 中序左根右,故BDCE为左子树;---------------FHG为右子树
------即后序中的DECB序列---------即先序中的HGF序列
(2)
- 后序左右根,故左子树中B为根;右子树中F为根
- 中序左根右,
- B为根:----------------DCE为右子树
----------即后序中的DEC序列 - F为根:---------------HG为右子树
---------即后序中的HG序列
(3)
- 后序左右根,故左子树中C为根;右子树中G为根
- 中序左根右,
- C为根:---- D为左叶子结点,E为右叶子节点
由中序中的DCE序列左根右得到 - G为根:------H为左叶子结点
由中序中的HG序列左根得到
2.7层序遍历二叉树----(从上往下,从左往右,逐层遍历)
2.7.1遍历特点
对于一颗二叉树,从根结点开始,按从上到下、从左到右的顺序访问每一个结点 且 每一个结点仅仅访问一次。
比如:
这颗二叉树的层序遍历结果为 abfcdgeh
2.7.2算法的设计思路
依旧是上述图例,分析此算法的执行过程:
- (1)根结点指针入队
- (2)根结点指针出队,入队根结点的左右孩子指针bf
- (3)b结点指针出队,入队b结点的左右孩子指针cd
- (4)f结点指针出队,入队f结点的左孩子指针g,其右孩子指针不存在
- (5)c结点指针出队,c结点的左右孩子指针不存在
- (6)d结点指针出队,入队d结点的左孩子指针g,其右孩子指针不存在
- (7)e结点指针出队,入队d结点的右孩子指针,其右孩子指针不存在
- (8)h结点指针出队,h结点的左右孩子指针不存在
- (9)此时队列为空,层序遍历结束
bool LevelOrder(BiNode* b)
注意:传入二叉树的根结点指针
//{4} 二叉树的层序遍历
// 注意:传入二叉树的根结点指针
bool LevelOrder(BiNode* b)
{
//[1]创建并初始化一个队列
queue qu;
Initqueue(&qu);
//[2]将根结点指针入队
Enqueue(&qu, b);
//[3]队不空时循环
while (!queueEmpty(&qu))
{
//<1>从队列中出列一个结点*e,访问并输出它
ElemType e;//这里的的e是用来接收队头元素的
//队列中所有元素都是指向树结点的指针
Dequeue(&qu, &e);
printf("%c", e->data); // 输出当前结点的值
//<2>若它有左孩子结点,将左孩子结点进队:
if (e->lchild != NULL)
{
Enqueue(&qu, e->lchild);
}
//<3>若它有右孩子结点,将右孩子结点进队。
if (e->rchild != NULL)
{
Enqueue(&qu, e->rchild);
}
}
return true;
}
完整代码即实现如下:
//用循环顺序队列实现二叉树的层序遍历
#include <stdio.h>
#include <stdlib.h>
#include<stdbool.h>
//{1}先实现循环顺序队列的构造
//(少用一个空间)
#define MAXSIZE 100//队列最大容量
//----------------------------------------------------------
typedef char TElemType;//二叉树中的结点存储char类型的数据
//{2}二叉树的结构实现
typedef struct BiNode
{
TElemType data;//数据域
struct BiNode* lchild, * rchild;//左,右孩子指针
}BiNode, * BiTree;
//-----------------------------------------------------------
typedef BiTree ElemType;//构造出来的队列用来存储指向树结点的指针
typedef struct queue
{
ElemType* data;
int front;
int rear;
}queue;
//[1]初始化队列
bool Initqueue(queue *q)
{
q->data =(ElemType*)malloc(sizeof(ElemType) * MAXSIZE);
if (!q)
{
return false;
}
q->front = q->rear = 0;
return true;
}
//[2]入队操作
bool Enqueue(queue* q,ElemType e)
{
if ((q->rear + 1) % MAXSIZE == q->front)
{
return false;//队满
}
q->data[q->rear] = e;
q->rear = (q->rear + 1) % MAXSIZE;
return true;
}
//[3]出队操作
bool Dequeue(queue* q, ElemType* e)
{
if (q->rear == q->front)
{
return false;//队空
}
*e = q->data[q->front];
q->front = (q->front + 1) % MAXSIZE;
return true;
}
//[4]判断队空
bool queueEmpty(queue* q)
{
if (q->front == q->rear)
{
return true;
}
return false;
}
//{3}先序创建二叉树
bool CreateBiTree(BiTree* T)
{
//[1]定义临时变量ch,用来存储每次输入的字符
TElemType ch;
scanf_s("%c", &ch);
//[2]若输入为#,则建立空结点,并返回
if (ch == '#')
{
(*T) = NULL;
}
//易错点1:输入空结点时,(*T) = NULL操作之后 应直接返回上一层,故if-else 语句块不能省略
//[2]否则创建结点,并按先序遍历序列递归建立其左右孩子结点
else
{
//<1>开辟子根结点空间(第一个结点为根结点)
(*T) = (BiNode*)malloc(sizeof(BiNode));
if (!(*T))
{
printf("内存分配失败!\n");
exit(-1);//程序错误,退出程序
}
//<2>将当前字符 存入 当前根结点
(*T)->data = ch;
//<3>构造左子树
CreateBiTree(&((*T)->lchild));
//<4>构造右子树
CreateBiTree(&((*T)->rchild));
//易错点2:传入的应该是 左右孩子指针的地址,而不是 左右孩子指针本身。因为要修改左右孩子指针的值(指向创建的新结点)
}
return true;
}
//{4} 二叉树的层序遍历
// 注意:传入二叉树的根结点指针
bool LevelOrder(BiNode* b)
{
//[1]创建并初始化一个队列
queue qu;
Initqueue(&qu);
//[2]将根结点指针入队
Enqueue(&qu, b);
//[3]队不空时循环
while (!queueEmpty(&qu))
{
//<1>从队列中出列一个结点指针(e),访问并输出它指向的树结点的值
ElemType e;
Dequeue(&qu, &e);
printf("%c", e->data); // 输出当前结点的值
//<2>若它有左孩子结点,将左孩子结点进队:
if (e->lchild != NULL)
{
Enqueue(&qu, e->lchild);
}
//<3>若它有右孩子结点,将右孩子结点进队。
if (e->rchild != NULL)
{
Enqueue(&qu, e->rchild);
}
}
return true;
}
int main()
{
BiTree T;
CreateBiTree(&T);
printf("层序遍历序列如下:");
LevelOrder(T);
printf("\n");
return 0;
return 0;
}
以模拟队列的二叉树实例为例:
先序建立二叉树应输入:abc##de###fg#h###
层序遍历二叉树的结果应为:abfcdgeh
2.8先序建立二叉树
按先序遍历序列建立二叉树的二叉链表(DRL)
2.8.1算法思路
- (1)从键盘输入二叉树的结点信息,建立二又树的存储结构
- (2)在建立二叉树的过程中按照二叉树先序方式建立
2.8.2二叉树序列的正确建立
-
(1)我们知道,对于一颗二叉树,我们至少要知道两个序列:
前序和中序 或者 后序和中序,才能确定唯一的二叉树
显然这个先序序列的二叉树是不唯一的,如下图:
那为了建立一颗唯一的二叉树,我们应该怎么办呢?
方法就是:从二叉树中每个结点的空指针中引出一个虚结点,
这里我们使用“#”来表示
如下图:
此时根据补充出来的虚结点的不同,这样两颗二叉树的序列就完全不一样了 -
(2)所以对于下图这样的二叉树,在先序建立二叉树时,用户应输入的序列是:
分析如下:
2.8.3实例建立过程详解
(1)输入A,不是#,建立根结点
(2)通过左孩子指针,输入B,不是#,建立根结点
(3)通过左孩子指针,输入C,不是#,建立根结点
(4)(5)
通过左孩子指针,输入#,返回上一层
通过右孩子指针,输入#,返回上一层
(6)刚才返回到第二层,通过右孩子指针,输入D,不是#,建立根结点
(7)通过左孩子指针,输入E,不是#,建立根结点
(8)(9)(10)(11)
通过左孩子指针,输入#,返回上一层
通过右孩子指针,输入G,不是#,建立根结点
通过左孩子指针,输入#,返回上一层
通过右孩子指针,输入#,返回上一层(此时回到了第四层)
返回第三层
(12)(13)(14)
通过右孩子指针,输入F,不是#,建立根结点
通过左孩子指针,输入#,返回上一层
通过右孩子指针,输入#,返回上一层(此时回到了第三层)
返回第二层
返回第一层
(15)通过右孩子指针,输入#,返回上一层(此时整树建立完毕,返回主函数)
2.8.4先序建立二叉树的算法分析:
bool CreateBiTree(BiTree *T)
注意1:传入指向二叉结点指针的二级指针,因为在建立二叉链表时需要 改变指针指向的内容
注意2:在递归建立二叉树时,需要判断是否为空结点,即输入字符为#表示空结点
易错点1:输入空结点时,(*T) = NULL操作之后 应直接返回上一层,故if-else 语句块不能省略
易错点2:传入的应该是 左右孩子指针的地址,而不是 左右孩子指针本身。因为要修改左右孩子指针的值(指向创建的新结点)
(1)定义临时变量ch,用来存储每次输入的字符
(2)若输入为#,则建立空结点,并返回
(3)否则创建结点,并按先序遍历序列递归建立其左右孩子结点
<1>开辟子根结点空间(第一个结点为根结点)
<2>将当前字符 存入 当前根结点
<3>构造左子树
<4>构造右子树
//5.先序建立二叉树
//注意1:传入指向二叉结点指针的二级指针,因为在建立二叉链表时需要 改变指针指向的内容
//注意2:在递归建立二叉树时,需要判断是否为空结点,即输入字符为#表示空结点
bool CreateBiTree(BiTree *T)
{
//[1]定义临时变量ch,用来存储每次输入的字符
TElemType ch;
scanf_s("%c",&ch);
//[2]若输入为#,则建立空结点,并返回
if (ch == '#')
{
(*T) = NULL;
}
//易错点1:输入空结点时,(*T) = NULL操作之后 应直接返回上一层,故if-else 语句块不能省略
//[2]否则创建结点,并按先序遍历序列递归建立其左右孩子结点
else
{
//<1>开辟子根结点空间(第一个结点为根结点)
(*T) = (BiNode*)malloc(sizeof(BiNode));
if (!(*T))
{
printf("内存分配失败!\n");
exit(-1);//程序错误,退出程序
}
//<2>将当前字符 存入 当前根结点
(*T)->data = ch;
//<3>构造左子树
CreateBiTree(&((*T)->lchild));
//<4>构造右子树
CreateBiTree(&((*T)->rchild));
//易错点2:传入的应该是 左右孩子指针的地址,而不是 左右孩子指针本身。因为要修改左右孩子指针的值(指向创建的新结点)
}
return true;
}
2.9先 中 后序遍历+先序建立整树完整代码如下:
输入实例:
#include<stdio.h>
#include<stdlib.h>
typedef char TElemType;//树中元素基本类型为char类型
#define bool int
#define true 1
#define false 0
//二叉树结点链式存储结构(二叉链表)
typedef struct BiNode
{
TElemType data;//数据域
struct BiNode* lchild, * rchild;//左,右孩子指针
}BiNode,*BiTree;
//BiNode:用来定义结点类型
//BiTree:用来定义树类型
二叉树结点链式存储结构(三叉链表)
//typedef struct TriTNode
//{
// TElemType data;//数据域
// struct TriNode* lchild, * parent,* rchild;//左孩子,双亲,右孩子指针
//}TirNode, * TirTree;
TirNode:用来定义结点类型
TirTree:用来定义树类型
//1.先序遍历二叉树(根左右)
//注意:传入指向根结点的一级指针,因为遍历并不改变数据结构
bool PreOrderTraverse(BiTree T)
{
//[1]返回条件:子树为空,则返回true;
if (T == NULL)
return true;
//[2]访问根结点
printf("%c", T->data);
//[3]递归遍历左子树
PreOrderTraverse(T->lchild);
//[4]递归遍历右子树
PreOrderTraverse(T->rchild);
}
//2.中序遍历二叉树(左根右)
//注意:传入指向根结点的一级指针,因为遍历并不改变数据结构
bool InOrderTraverse(BiTree T)
{
//[1]返回条件:子树为空,则返回true;
if (T == NULL)
return true;
//[2]递归遍历左子树
InOrderTraverse(T->lchild);
//[3]访问根结点
printf("%c", T->data);
//[4]递归遍历右子树
InOrderTraverse(T->rchild);
}
//3.后序遍历二叉树(左右根)
//注意:传入指向根结点的一级指针,因为遍历并不改变数据结构
bool PostOrderTraverse(BiTree T)
{
//[1]返回条件:子树为空,则返回true;
if (T == NULL)
return true;
//[2]递归遍历左子树
PostOrderTraverse(T->lchild);
//[3]递归遍历右子树
PostOrderTraverse(T->rchild);
//[4]访问根结点
printf("%c", T->data);
}
//5.先序建立二叉树
//注意1:传入指向二叉结点指针的二级指针,因为在建立二叉链表时需要 改变指针指向的内容
//注意2:在递归建立二叉树时,需要判断是否为空结点,即输入字符为#表示空结点
bool CreateBiTree(BiTree *T)
{
//[1]定义临时变量ch,用来存储每次输入的字符
TElemType ch;
scanf_s("%c",&ch);
//[2]若输入为#,则建立空结点,并返回
if (ch == '#')
{
(*T) = NULL;
}
//易错点1:输入空结点时,(*T) = NULL操作之后 应直接返回上一层,故if-else 语句块不能省略
//[2]否则创建结点,并按先序遍历序列递归建立其左右孩子结点
else
{
//<1>开辟子根结点空间(第一个结点为根结点)
(*T) = (BiNode*)malloc(sizeof(BiNode));
if (!(*T))
{
printf("内存分配失败!\n");
exit(-1);//程序错误,退出程序
}
//<2>将当前字符 存入 当前根结点
(*T)->data = ch;
//<3>构造左子树
CreateBiTree(&((*T)->lchild));
//<4>构造右子树
CreateBiTree(&((*T)->rchild));
//易错点2:传入的应该是 左右孩子指针的地址,而不是 左右孩子指针本身。因为要修改左右孩子指针的值(指向创建的新结点)
}
return true;
}
int main()
{
BiTree T;
CreateBiTree(&T);
printf("先序遍历序列如下:");
PreOrderTraverse(T);
printf("\n");
printf("中序遍历序列如下:");
InOrderTraverse(T);
printf("\n");
printf("后序遍历序列如下:");
PostOrderTraverse(T);
printf("\n");
return 0;
}