文章标题:二叉树遍历:递归法与层序法的透彻解读,助力算法岗位
文章内容:
震撼!二叉树遍历的递归魔法与层序奥秘全面剖析,助你在算法岗面试中脱颖而出
- 二 叉 树
-
- 定 义
- 前 序 遍 历
-
- 定 义
- 递 归 实 现
- 中 序 遍 历
-
- 定 义
- 递 归 实 现
- 后 序 遍 历
-
- 定 义
- 递 归 实 现
- 层 序 遍 历
-
- 定 义
- 代 码 实 现
- 第 k 层 结 点 的 个 数
- 计 算 树 的 高 度
- 计 算 树 的 节 点 数 目
- 判 断 是 否 为 完 全 二 叉 树
- 销 毁 二 叉 树
- 总 结
二叉树是计算机科学中至关重要的数据结构,在搜索、排序等领域应用广泛。遍历作为其核心操作,包含前序、中序、后序及层序等方式,是理解树结构和实现相关算法的基础。本文将剖析二叉树遍历机制及衍生应用。
二 叉 树
对于二叉树而言,普通的无序二叉树在增删查改方面没有实际价值,原因在于其结构复杂,不如链表实用。
定 义
二叉树遍历指的是按照特定规则,依次对二叉树中的节点进行操作,且每个节点仅操作一次。一棵二叉树由根节点、左子树和右子树组成。
前 序 遍 历
定 义
前序遍历也叫前根遍历,按照根节点、左子树、右子树的顺序依次进行访问。
例如图中所示的二叉树,前序遍历的访问顺序依次为1、2、3、NULL(3的左子树)、NULL(3的右子树)、NULL(2的右子树)、4、5、NULL(5的左子树)、NULL(5的右子树)、6、NULL(6的左子树)、NULL(6的右子树)。
递 归 实 现
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
typedef int BTDataType;
typedef struct BinaryTreeNode
{
BTDataType data;
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
}BTNode;
BTNode* BuyNode(BTDataType x)
{
BTNode* node = (BTNode*)malloc(sizeof(BTNode));
if (node == NULL)
{
perror("malloc fail");
return NULL;
}
node->data = x;
node->left = NULL;
node->right = NULL;
return node;
}
// 1
// 2 4
//3 5 6
BTNode* CreatTree()
{
BTNode* node1 = BuyNode(1);
BTNode* node2 = BuyNode(2);
BTNode* node3 = BuyNode(3);
BTNode* node4 = BuyNode(4);
BTNode* node5 = BuyNode(5);
BTNode* node6 = BuyNode(6);
BTNode* node7 = BuyNode(7);
node1->left = node2;
node1->right = node4;
node2->left = node3;
node4->left = node5;
node4->right = node6;
return node1;
}
//前序遍历
void PreOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
printf("%d ", root->data);
PreOrder(root->left);
PreOrder(root->right);
}
int main()
{
BTNode* root = CreatTree();
PreOrder(root);
return 0;
}
(1)当根节点不为空时,访问并输出根节点。
(2)根节点1的左子树不为空,遍历节点2。
(3)节点2的左子树不为空,遍历节点3。
(4)节点3的左子树为空,输出NULL后返回节点3。
(5)节点3的右子树为空,输出NULL后返回节点3。
(6)返回节点3后,回退到节点2,节点2的右子树为空,输出NULL后回退到根节点1。
(7)根节点1的右子树不为空,遍历其右子树。
(8)节点4的左子树不为空,遍历节点5。
(9)节点5的左子树为空,输出NULL后返回节点5。
(10)节点5的右子树为空,输出NULL后返回节点4。
(11)节点4的右子树不为空,输出节点6。
(12)节点6的左子树为空,输出NULL后返回节点6。
(13)节点6的右子树为空,输出NULL后返回根节点,前序遍历结束。
中 序 遍 历
定 义
中序遍历也叫中根遍历,按照左子树、根节点、右子树的顺序依次进行访问。需注意左树访问完成后才能访问根节点。
例如图中所示的二叉树,中序遍历的访问顺序依次为NULL(3的左子树)、3、NULL(3的右子树)、2、NULL(2的右子树)、1、NULL(5的左子树)、5、NULL(5的右子树)、4、NULL(6的左子树)、6、NULL(6的右子树)。
递 归 实 现
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
typedef int BTDataType;
typedef struct BinaryTreeNode
{
BTDataType data;
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
}BTNode;
BTNode* BuyNode(BTDataType x)
{
BTNode* node = (BTNode*)malloc(sizeof(BTNode));
if (node == NULL)
{
perror("malloc fail");
return NULL;
}
node->data = x;
node->left = NULL;
node->right = NULL;
return node;
}
// 1
// 2 4
//3 5 6
BTNode* CreatTree()
{
BTNode* node1 = BuyNode(1);
BTNode* node2 = BuyNode(2);
BTNode* node3 = BuyNode(3);
BTNode* node4 = BuyNode(4);
BTNode* node5 = BuyNode(5);
BTNode* node6 = BuyNode(6);
BTNode* node7 = BuyNode(7);
node1->left = node2;
node1->right = node4;
node2->left = node3;
node4->left = node5;
node4->right = node6;
return node1;
}
//中序遍历
void InOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
InOrder(root->left);
printf("%d ", root->data);
InOrder(root->right);
}
int main()
{
BTNode* root = CreatTree();
InOrder(root);
return 0;
}
(1)首先遍历3的左子树,为空,输出NULL后返回节点3。
(2)遍历节点3后,遍历其右子树。
(3)节点3的右子树为空,输出NULL后,返回到节点2。
(4)遍历节点2后,遍历其右子树。
(5)节点2的右子树为空,输出NULL后,返回到根节点。
(6)根节点不为空,遍历根节点后遍历5的左子树。
(7)节点5的左子树为空,输出NULL后返回节点5。
(8)遍历节点5后,遍历其右子树。
(9)节点5的右子树为空,输出NULL后返回节点4。
(10)遍历节点4后,遍历6的左子树。
(11)节点6的左子树为空,输出NULL后返回节点6。
(12)遍历节点6后,遍历其右子树。
(13)节点6的右子树为空,输出NULL后返回根节点,中序遍历结束。
后 序 遍 历
定 义
后序遍历也叫后根遍历,按照左子树、右子树、根节点的顺序依次进行访问。
例如图中所示的二叉树,后序遍历的访问顺序依次为NULL(3的左子树)、NULL(3的右子树)、3、NULL(2的右子树)、2、NULL(5的左子树)、NULL(5的右子树)、5、NULL(6的左子树)、NULL(6的右子树)、6、4、1。
递 归 实 现
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
typedef int BTDataType;
typedef struct BinaryTreeNode
{
BTDataType data;
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
}BTNode;
BTNode* BuyNode(BTDataType x)
{
BTNode* node = (BTNode*)malloc(sizeof(BTNode));
if (node == NULL)
{
perror("malloc fail");
return NULL;
}
node->data = x;
node->left = NULL;
node->right = NULL;
return node;
}
// 1
// 2 4
//3 5 6
BTNode* CreatTree()
{
BTNode* node1 = BuyNode(1);
BTNode* node2 = BuyNode(2);
BTNode* node3 = BuyNode(3);
BTNode* node4 = BuyNode(4);
BTNode* node5 = BuyNode(5);
BTNode* node6 = BuyNode(6);
BTNode* node7 = BuyNode(7);
node1->left = node2;
node1->right = node4;
node2->left = node3;
node4->left = node5;
node4->right = node6;
return node1;
}
//后序遍历
void PostOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
PostOrder(root->left);
PostOrder(root->right);
printf("%d ", root->data);
}
int main()
{
BTNode* root = CreatTree();
PostOrder(root);
return 0;
}
(1)首先遍历3的左子树,为空,输出NULL。
(2)遍历3的右子树,为空,输出NULL后返回节点3。
(3)遍历节点3后返回2的右子树。
(4)2的右子树为空,输出NULL后,返回到节点2。
(5)遍历节点2后返回到5的左子树。
(6)5的左子树为空,输出NULL后,返回到5的右子树。
(7)5的右子树为空,输出NULL后,返回到5。
(8)遍历5后返回到6的左子树。
(9)6的左子树为空,输出NULL后,返回到6的右子树。
(10)6的右子树为空,输出NULL后,返回到6。
(11)遍历6后返回到4。
(12)遍历4后返回到根节点。
(13)遍历根节点。
层 序 遍 历
定 义
从根节点出发,按照“从上到下、从左到右”的层次顺序,逐层访问树的节点。例如二叉树有多层节点,先访问完第1层(根节点),再依次访问第2层、第3层……同层节点按从左到右顺序处理。图中所示的二叉树层序遍历的访问顺序依次为1、2、3、4、5、6。
代 码 实 现
使用队列进行层序遍历。
Queue.h
#include<stdio.h>
#include<stdbool.h>
#include<assert.h>
#include<stdlib.h>
typedef struct BinaryTreeNode* QDataType;
typedef struct QueueNode
{
struct QueueNode* next;
QDataType data;
}QNode;
typedef struct Queue
{
QNode* head;
QNode* tail;
int size;
}Queue;
//初始化队列
void QueueInit(Queue* pq);
//销毁队列
void QueueDestory(Queue* pq);
//队尾入队列
void QueuePush(Queue* pq,QDataType x);
//队头出队列
void QueuePop(Queue* pq);
//获取队列中有效元素个数
int QueueSize(Queue* pq);
//检查队列是否为空
bool QueueEmpty(Queue* pq);
//取出队头的数据
QDataType QueueFront(Queue* pq);
//取出队尾的数据
QDataType QueueBack(Queue* pq);
//输出队列
void QueuePrint(Queue* pq);
void QueueMiddlePush(void(*pf)(Queue* pq, QDataType x), Queue* pq);
void QueueMiddle(Queue* pq, int num);
//保存队列到文件中
void QueueSave(Queue* pq);
//从文件中加载队列
void QueueLoad(Queue* pq);
//输出队列元素时删除队列元素
void QueuePrintDestory(Queue* pq);
Queue.c
“`c
define _CRT_SECURE_NO_WARNINGS 1
include “Queue.h”
void QueueInit(Queue* pq)
{
assert(pq);
pq->head = pq->tail = NULL;
pq->size = 0;
QueueLoad(pq);
}
void QueueDestory(Queue pq)
{
assert(pq);
QNode cur = pq->head;
while (cur)
{
QNode next = cur->next;
free(cur);
cur = next;
}
pq->head = pq->tail = NULL;
pq->size = 0;
}
void QueuePush(Queue pq, QDataType x)
{
assert(pq);
QNode newnode = (QNode)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror(“newnode”);
return;
}
newnode->data = x;
newnode->next = NULL;
if (pq->head == NULL)
{
assert(pq->tail == NULL);
pq->head = pq->tail = newnode;
}
else
{
pq->tail->next = newnode;
pq->tail = newnode;
}
pq->size++;
}
void QueuePop(Queue pq)
{
assert(pq);
assert(pq->head != NULL);
//方法1
//QNode next = pq->head->next;
//free(pq->head);
//pq->head = next;
//if (pq->head == NULL)
//{
// pq->tail = NULL;
//}
//方法2
if (pq->head->next == NULL)
{
free(pq->head);
pq->tail = pq->head = NULL;
}
else
{
QNode next = pq->head->next;
free(pq->head);
pq->head = next;
}
pq->size–;
}
int QueueSize(Queue pq)
{
assert(pq);
return pq->size;
}
bool QueueEmpty(Queue* pq)
{
assert(pq);
//return pq->size == 0;
return pq->head == NULL && pq->tail == NULL;
}
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->head->data;
}
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->tail->data;
}
void QueuePrint(Queue pq)
{
assert(pq);
QNode cur = pq->head;
if (QueueEmpty(pq))