炸裂!二叉树遍历的递归魔法与层序奥秘全解析,这波操作让你卷死算法岗面试官
💻作 者 简 介:曾 与 你 一 样 迷 茫,现 以 经 验 助 你 入 门 数据 结 构。
💡个 人 主 页:@笑口常开xpr 的 个 人 主 页
📚系 列 专 栏:硬 核 数 据 结 构 与 算 法
✨代 码 趣 语:树 的 直 观 概 念 意 味 着 我 们 对 数 据 进 行 组 织。
💪代 码 千 行,始 于 坚 持,每 日 敲 码,进 阶 编 程 之 路。
📦gitee 链 接:gitee
二 叉 树 是 计 算 机 科 学 中 重 要 的 数 据 结 构,在 搜 索、排 序 等 领 域 应 用 广 泛。遍 历 作 为 其 核 心 操 作,包 括 前 序、中 序、后 序 及 层 序 等 方 式,是 理 解 树 结 构 和 实 现 相 关 算 法 的 基 础。本 文 将 解 析 二 叉 树 遍 历 机 制 及 衍 生 应 用。
二 叉 树
对 于 二 叉 树,普 通 无 序 二 叉 树 对 于 增 删 查 改 没 有 意 义, 原 因 是 结 构 复 杂 ,不 如 使 用 链 表。
定 义
二 叉 树 遍 历 是 按 照 某 种 特 定 的 规 则,依 次 对 二 叉 树 中 的 结 点 进 行 相 应 的 操 作,并 且 每 个 结 点 只 操 作 一 次。一 棵 二 叉 树 分 为 根,左 子 树,右 子 树。
前 序 遍 历
定 义
前 序 遍 历 又 被 称 为 前 根 遍 历,按 照 根 - - - 左子树 - - - 右 子 树 的 顺 序 依 次 访 问。
如 图 所 示 的 二 叉 树 前 序 遍 历 的 访 问 顺 序 依 次 是 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)根 节 点 不 为 NULL,访 问 并 输 出 根 节 点。
(2)1 的 左 子 树 不 为 NULL,遍 历 2。
(3)2 的 左 子 树 不 为 NULL,遍 历 3。
(4)3 的 左 子 树 为 NULL,不 遍 历 输 出 NULL,然 后 返 回 3。
(5)3 的 右 子 树 为 NULL,不 遍 历 输 出 NULL,然 后 返 回 3。
(6)返 回 3 之 后,回 退 到 2,2 的 右 子 树 为 空,不 遍 历 输 出 NULL,回 退 到 1。
(7)1 的 右 子 树 不 为 空,遍 历 1 的 右 子 树。
(8)4 的 左 子 树 不 为 空,遍 历 5。
(9)5 的 左 子 树 为 NULL,不 遍 历 输 出 NULL,然 后 返 回 5。
(10)5 的 右 子 树 为 NULL,不 遍 历 输 出 NULL,然 后 返 回 4。
(11)4 的 右 子 树 不 为 NULL,遍 历 输 出 6。
(12)6 的 左 子 树 为 NULL,输 出 NULL 后 返 回 6。
(13)6 的 右 子 树 为 NULL,输 出 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)3 的 右 子 树 为 空,不 遍 历,输 出 NULL 后,返 回 到 2。
(4)遍 历 2 后,遍 历 2 的 右 子 树。
(5)2 的 右 子 树 为 NULL,不 遍 历,输 出 NULL 后,返 回 根 节 点。
(6)根 节 点 不 为 空,遍 历 根 节 点 后 遍 历 5 的 左 子 树。
(7)5 的 左 子 树 为 空,不 遍 历,输 出 NULL 后 返 回 5。
(8)遍 历 5 后,遍 历 5 的 右 子 树。
(9)5 的 右 子 树 为 空,不 遍 历,输 出 NULL 后 返 回 4。
(10)遍 历 4 后,遍 历 6 的 左 子 树。
(11)6 的 左 子 树 为 空,不 遍 历,输 出 NULL 后 返 回 6。
(12)遍 历 6 后,遍 历 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,输 出 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
#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))
{
printf("队列为空\n");
return;
}
else
{
while (cur)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
}
void QueueMiddlePush(void(*pf)(Queue* pq, QDataType x), Queue* pq)
{
int x = 0;
printf("请输入你想要插入的元素:>");
scanf("%d", &x);
pf(pq, x);
QueuePrint(pq);
}
void QueueMiddle(Queue* pq, int num)
{
if (num == 2)
{
if (QueueEmpty(pq))
{
printf("队列已空,无法出队\n");
}
else
{
printf("出队元素:%d\n", QueueFront(pq));
QueuePop(pq);
}
}
else if (num == 4)
{
if (QueueEmpty(pq))
{
printf("队列已空,无队头元素\n");
}
else
{
printf("队头元素:%d\n", QueueFront(pq));
}
}
else if (num == 5)
{
if (QueueEmpty(pq))
{
printf("队列已空,无队尾元素\n");
}
else
{
printf("队尾元素:%d\n", QueueBack(pq));
}
}
else
{
if (QueueEmpty(pq))
{
printf("队列已空\n");
}
else
{
printf("队列不为空\n");
}
}
}
void QueueSave(Queue* pq)
{
assert(pq);
FILE* pf = fopen("queue.txt", "w");
if (pf == NULL)
{
perror("无法打开文件");
return;
}
QNode* cur = pq->head;
while (cur)
{
fprintf(pf, "%d ", cur->data);
cur = cur->next;
}
fclose(pf);
pf = NULL;
printf("保存文件成功\n");
}
void QueueLoad(Queue* pq)
{
assert(pq);
FILE* pf = fopen("queue.txt", "r");
if (pf == NULL)
{
//文件不存在或无法打开,可能是第一次运行
return;
}
int data = 0;
while (fscanf(pf, "%d", &data) != EOF)
{
QueuePush(pq, data);
}
fclose(pf);
pf = NULL;
}
void QueuePrintDestory(Queue* pq)
{
assert(pq);
while (!QueueEmpty(pq))
{
printf("%d ", QueueFront(pq));
QueuePop(pq);
}
printf("\n");
}
test.c
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include "Queue.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 LevelOrder(BTNode* root)
{
Queue q;
QueueInit(&q);
if (root)
{
QueuePush(&q, root);
}
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
QueuePop(&q);//Pop的是队列的节点,front保存的是树的节点
printf("%d ", front->data);
if (front->left)
{
QueuePush(&q, front->left);
}
if (front->right)
{
QueuePush(&q, front->right);
}
}
printf("\n");
QueueDestory(&q);
}
int main()
{
BTNode* root = CreatTree();
LevelOrder(root);
return 0;
}
代 码 执 行 流 程
(1)根 节 点 1 入 队。
(2)取 出 1,打 印 1,将 子 节 点 2 和 4 入 队(队 列:2 -> 4)。
(3)取 出 2,打 印 2,将 子 节 点 3 入 队(队 列:4 -> 3)。
(4)取 出 4,打 印 4,将 子 节 点 5 和 6 入 队(队 列:3 -> 5 -> 6)。
(5)取 出 3,打 印 3,无 子 节 点 入 队(队 列:5 -> 6)。
(6)取 出 5,打 印 5,无 子 节 点 入 队(队 列:6)。
(7)取 出 6,打 印 6,无 子 节 点 入 队(队 列 为 空)。
关 键 点
(1)队 列 确 保 了 节 点 按 层 次 顺 序 被 处 理。每 处 理 一 个 节 点 时,将 其 子 节 点 加 入 队 列 尾 部,保 证 下 一 层 的 节 点 在 当 前 层 的 所 有 节 点 处 理 完 后 才 被 处 理。
(2)front 存 储 的 是 当 前 从 队 列 中 取 出 的 树 节 点 的 指 针,通 过 该 指 针 可 以 访 问 树 节 点 的 数 据 和 子 节 点。
第 k 层 结 点 的 个 数
//第k层结点的个数
//根的第k层个数 = 左子树的k - 1层个数 + 右子树的k - 1层个数
int TreeLevel(BTNode* root, int k)
{
assert(k > 0);
if (root == NULL)
{
return 0;
}
if (k == 1)
{
return 1;
}
return TreeLevel(root->left, k - 1) +
TreeLevel(root->right, k - 1);
}
关 键 点
(1)当 前 节 点 为 空 时,说 明 已 超 出 树 的 范 围,返 回 0。当 k 递 减 到 1 时,表 示 已 到 达 目 标 层,当 前 节 点 对 计 数 贡 献 为 1。
(2)递 归 计 算 左 子 树 的 第 k - 1 层 节 点 数 和 右 子 树 的 第k - 1 层 节 点 数。将 两 者 结 果 相 加,得 到 当 前 节 点 下 第 k 层 的 总 节 点 数。
复 杂 度
时 间 复 杂 度:O(n),需 要 遍 历 树 的 所 有 节 点。
空 间 复 杂 度:最 坏 情 况 下 为 O(h),其 中 h 是 树 的 高 度。
计 算 树 的 高 度
int TreeHigh(BTNode* root)
{
//方法1
//时间复杂度:O(N)
//return root == NULL ? 0 : (TreeHigh(root->left) > TreeHigh(root->right) ?
// TreeHigh(root->left) + 1 : TreeHigh(root->right) + 1);
//方法2
//时间复杂度:O(N^2)
//if (root == NULL)
// return 0;
//return TreeHigh(root->left) > TreeHigh(root->right)
// ? TreeHigh(root->left) + 1 : TreeHigh(root->right) + 1;
//方法3
if (root == NULL)
return 0;
int leftHeight = TreeHigh(root->left);
int rightHeight = TreeHigh(root->right);
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
方 法 3 复 杂 度
时 间 复 杂 度:O(N)
空 间 复 杂 度:O(h),其 中 h 是 二 叉 树 的 高 度。
计 算 树 的 节 点 数 目
//方法1
//void Treesize(BTNode* root, int* size)
//{
// if (root == NULL)
// {
// return;
// }
// Treesize(root->left, size);
// Treesize(root->right, size);
// (*size)++;
//}
//方法2
int Treesize(BTNode* root)
{
return root == NULL ? 0 : Treesize(root->left) + Treesize(root->right) + 1;
}
原 理
采 用 分 治 思 想,将 整 棵 树 的 节 点 数 分 解 为:
左 子 树 节 点 数 + 右 子 树 节 点 数 + 1(当 前 根 节 点)
递 归 终 止 条 件 :空 节 点 返 回 0。
复 杂 度
时 间 复 杂 度:O(N),每 个 节 点 恰 好 被 访 问 一 次。
空 间 复 杂 度:O(h),递 归 栈 的 最 大 深 度 为 树 的 高 度 h。
判 断 是 否 为 完 全 二 叉 树
bool IsCompleteTree(BTNode* root)
{
Queue q;
QueueInit(&q);
if (root)
{
QueuePush(&q, root);
}
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
if (front == NULL)
{
break;
}
else
{
QueuePush(&q, front->left);
QueuePush(&q, front->right);
}
}
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
//有非空,说明后面节点不是完全连续
if (front)
{
QueueDestory(&q);
return false;
}
}
QueueDestory(&q);
return true;
}
和 层 序 遍 历 类 似 这 段 代 码 需 要 使 用 队 列 来 判 断,这 里 省 略 了 Queue.h 和 Queue.c 这 两 个 文 件 的 代 码。
完 全 二 叉 树 的 特 性
非 空 节 点 必 须 连 续:在 层 序 遍 历 中,所 有 非 空 节 点 必 须 出 现 在 空 节 点 之 前。
遇 到 第 一 个 空 节 点 后,后 续 不 能 再 有 非 空 节 点:一 旦 在 层 序 遍 历 中 遇 到 一 个 空 节 点,那 么 队 列 中 剩 余 的 所 有 元 素 都 必 须 是 空 节 点。
这 段 代 码 通 过 层 序 遍 历 将 所 有 节 点(包 括 空 节 点)加 入 队 列,当 第 一 次 遇 到 空 节 点 时 停 止 入 队,然 后 检 查 队 列 中 剩 余 元 素 是 否 全 为 空,以 此 判 断 是 否 为 完 全 二 叉 树。
销 毁 二 叉 树
void BinaryTreeDestory(BTNode* root)
{
if (root == NULL)
{
return;
}
BinaryTreeDestory(root->left);
BinaryTreeDestory(root->right);
free(root);
}
这 里 推 荐 使 用 后 序 遍 历 如 果 使 用 前 序 遍 历 或 者 中 序 遍 历 无 法 找 到 当 前 的 左 右 子 树。
总 结
通 过 解 析 二 叉 树 四 种 遍 历 方 式 及 节 点 计 数、高 度 计 算 等 操 作,揭 示 了 其 递 归 分 治 与 层 次 化 访 问 的 逻 辑。这 些 操 作 时 间 复 杂 度 与 节 点 数 相 关,空 间 复 杂 度 受 树 高 影 响。掌 握 基 础 操 作 后,可 进 一 步 学 习 平 衡 二 叉 树 等 变 种 结 构,提 升 对 树 结 构 的 理 解 与 应 用 能 力。