#数据结构 #C
遍历:按照某种次序把所有结点都访问一遍。
- 层次遍历:基于树的层次特性确定的次序规则。
- 前/ 中 / 后序遍历:基于树的递归特性确定的次序规则。
- 先序遍历:根-->左-->右(N-L-R);
- 中序遍历:左-->根-->右(L-N-R);
- 后序遍历:左-->右-->根(L-R-N)。
以如下的二叉树为例子,三种遍历的方式顺序为下:
- 先序遍历: A -> B -> D -> E -> C -> F -> G
- 中序遍历: D -> B -> E -> A -> F -> C -> G
- 后序遍历: D -> E -> B -> F -> G -> C -> A
前序遍历(PreOrder),又叫先序遍历,的操作过程如下:
- 若二叉树为空,则什么也不做;
- 若二叉树非空:
- 1.访问根结点;
- 2.先序遍历左子树;
- 3.先序遍历右子树。
typedef struct BiTNode {
int data;
struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;
// 先序遍历
void PreOrder(BiTree T) {
if (T != NULL) {
visit(T); // 访问根结点
PreOrder(T->lchild); // 递归遍历左子树
PreOrder(T->rchild); // 递归遍历右子树
}
}
具体例子: 针对某一个二叉树的BiTree,将其所有的数据元素使用先序遍历,存储在数组Array中,并预设数组大小足够不会发生越界。 程序实现:
typedef struct BiTNode {
int data;
struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;
// pos作为指针传参,是需要将其值带出来
void PushBack(int* array, int* pos, int data)
{
if (*pos >= ARRAY_MAX_SIZE) {
return;
}
array[*pos] = data;
(*pos)++;
}
// 测试输出Array
void OutPutArray(int* array, int len)
{
for(int i = 0; i < len; i++)
{
printf("array[%d] = %d\n", i, array[i]);
}
return;
}
void PreOrderTraverse(BiTree binaryTree, int* array, int* pos)
{
if (binaryTree == NULL) {
return;
}
PushBack(array, pos, binaryTree->data);
PreOrderTraverse(binaryTree->lchild, array, pos);
PreOrderTraverse(binaryTree->rchild, array, pos);
return;
}
void BiTreeInsertToArray(BiTree binaryTree, int* array)
{
int pos = 0;
PreOrderTraverse(binaryTree, array, &pos);
OutPutArray(array, pos);
return;
}
中序遍历(InOrder)的操作过程如下:
- 若二叉树为空,则什么也不做;
- 若二叉树非空:
- 1.中序遍历左子树;
- 2.访问根结点;
- 3.中序遍历右子树。
typedef struct BiTNode {
int data;
struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;
// 中序遍历
void InOrder(BiTree T) {
if (T != NULL) {
InOrder(T->lchild); // 递归遍历左子树
visit(T); // 访问根结点
InOrder(T->rchild); // 递归遍历右子树
}
}
后序遍历(PostOrder)的操作过程如下:
- 若二叉树为空,则什么也不做;
- 若二叉树非空:
- 1.后序遍历左子树;
- 2.后序遍历右子树。
- 3.访问根结点;
typedef struct BiTNode {
int data;
struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;
// 中序遍历
void PostOrder(BiTree T) {
if (T != NULL) {
PostOrder(T->lchild); // 递归遍历左子树
PostOrder(T->rchild); // 递归遍历右子树
visit(T); // 访问根结点
}
}
前、中、后序遍历皆属于「深度优先遍历 (Depth-First Traversal)」,其体现着一种“先走到尽头,再回头继续”的回溯遍历方式。
求树的深度
int GetTreeDepth(BiTree T){
if(T == NULL){
return 0;
} else {
int l = GetTreeDepth(T->lchild);
int r = GetTreeDepth(T->rchild);
// 树的深度是左子树深度和右子树的深度的最大值 + 1
// 典型后序遍历
return l > s ? (l + 1) : (s + 1);
}
}
算法:
- 初始化一个辅助队列
- 根结点入队
- 若队列非空,则队头结点出队,访问该结点,并将其左、右孩子插入队尾(如果有的话)
- 重复第3步直至队列为空
层序遍历本质上是「广度优先搜索 (Breadth-First Traversal)」,其体现着一种“一圈一圈向外”的层进遍历方式。
// 二叉树结点 链式存储
typedef struct BiTNode {
char data;
struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;
// 辅助链式队列结点
typedef struct LinkNode {
BiTNode *data; // 存指针而非结点
struct LinkNode *next;
} LinkNode;
typedef struct {
LinkNode *front, *rear; // 队头队尾
} LinkQueue;
// 层序遍历
void LevelOrder(BiTree T) {
LinkQueue Q;
InitQueue Q; // 初始化辅助队列
BiTree p;
EnQueue(Q, T); // 根结点入队
while (!IsEmpty(Q)) { // 队列不空则循环
// 队列尾部结点依次出队
p = GetRear(Q);
DeQueue(Q, p);
visit(p);
// 将尾部结点的子结点,依次插入队列中
if (p->lchild != NULL) {
EnQueue(Q, p->lchild); // 左孩子入队
}
if (p->rchild != NULL) {
EnQueue(Q, p->rchild); // 右孩子入队
}
}
}
上述是已知二叉树,求遍历序列,本部分依据已有的二叉序列构造二叉树。
中序遍历:递归遍历左子树 ---> 根结点 ---> 递归遍历右子树。
对于一个中序遍历序列,可能对应多种二叉树形态
前序遍历:根结点 ---> 递归遍历左子树 ---> 递归遍历右子树
对于一个前序遍历序列,可能对应多种二叉树形态
后序遍历:递归遍历左子树 ---> 递归遍历右子树 ---> 根结点
一个后序遍历序列,可能对应多种二叉树形态。
层序遍历:
一个层序遍历序列,可能对应多种二叉树形态。
结论:若只给出一棵二叉树的 前/中/后/层序遍历序列中的一种,不能唯一确定一棵二叉树。
但是,给出中序 + X 两种,就可以根据遍历序列构造唯一的二叉树。
- 前序遍历:根结点 → 前序遍历左子树 → 前序遍历右子树
- 中序遍历:中序遍历左子树 → 根结点 → 中序遍历右子树
【例子】给出前序遍历ADBCE
,中序遍历BDCAE
,求出二叉树的结构。
- 后序遍历:后序遍历左子树 → 后序遍历右子树 → 根结点
- 中序遍历:中序遍历左子树 → 根结点 → 中序遍历右子树
【例子】给出后序遍历EFAHCIGBD
,中序遍历EAFDHCBGI
,求出二叉树的结构。
- 根据对应关系,后序遍历的最后一个结点即根结点,找到根结点;
- 根结点在中序遍历的左右部分各为根结点的左右子树;
- 根据后序遍历最后一个结点是根结点,找到左右子树的根结点;
- 找到子树的左右孩子结点,直到每个结点都确定有自己的位置为止。
- 层序遍历:根结点 → 左子树的根 → 右子树的跟
- 中序遍历:中序遍历左子树 → 根结点 → 中序遍历右子树
【例子】给出层序遍历DABEFCGHI
,中序遍历EAFDHCBGI
,求出二叉树的结构。
- 根据对应关系,层序遍历的第一个结点即根结点,找到根结点;
- 根结点在中序遍历的左右部分各为根结点的左右子树;
- 根据层序遍历的顺序,确定左右子树的根结点;
- 找到子树的左右孩子结点,直到每个结点都确定有自己的位置为止。
由二叉树遍历序列构造二叉树,通常有三种方式:
- 前序 + 中序 遍历序列;
- 后序 + 中序 遍历序列;
- 层序 + 中序 遍历序列。
其中关键的做法是:找到树的根结点,并根据中序序列划分左右子树,再找到左右子树根结点。
问:能够对前序、后序、层序进行两两组合构建出二叉树。
可以以 前序AB
,后序BA
,层序AB
, 构建出两种二叉树。
因此前序、后序、层序序列的两两组合无法唯一确定一个二叉树。