[数据结构]LeetCode学习

二叉树节点定义与初始化

// 二叉树节点结构(通用模板)

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;

}

  1. 堆调整:找最大孩子,交换,递归下沉

  2. 建堆:从 n/2 - 1 往前逐个 heapify

  3. 下标:左孩子 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) {

}

孩子兄弟表示法核心操作与高频题型解析

  1. 孩子兄弟表示法常规操作(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 创建树(双亲数组法)

代码思想

  1. 初始化阶段:根据给定的结点总数,循环创建所有独立的结点对象。

  2. 关系构建阶段:遍历双亲数组,为每个结点匹配对应的双亲结点。

  3. 根结点判定:双亲值为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的结点

代码思想

  1. 终止条件:若当前结点为空,直接返回查找失败。

  2. 匹配判断:若当前结点数据等于目标值,返回查找成功。

  3. 递归查找:优先递归遍历孩子子树;若孩子子树未找到,再递归遍历兄弟结点。

代码实现

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 插入孩子(最右位置)

代码思想

  1. 合法性校验:若双亲结点为空,直接返回插入失败。

  2. 新结点创建:为待插入数据创建新的孩子结点。

  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为根的子树

代码思想

  1. 终止条件:若当前结点为空,直接返回删除失败。

  2. 目标匹配:若当前结点为待删除子树的根,先递归删除其所有孩子与兄弟结点,再释放当前结点内存并置空指针。

  3. 递归查找:若未匹配,分别递归查找并删除孩子子树、兄弟结点中的目标子树。

代码实现

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 先序遍历

代码思想

  1. 终止条件:若当前结点为空,直接返回遍历成功。

  2. 访问顺序:遵循「根结点→孩子子树→兄弟结点」的顺序,先访问当前结点,再递归遍历孩子子树,最后递归遍历兄弟结点。

代码实现

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 求树深度

代码思想

  1. 终止条件:若当前结点为空,深度为0。

  2. 递归计算:分别递归求解孩子子树深度、兄弟子树深度。

  3. 结果推导:取孩子与兄弟子树深度的最大值,加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 总结点数

代码思想

  1. 终止条件:若当前结点为空,结点数为0。

  2. 递归统计:分别递归统计孩子子树、兄弟子树的结点数。

  3. 结果累加:总结点数 = 当前结点(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 叶子结点数

代码思想

  1. 终止条件:若当前结点为空,叶子数为0。

  2. 叶子判定:若结点无孩子(firstchild=NULL),为叶子结点,叶子数 = 1 + 兄弟子树叶子数。

  3. 非叶子处理:递归统计孩子子树、兄弟子树的叶子数,累加得到总叶子数。

代码实现

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;

}

  1. 7种高频题型及递归时间复杂度分析

题型1:求树深度

代码思想

  1. 终止条件:空结点深度为0。

  2. 递归求解:分别递归计算孩子子树、兄弟子树的深度。

  3. 结果计算:取两者深度的最大值,加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:求总结点数

代码思想

  1. 终止条件:空树结点数为0。

  2. 递归统计:分别递归统计孩子子树、兄弟子树的结点数。

  3. 结果累加:总结点数 = 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:求叶子结点数

代码思想

  1. 终止条件:空树叶子数为0。

  2. 叶子判定:无孩子的结点为叶子,叶子数 = 1 + 兄弟子树叶子数。

  3. 非叶子处理:递归统计孩子、兄弟子树叶子数并累加。

代码实现

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:森林转二叉树

代码思想

  1. 边界处理:若森林为空,直接返回空二叉树。

  2. 根结点设定:以森林中第一棵树的根作为二叉树根结点。

  3. 兄弟链接:遍历森林中剩余树的根结点,依次链接为二叉树的右兄弟链。

代码实现

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:二叉树还原森林

代码思想

  1. 初始化:清空森林根结点数组与树的棵数。

  2. 遍历拆分:遍历二叉树的右兄弟链,依次取出根结点存入数组。

  3. 断开链接:拆分时断开每个根结点的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:先序遍历

代码思想

  1. 终止条件:空结点直接返回。

  2. 遍历顺序:遵循「根→孩子→兄弟」,先访问当前结点,再递归遍历孩子子树,最后递归遍历兄弟结点。

代码实现

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:后序遍历

代码思想

  1. 终止条件:空结点直接返回。

  2. 遍历顺序:遵循「孩子→根→兄弟」,先递归遍历孩子子树,再访问当前结点,最后递归遍历兄弟结点。

代码实现

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;

}

核心总结

  1. 结构本质:孩子兄弟表示法通过 firstchild 指向第一个孩子、 nextsibling 指向右兄弟,将普通树/森林转化为二叉树存储,是树与二叉树转换的核心方法。

  2. 递归算法复杂度:所有基于递归的遍历、计数、深度求解等操作,均需访问树中全部n个结点,时间复杂度统一为O(n)。

  3. 森林与二叉树转换:转换过程仅需遍历根结点链,时间复杂度为O(k)(k为树的棵数),属于线性时间复杂度算法。

  4. 操作特性:孩子兄弟表示法的插入、删除、查找等操作均基于深度优先递归实现,逻辑简洁且符合树结构的递归特性;统一返回 bool 型标识操作结果,参数区分输入输出,代码规范性与可读性更强。

  5. 题型关联:7种高频题型均对应LeetCode经典二叉树题目,核心思想一致,可通过二叉树题目强化孩子兄弟表示法的递归逻辑理解。

相关推荐
x138702859571 小时前
c语言排雷游戏(基础版9*9)
c语言·算法·游戏
sheeta19982 小时前
LeetCode 每日一题笔记 日期:2026.06.06 题目:2196. 根据描述创建二叉树
笔记·算法·leetcode
小欣加油2 小时前
leetcode994 腐烂的橘子
数据结构·c++·算法·leetcode·bfs
QuZero3 小时前
Guava Cache Deep Dive
java·后端·算法·guava
随意起个昵称3 小时前
线性dp-LIS题目4(A Twisty Movement)
算法·动态规划
Felven3 小时前
B. Fair Numbers
数据结构·算法
人道领域3 小时前
【LeetCode刷题日记】93.复原IP地址
java·开发语言·算法·leetcode
jarreyer4 小时前
【算法记录1】模型训练问题
算法
Felven4 小时前
D. Friends and the Restaurant
算法