树
二叉树节点定义与初始化
// 二叉树节点结构(通用模板)
typedef struct BiTNode {
int data; // 数据域(可替换为char等类型)
struct BiTNode* lchild; // 左孩子指针
struct BiTNode* rchild; // 右孩子指针
} BiTNode, *BiTree;
// 创建新节点
BiTNode* createNode(int data) {
BiTNode* node = (BiTNode*)malloc(sizeof(BiTNode));
node->data = data;
node->lchild = node->rchild = NULL; // 初始无孩子
return node;
}
二叉树遍历_递归
// 先序遍历(根→左→右)
void PreOrder(BiTree T) {
if (T != NULL) {
printf("%d ", T->data); // 访问根节点
PreOrder(T->lchild); // 遍历左子树
PreOrder(T->rchild); // 遍历右子树
}
}
// 中序遍历(左→根→右)
void InOrder(BiTree T) {
if (T != NULL) {
InOrder(T->lchild);
printf("%d ", T->data); // 访问根节点
InOrder(T->rchild);
}
}
// 后序遍历(左→右→根)
void PostOrder(BiTree T) {
if (T != NULL) {
PostOrder(T->lchild);
PostOrder(T->rchild);
printf("%d ", T->data); // 访问根节点
}
}
二叉树遍历_非递归
// 非递归中序遍历(栈实现)
void InOrderNonRecursive(BiTree T) {
BiTNode* stack100; // 栈(假设最大深度100)
int top = -1; // 栈顶指针
BiTNode* p = T; // 遍历指针
while (p != NULL || top != -1) {
// 左链入栈
while (p != NULL) {
stack++top = p;
p = p->lchild;
}
// 出栈访问
if (top != -1) {
p = stacktop--;
printf("%d ", p->data);
p = p->rchild; // 转向右子树
}
}
}
// 非递归层次遍历(队列实现)
void LevelOrder(BiTree T) {
if (T == NULL) return;
BiTNode* queue100; // 队列
int front = 0, rear = 0; // 队头、队尾指针
queuerear++ = T; // 根节点入队
while (front < rear) {
BiTNode* p = queuefront++; // 出队
printf("%d ", p->data); // 访问
// 左右孩子入队
if (p->lchild != NULL) queuerear++ = p->lchild;
if (p->rchild != NULL) queuerear++ = p->rchild;
}
}
求树的深度
// 递归求深度:左右子树深度最大值+1
int GetDepth(BiTree T) {
if (T == NULL) return 0; // 空树深度为0
int leftDepth = GetDepth(T->lchild);
int rightDepth = GetDepth(T->rchild);
return (leftDepth > rightDepth ? leftDepth : rightDepth) + 1;
}
求叶子节点数
// 递归求叶子数:左右子树叶子数之和(叶子节点:左右孩子均为空)
int GetLeafCount(BiTree T) {
if (T == NULL) return 0;
if (T->lchild == NULL && T->rchild == NULL) {
return 1; // 叶子节点
}
return GetLeafCount(T->lchild) + GetLeafCount(T->rchild);
}
由先序和中序序列构建二叉树
// 在中序序列中查找值为x的下标(辅助函数)
int FindIndex(int in\[\], int start, int end, int x) {
for (int i = start; i <= end; i++) {
if (ini == x) return i;
}
return -1; // 未找到(输入合法时不会触发)
}
// 递归构造:prepreStart..preEnd为前序,ininStart..inEnd为中序
BiTree BuildTree(int pre\[\], int in\[\], int preStart, int preEnd, int inStart, int inEnd) {
if (preStart > preEnd || inStart > inEnd) {
return NULL; // 空树
}
// 前序首元素为根节点
int rootVal = prepreStart;
BiTNode* root = createNode(rootVal);
// 找根在中序中的位置
int rootInx = FindIndex(in, inStart, inEnd, rootVal);
int leftSize = rootInx - inStart; // 左子树节点数
// 构造左子树:前序preStart+1..preStart+leftSize,中序inStart..rootInx-1
root->lchild = BuildTree(pre, in, preStart + 1, preStart + leftSize, inStart, rootInx - 1);
// 构造右子树:前序preStart+leftSize+1..preEnd,中序rootInx+1..inEnd
root->rchild = BuildTree(pre, in, preStart + leftSize + 1, preEnd, rootInx + 1, inEnd);
return root;
}
二叉排序树(BST)的基本操作
// BST插入(递归):小于根插左子树,大于根插右子树
BiTree BSTInsert(BiTree T, int key) {
if (T == NULL) {
return createNode(key); // 新节点作为根
}
if (key < T->data) {
T->lchild = BSTInsert(T->lchild, key);
} else if (key > T->data) {
T->rchild = BSTInsert(T->rchild, key);
}
// 相等元素不插入(BST通常不允许重复)
return T;
}
// BST查找(递归)
BiTNode* BSTSearch(BiTree T, int key) {
if (T == NULL || T->data == key) {
return T; // 找到或空树
}
if (key < T->data) {
return BSTSearch(T->lchild, key);
} else {
return BSTSearch(T->rchild, key);
}
}
#include <iostream>
#include <vector>
#include <queue>
#include <cstring>
using namespace std;
const int INF = 0x3f3f3f3f;
///
///无穷大的另一种 写法:
///
double dINF = numeric_limits<double>::infinity();
const int MAXN = 1005;
// 邻接表存图
vector<pair<int, int>> adjMAXN;
int distMAXN; // 最短距离
bool visMAXN; // 是否已确定最短路
// 堆:小根堆(距离,节点)
priority_queue<pair<int, int>,
vector<pair<int, int>>,
greater<pair<int, int>>> q;
void dijkstra(int start, int n) {
// 初始化
memset(dist, 0x3f, sizeof(dist));
memset(vis, false, sizeof(vis));
diststart = 0;
q.push({0, start});
while (!q.empty()) {
auto d, u = q.top();
q.pop();
if (visu) continue;
visu = true;
// 松弛所有邻边
for (auto v, w : adju) {
if (distv > distu + w) {
distv = distu + w;
q.push({distv, v});
}
}
}
}
int main() {
int n, m, start;
cin >> n >> m >> start; // 点数、边数、起点
for (int i = 0; i < m; ++i) {
int u, v, w;
cin >> u >> v >> w;
adju.emplace_back(v, w);
// 无向图再加一行:adjv.emplace_back(u, w);
}
dijkstra(start, n);
// 输出从 start 到各点的最短距离
for (int i = 1; i <= n; ++i) {
if (disti == INF) cout << "INF ";
else cout << disti << " ";
}
return 0;
}
1.图:Dijkstra Algorithm(优先队列实现_难理解)
#include <iostream>
using namespace std;
// 二叉排序树结点结构
struct Node {
int val;
Node *left, *right;
Node(int x) : val(x), left(nullptr), right(nullptr) {}
};
// 根节点
Node* root = nullptr;
// 1. 插入(递归)
Node* insert(Node* root, int x) {
if (!root) return new Node(x);
if (x < root->val)
root->left = insert(root->left, x);
else
root->right = insert(root->right, x);
return root;
}
// 2. 查找(递归)
bool search(Node* root, int x) {
if (!root) return false;
if (x == root->val) return true;
return x < root->val ? search(root->left, x) : search(root->right, x);
}
// 3. 删除
Node* findMin(Node* root) {
while (root->left) root = root->left;
return root;
}
Node* remove(Node* root, int x) {
if (!root) return nullptr;
// 找到要删除的结点
if (x == root->val) {
// 情况1:无孩子 / 只有一个孩子
if (!root->left) {
Node* tmp = root->right;
delete root;
return tmp;
}
if (!root->right) {
Node* tmp = root->left;
delete root;
return tmp;
}
// 情况2:两个孩子 → 用右子树最小值替换
Node* tmp = findMin(root->right);
root->val = tmp->val;
root->right = remove(root->right, tmp->val);
}
else if (x < root->val)
root->left = remove(root->left, x);
else
root->right = remove(root->right, x);
return root;
}
// 4. 中序遍历(BST 必升序)
void inOrder(Node* root) {
if (!root) return;
inOrder(root->left);
cout << root->val << " ";
inOrder(root->right);
}
// 测试
int main() {
int a\[\] = {5,3,7,2,4,6,8};
for (int x : a) root = insert(root, x);
inOrder(root); cout << endl; // 升序输出
root = remove(root, 3);
inOrder(root); cout << endl;
return 0;
}
4.二叉排序树BST
特点大根堆经过堆排序之后就转换成了小根堆。
#include <iostream>
#include <vector>
//通用STL标准库
using namespace std;
// 大根堆调整:向下沉
//三角对比置换,不能够直接用于构建大根堆
//堆删除、堆构建、堆排序的基础单元
bool heapify( vector<int>& arr,
int n,
int i)
{
int largest = i;
int left = 2 * i + 1;
int right = 2 * i + 2;
if (left < n && arrleft > arrlargest)
largest = left;
if (right < n && arrright > arrlargest)
largest = right;
if (largest != i) {
swap(arri, arrlargest);
heapify(arr, n, largest);
}
return true;
}
// 建堆:建立一个大根堆
//从最后一个父节点开始,回溯
//对每一个父节点进行 三角置换
void buildMaxHeap(vector<int>& arr) {
int n = arr.size();
for (int i = n / 2 - 1; i >= 0; --i)
heapify(arr, n, i);
}
// 堆排序
//原地排序思想:将堆顶移除,放置在后面
//调整待排序 序列长度
//直到剩余一个元素,排序完成
void heapSort(vector<int>& arr) {
int n = arr.size();
buildMaxHeap(arr);
for (int i = n - 1; i > 0; --i) {
swap(arr0, arri); // 堆顶放末尾
heapify(arr, i, 0); // 调整堆
}
}
// 堆插入:向上浮
//一个数组,按照层序遍历,二叉摆放
//根节点>父节点的父节点>父节点>...
void insertHeap(vector<int>& arr, int val) {
arr.push_back(val);
int i = arr.size() - 1;
// 比父节点大就交换
while (i > 0 && arri > arr(i - 1) / 2) {
swap(arri, arr(i - 1) / 2);
i = (i - 1) / 2;
}
}
// 删除堆顶
//堆排序的一个缩影
void deleteTop(vector<int>& arr) {
if (arr.empty()) return;
int n = arr.size();
arr0 = arr.back();
arr.pop_back();
heapify(arr, n - 1, 0);
}
// 打印
void print(vector<int>& arr) {
for (int x : arr) cout << x << " ";
cout << endl;
}
// 测试
int main() {
vector<int> arr = {3, 1, 4, 1, 5, 9};
buildMaxHeap(arr);
cout << "建堆后:"; print(arr);
insertHeap(arr, 10);
cout << "插入10后:"; print(arr);
deleteTop(arr);
cout << "删除堆顶后:"; print(arr);
heapSort(arr);
cout << "堆排序后:"; print(arr);
return 0;
}
-
堆调整:找最大孩子,交换,递归下沉
-
建堆:从 n/2 - 1 往前逐个 heapify
-
下标:左孩子 2i+1 ,右孩子 2i+2 ,父节点 (i-1)/2
注:
-
数组下标从 0 开始 → 最后一个非叶子节点下标是:n/2 - 1;
-
数组下标从 1 开始 → 最后一个非叶子节点下标是:n/2.
2.堆
树_森林(孩子兄弟表示法)
typedef int bool;
#define true 1
#define false 0
// 使用
bool flag = true;
if (flag == false) {
}
孩子兄弟表示法核心操作与高频题型解析
- 孩子兄弟表示法常规操作(C语言)
结构定义(全局)
c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
// 孩子兄弟表示法结点结构
typedef struct CSNode {
// 结点数据域
int data;
// 指向第一个孩子结点
struct CSNode *firstchild;
// 指向右兄弟结点
struct CSNode *nextsibling;
} CSNode, *CSTree;
/**
* @brief 创建新结点
* @paramin iData 结点存储的数据
* @return 新结点指针
*/
CSTree CreateNode(int iData) {
// 分配结点内存空间
CSTree pNewNode = (CSTree)malloc(sizeof(CSNode));
// 初始化结点数据与指针
pNewNode->data = iData;
pNewNode->firstchild = NULL;
pNewNode->nextsibling = NULL;
return pNewNode;
}
1.1 创建树(双亲数组法)
代码思想
-
初始化阶段:根据给定的结点总数,循环创建所有独立的结点对象。
-
关系构建阶段:遍历双亲数组,为每个结点匹配对应的双亲结点。
-
根结点判定:双亲值为0的结点为树的根结点;其余结点按双亲关系,挂载为双亲的第一个孩子或兄弟链末尾结点。
代码实现
c
/**
* @brief 基于双亲数组创建孩子兄弟表示法的树
* @paramin iParentArr 双亲数组(下标1开始,0表示根结点)
* @paramin iValueArr 结点数据数组(下标1开始)
* @paramin iNodeCount 树的总结点数
* @paramout ppRootNode 输出:树的根结点指针
* @return 创建成功返回true,失败返回false
*/
bool CreateTree(int iParentArr\[\], int iValueArr\[\], int iNodeCount, CSTree *ppRootNode) {
// 存储所有创建的结点(下标1开始)
CSTree pNodeArr101;
// 循环遍历变量
int iNodeIdx;
bool bResult = true;
// 初始化根结点
*ppRootNode = NULL;
// 1. 创建所有独立结点
for (iNodeIdx = 1; iNodeIdx <= iNodeCount; iNodeIdx++) {
pNodeArriNodeIdx = CreateNode(iValueArriNodeIdx);
if (pNodeArriNodeIdx == NULL) {
bResult = false;
break;
}
}
if (bResult) {
// 2. 建立孩子兄弟关系
for (iNodeIdx = 1; iNodeIdx <= iNodeCount; iNodeIdx++) {
// 双亲为0,当前结点为根结点
if (iParentArriNodeIdx == 0) {
*ppRootNode = pNodeArriNodeIdx;
} else {
// 获取当前结点的双亲结点
CSTree pParentNode = pNodeArriParentArr\[iNodeIdx];
// 双亲无孩子,当前结点作为第一个孩子
if (pParentNode->firstchild == NULL) {
pParentNode->firstchild = pNodeArriNodeIdx;
} else {
// 双亲有孩子,遍历兄弟链至末尾挂载
CSTree pSiblingNode = pParentNode->firstchild;
while (pSiblingNode->nextsibling != NULL) {
pSiblingNode = pSiblingNode->nextsibling;
}
pSiblingNode->nextsibling = pNodeArriNodeIdx;
}
}
}
}
return bResult;
}
1.2 查找值为x的结点
代码思想
-
终止条件:若当前结点为空,直接返回查找失败。
-
匹配判断:若当前结点数据等于目标值,返回查找成功。
-
递归查找:优先递归遍历孩子子树;若孩子子树未找到,再递归遍历兄弟结点。
代码实现
c
/**
* @brief 在树中查找指定数据的结点
* @paramin pTreeRoot 树的根结点
* @paramin iTarget 待查找的目标数据
* @paramout ppTargetNode 输出:找到的目标结点指针
* @return 找到返回true,未找到返回false
*/
bool Search(CSTree pTreeRoot, int iTarget, CSTree *ppTargetNode) {
bool bResult = false;
// 空树直接返回false
if (pTreeRoot == NULL) {
*ppTargetNode = NULL;
return bResult;
}
// 当前结点为目标结点
if (pTreeRoot->data == iTarget) {
*ppTargetNode = pTreeRoot;
bResult = true;
return bResult;
}
// 递归查找孩子子树
bool bChildFind = Search(pTreeRoot->firstchild, iTarget, ppTargetNode);
if (bChildFind) {
bResult = true;
return bResult;
}
// 递归查找兄弟结点
bool bSiblingFind = Search(pTreeRoot->nextsibling, iTarget, ppTargetNode);
if (bSiblingFind) {
bResult = true;
}
return bResult;
}
1.3 插入孩子(最右位置)
代码思想
-
合法性校验:若双亲结点为空,直接返回插入失败。
-
新结点创建:为待插入数据创建新的孩子结点。
-
位置判定:若双亲无孩子,新结点作为第一个孩子;若有孩子,遍历兄弟链至末尾,将新结点挂载为最后一个兄弟。
代码实现
c
/**
* @brief 为指定结点插入最右侧的孩子结点
* @paramin pParentNode 待插入孩子的双亲结点
* @paramin iChildData 新孩子结点的数据
* @return 插入成功返回true,失败返回false
*/
bool InsertChild(CSTree pParentNode, int iChildData) {
bool bResult = false;
// 双亲结点为空,直接返回false
if (pParentNode == NULL) {
return bResult;
}
// 创建新的孩子结点
CSTree pNewChild = CreateNode(iChildData);
if (pNewChild == NULL) {
return bResult;
}
// 双亲无孩子,新结点作为第一个孩子
if (pParentNode->firstchild == NULL) {
pParentNode->firstchild = pNewChild;
} else {
// 遍历兄弟链至末尾
CSTree pLastSibling = pParentNode->firstchild;
while (pLastSibling->nextsibling != NULL) {
pLastSibling = pLastSibling->nextsibling;
}
// 新结点挂载为最后一个兄弟
pLastSibling->nextsibling = pNewChild;
}
bResult = true;
return bResult;
}
1.4 删除以x为根的子树
代码思想
-
终止条件:若当前结点为空,直接返回删除失败。
-
目标匹配:若当前结点为待删除子树的根,先递归删除其所有孩子与兄弟结点,再释放当前结点内存并置空指针。
-
递归查找:若未匹配,分别递归查找并删除孩子子树、兄弟结点中的目标子树。
代码实现
c
/**
* @brief 删除以指定数据为根的子树
* @paramin,out ppTreeRoot 树的根结点指针的地址(二级指针)
* @paramin iDelTarget 待删除子树的根结点数据
* @return 删除成功返回true,失败返回false
*/
bool DeleteSubTree(CSTree *ppTreeRoot, int iDelTarget) {
bool bResult = false;
// 树为空,直接返回false
if (*ppTreeRoot == NULL) {
return bResult;
}
// 当前结点为待删除的根结点
if ((*ppTreeRoot)->data == iDelTarget) {
// 递归删除孩子子树
DeleteSubTree(&(*ppTreeRoot)->firstchild, iDelTarget);
// 递归删除兄弟结点
DeleteSubTree(&(*ppTreeRoot)->nextsibling, iDelTarget);
// 释放当前结点内存
free(*ppTreeRoot);
// 置空指针避免野指针
*ppTreeRoot = NULL;
bResult = true;
return bResult;
}
// 递归查找并删除孩子子树
bool bChildDel = DeleteSubTree(&(*ppTreeRoot)->firstchild, iDelTarget);
// 递归查找并删除兄弟结点
bool bSiblingDel = DeleteSubTree(&(*ppTreeRoot)->nextsibling, iDelTarget);
if (bChildDel || bSiblingDel) {
bResult = true;
}
return bResult;
}
1.5 先序遍历
代码思想
-
终止条件:若当前结点为空,直接返回遍历成功。
-
访问顺序:遵循「根结点→孩子子树→兄弟结点」的顺序,先访问当前结点,再递归遍历孩子子树,最后递归遍历兄弟结点。
代码实现
c
/**
* @brief 先序遍历孩子兄弟表示法的树
* @paramin pTreeRoot 树的根结点
* @return 遍历成功返回true,失败返回false
*/
bool PreOrder(CSTree pTreeRoot) {
bool bResult = true;
// 空树直接返回true
if (pTreeRoot == NULL) {
return bResult;
}
// 访问当前根结点
printf("%d ", pTreeRoot->data);
// 递归遍历孩子子树
PreOrder(pTreeRoot->firstchild);
// 递归遍历兄弟结点
PreOrder(pTreeRoot->nextsibling);
return bResult;
}
1.6 求树深度
代码思想
-
终止条件:若当前结点为空,深度为0。
-
递归计算:分别递归求解孩子子树深度、兄弟子树深度。
-
结果推导:取孩子与兄弟子树深度的最大值,加1得到当前结点所在子树的深度。
代码实现
c
/**
* @brief 计算孩子兄弟表示法的树的深度
* @paramin pTreeRoot 树的根结点
* @paramout piTreeDepth 输出:树的深度值
* @return 计算成功返回true,失败返回false
*/
bool GetDepth(CSTree pTreeRoot, int *piTreeDepth) {
bool bResult = true;
int iChildDepth, iSiblingDepth;
// 空结点深度为0
if (pTreeRoot == NULL) {
*piTreeDepth = 0;
return bResult;
}
// 递归计算孩子子树深度
GetDepth(pTreeRoot->firstchild, &iChildDepth);
// 递归计算兄弟子树深度
GetDepth(pTreeRoot->nextsibling, &iSiblingDepth);
// 取最大值加1为当前结点深度
if (iChildDepth > iSiblingDepth) {
*piTreeDepth = iChildDepth + 1;
} else {
*piTreeDepth = iSiblingDepth + 1;
}
return bResult;
}
1.7 总结点数
代码思想
-
终止条件:若当前结点为空,结点数为0。
-
递归统计:分别递归统计孩子子树、兄弟子树的结点数。
-
结果累加:总结点数 = 当前结点(1) + 孩子子树结点数 + 兄弟子树结点数。
代码实现
c
/**
* @brief 统计树的总结点数
* @paramin pTreeRoot 树的根结点
* @paramout piNodeCount 输出:总结点数量
* @return 统计成功返回true,失败返回false
*/
bool CountNodes(CSTree pTreeRoot, int *piNodeCount) {
bool bResult = true;
int iChildCount, iSiblingCount;
// 空树结点数为0
if (pTreeRoot == NULL) {
*piNodeCount = 0;
return bResult;
}
// 递归统计孩子子树结点数
CountNodes(pTreeRoot->firstchild, &iChildCount);
// 递归统计兄弟子树结点数
CountNodes(pTreeRoot->nextsibling, &iSiblingCount);
// 当前结点 + 孩子子树结点数 + 兄弟子树结点数
*piNodeCount = 1 + iChildCount + iSiblingCount;
return bResult;
}
1.8 叶子结点数
代码思想
-
终止条件:若当前结点为空,叶子数为0。
-
叶子判定:若结点无孩子(firstchild=NULL),为叶子结点,叶子数 = 1 + 兄弟子树叶子数。
-
非叶子处理:递归统计孩子子树、兄弟子树的叶子数,累加得到总叶子数。
代码实现
c
/**
* @brief 统计树的叶子结点数
* @paramin pTreeRoot 树的根结点
* @paramout piLeafCount 输出:叶子结点数量
* @return 统计成功返回true,失败返回false
*/
bool CountLeaves(CSTree pTreeRoot, int *piLeafCount) {
bool bResult = true;
int iChildLeaf, iSiblingLeaf;
// 空树叶子数为0
if (pTreeRoot == NULL) {
*piLeafCount = 0;
return bResult;
}
// 无孩子结点,为叶子结点
if (pTreeRoot->firstchild == NULL) {
// 递归统计兄弟子树叶子数
CountLeaves(pTreeRoot->nextsibling, &iSiblingLeaf);
// 当前叶子 + 兄弟子树叶子数
*piLeafCount = 1 + iSiblingLeaf;
} else {
// 递归统计孩子子树叶子数
CountLeaves(pTreeRoot->firstchild, &iChildLeaf);
// 递归统计兄弟子树叶子数
CountLeaves(pTreeRoot->nextsibling, &iSiblingLeaf);
// 累加叶子数
*piLeafCount = iChildLeaf + iSiblingLeaf;
}
return bResult;
}
- 7种高频题型及递归时间复杂度分析
题型1:求树深度
代码思想
-
终止条件:空结点深度为0。
-
递归求解:分别递归计算孩子子树、兄弟子树的深度。
-
结果计算:取两者深度的最大值,加1得到当前子树深度。
代码实现
c
bool GetDepth(CSTree pTreeRoot, int *piTreeDepth) {
bool bResult = true;
int iChildDepth, iSiblingDepth;
if (pTreeRoot == NULL) {
*piTreeDepth = 0;
return bResult;
}
GetDepth(pTreeRoot->firstchild, &iChildDepth);
GetDepth(pTreeRoot->nextsibling, &iSiblingDepth);
if (iChildDepth > iSiblingDepth) {
*piTreeDepth = iChildDepth + 1;
} else {
*piTreeDepth = iSiblingDepth + 1;
}
return bResult;
}
递归式:T(n) = T(left) + T(right) + O(1)
时间复杂度:O(n)
LeetCode 关联题:104. 二叉树的最大深度
C语言解析
c
// 二叉树结点定义
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
};
// 求二叉树最大深度(对应孩子兄弟树深度求解思想)
int maxDepth(struct TreeNode* root) {
// 终止条件:空结点深度为0
if (root == NULL) {
return 0;
}
// 递归求左子树深度
int leftDepth = maxDepth(root->left);
// 递归求右子树深度
int rightDepth = maxDepth(root->right);
// 取最大值加1
return (leftDepth > rightDepth ? leftDepth : rightDepth) + 1;
}
题型2:求总结点数
代码思想
-
终止条件:空树结点数为0。
-
递归统计:分别递归统计孩子子树、兄弟子树的结点数。
-
结果累加:总结点数 = 1(当前结点)+ 孩子结点数 + 兄弟结点数。
代码实现
c
bool CountNodes(CSTree pTreeRoot, int *piNodeCount) {
bool bResult = true;
int iChildCount, iSiblingCount;
if (pTreeRoot == NULL) {
*piNodeCount = 0;
return bResult;
}
CountNodes(pTreeRoot->firstchild, &iChildCount);
CountNodes(pTreeRoot->nextsibling, &iSiblingCount);
*piNodeCount = 1 + iChildCount + iSiblingCount;
return bResult;
}
递归式:T(n) = T(left) + T(right) + 1
时间复杂度:O(n)
LeetCode 关联题:222. 完全二叉树的节点个数
C语言解析
c
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
};
// 统计完全二叉树结点数(通用递归统计思想)
int countNodes(struct TreeNode* root) {
if (root == NULL) {
return 0;
}
// 递归统计左、右子树结点数
int left = countNodes(root->left);
int right = countNodes(root->right);
// 累加结果
return left + right + 1;
}
题型3:求叶子结点数
代码思想
-
终止条件:空树叶子数为0。
-
叶子判定:无孩子的结点为叶子,叶子数 = 1 + 兄弟子树叶子数。
-
非叶子处理:递归统计孩子、兄弟子树叶子数并累加。
代码实现
c
bool CountLeaves(CSTree pTreeRoot, int *piLeafCount) {
bool bResult = true;
int iChildLeaf, iSiblingLeaf;
if (pTreeRoot == NULL) {
*piLeafCount = 0;
return bResult;
}
if (pTreeRoot->firstchild == NULL) {
CountLeaves(pTreeRoot->nextsibling, &iSiblingLeaf);
*piLeafCount = 1 + iSiblingLeaf;
} else {
CountLeaves(pTreeRoot->firstchild, &iChildLeaf);
CountLeaves(pTreeRoot->nextsibling, &iSiblingLeaf);
*piLeafCount = iChildLeaf + iSiblingLeaf;
}
return bResult;
}
递归式:T(n) = T(left) + T(right) + O(1)
时间复杂度:O(n)
LeetCode 关联题:404. 左叶子之和
C语言解析
c
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
};
// 统计左叶子之和(叶子判定+递归遍历思想)
int sumOfLeftLeaves(struct TreeNode* root) {
if (root == NULL) {
return 0;
}
int sum = 0;
// 判定左孩子是否为叶子结点
if (root->left != NULL && root->left->left == NULL && root->left->right == NULL) {
sum += root->left->val;
}
// 递归遍历左、右子树
sum += sumOfLeftLeaves(root->left);
sum += sumOfLeftLeaves(root->right);
return sum;
}
题型4:森林转二叉树
代码思想
-
边界处理:若森林为空,直接返回空二叉树。
-
根结点设定:以森林中第一棵树的根作为二叉树根结点。
-
兄弟链接:遍历森林中剩余树的根结点,依次链接为二叉树的右兄弟链。
代码实现
c
/**
* @brief 将森林转换为孩子兄弟表示法的二叉树
* @paramin pTreeRootArr 森林中各树的根结点数组(下标1开始)
* @paramin iTreeCount 森林中树的棵数
* @paramout ppBinaryRoot 输出:二叉树根结点
* @return 转换成功返回true,失败返回false
*/
bool ForestToBinaryTree(CSTree pTreeRootArr\[\], int iTreeCount, CSTree *ppBinaryRoot) {
bool bResult = true;
int iTreeIdx;
CSTree pSiblingNode;
// 森林为空
if (iTreeCount == 0) {
*ppBinaryRoot = NULL;
return bResult;
}
// 第一棵树的根为二叉树根
*ppBinaryRoot = pTreeRootArr1;
pSiblingNode = *ppBinaryRoot;
// 遍历所有树,链接为右兄弟
for (iTreeIdx = 2; iTreeIdx <= iTreeCount; iTreeIdx++) {
pSiblingNode->nextsibling = pTreeRootArriTreeIdx;
pSiblingNode = pSiblingNode->nextsibling;
}
return bResult;
}
算法特性:非递归算法
时间复杂度:O(k)(k为森林中树的棵数)
LeetCode 关联题:897. 递增顺序搜索树
C语言解析
c
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
};
// 将二叉树转为单链(类似森林转二叉树的链接思想)
struct TreeNode* increasingBST(struct TreeNode* root) {
struct TreeNode* dummy = (struct TreeNode*)malloc(sizeof(struct TreeNode));
struct TreeNode* cur = dummy;
void inorder(struct TreeNode* node) {
if (node == NULL) return;
inorder(node->left);
// 链接为右链
cur->right = node;
node->left = NULL;
cur = node;
inorder(node->right);
}
inorder(root);
return dummy->right;
}
题型5:二叉树还原森林
代码思想
-
初始化:清空森林根结点数组与树的棵数。
-
遍历拆分:遍历二叉树的右兄弟链,依次取出根结点存入数组。
-
断开链接:拆分时断开每个根结点的nextsibling指针,还原为独立的树。
代码实现
c
/**
* @brief 将孩子兄弟二叉树还原为森林
* @paramin pBinaryRoot 二叉树根结点
* @paramout ppTreeRootArr 输出:森林各树的根结点数组(下标1开始)
* @paramout piTreeCount 输出:森林中树的棵数
* @return 还原成功返回true,失败返回false
*/
bool BinaryTreeToForest(CSTree pBinaryRoot, CSTree ppTreeRootArr\[\], int *piTreeCount) {
bool bResult = true;
CSTree pNextNode;
*piTreeCount = 0;
// 遍历右兄弟链拆分
while (pBinaryRoot != NULL) {
(*piTreeCount)++;
ppTreeRootArr\*piTreeCount = pBinaryRoot;
pNextNode = pBinaryRoot->nextsibling;
// 断开兄弟链接
pBinaryRoot->nextsibling = NULL;
pBinaryRoot = pNextNode;
}
return bResult;
}
算法特性:非递归算法
时间复杂度:O(k)(k为森林中树的棵数)
LeetCode 关联题:430. 扁平化多级双向链表
C语言解析
c
struct Node {
int val;
struct Node* prev;
struct Node* next;
struct Node* child;
};
// 扁平化多级链表(类似二叉树还原森林的拆分思想)
struct Node* flatten(struct Node* head) {
if (head == NULL) return NULL;
struct Node* cur = head;
while (cur != NULL) {
if (cur->child != NULL) {
struct Node* next = cur->next;
struct Node* child = cur->child;
// 扁平化子链
while (child->next != NULL) {
child = child->next;
}
// 链接拆分
child->next = next;
if (next != NULL) next->prev = child;
cur->next = cur->child;
cur->child->prev = cur;
cur->child = NULL;
}
cur = cur->next;
}
return head;
}
题型6:先序遍历
代码思想
-
终止条件:空结点直接返回。
-
遍历顺序:遵循「根→孩子→兄弟」,先访问当前结点,再递归遍历孩子子树,最后递归遍历兄弟结点。
代码实现
c
bool PreOrder(CSTree pTreeRoot) {
bool bResult = true;
if (pTreeRoot == NULL) {
return bResult;
}
printf("%d ", pTreeRoot->data);
PreOrder(pTreeRoot->firstchild);
PreOrder(pTreeRoot->nextsibling);
return bResult;
}
递归式:T(n) = T(left) + T(right) + O(1)
时间复杂度:O(n)
LeetCode 关联题:144. 二叉树的前序遍历
C语言解析
c
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
};
// 二叉树前序遍历(对应孩子兄弟树先序遍历)
void preorder(struct TreeNode* root, int* res, int* returnSize) {
if (root == NULL) return;
// 访问根
res(\*returnSize)++ = root->val;
// 遍历左(孩子)
preorder(root->left, res, returnSize);
// 遍历右(兄弟)
preorder(root->right, res, returnSize);
}
int* preorderTraversal(struct TreeNode* root, int* returnSize) {
*returnSize = 0;
int* res = (int*)malloc(100 * sizeof(int));
preorder(root, res, returnSize);
return res;
}
题型7:后序遍历
代码思想
-
终止条件:空结点直接返回。
-
遍历顺序:遵循「孩子→根→兄弟」,先递归遍历孩子子树,再访问当前结点,最后递归遍历兄弟结点。
代码实现
c
/**
* @brief 后序遍历孩子兄弟表示法的树
* @paramin pTreeRoot 树的根结点
* @return 遍历成功返回true,失败返回false
*/
bool PostOrder(CSTree pTreeRoot) {
bool bResult = true;
if (pTreeRoot == NULL) {
return bResult;
}
// 递归遍历孩子子树
PostOrder(pTreeRoot->firstchild);
// 访问当前根结点
printf("%d ", pTreeRoot->data);
// 递归遍历兄弟结点
PostOrder(pTreeRoot->nextsibling);
return bResult;
}
递归式:T(n) = T(left) + T(right) + O(1)
时间复杂度:O(n)
LeetCode 关联题:145. 二叉树的后序遍历
C语言解析
c
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
};
// 二叉树后序遍历(对应孩子兄弟树后序遍历)
void postorder(struct TreeNode* root, int* res, int* returnSize) {
if (root == NULL) return;
// 遍历左(孩子)
postorder(root->left, res, returnSize);
// 遍历右(兄弟)
postorder(root->right, res, returnSize);
// 访问根
res(\*returnSize)++ = root->val;
}
int* postorderTraversal(struct TreeNode* root, int* returnSize) {
*returnSize = 0;
int* res = (int*)malloc(100 * sizeof(int));
postorder(root, res, returnSize);
return res;
}
核心总结
-
结构本质:孩子兄弟表示法通过 firstchild 指向第一个孩子、 nextsibling 指向右兄弟,将普通树/森林转化为二叉树存储,是树与二叉树转换的核心方法。
-
递归算法复杂度:所有基于递归的遍历、计数、深度求解等操作,均需访问树中全部n个结点,时间复杂度统一为O(n)。
-
森林与二叉树转换:转换过程仅需遍历根结点链,时间复杂度为O(k)(k为树的棵数),属于线性时间复杂度算法。
-
操作特性:孩子兄弟表示法的插入、删除、查找等操作均基于深度优先递归实现,逻辑简洁且符合树结构的递归特性;统一返回 bool 型标识操作结果,参数区分输入输出,代码规范性与可读性更强。
-
题型关联:7种高频题型均对应LeetCode经典二叉树题目,核心思想一致,可通过二叉树题目强化孩子兄弟表示法的递归逻辑理解。