当前位置: 首页 > news >正文

《从零搭建二叉树体系:从节点定义到子树判断的实战指南(含源码可直接运行)》

目录

  1. 二叉树基础概念
  2. 环境准备与目录结构
  3. 二叉树的构建
  4. 二叉树的遍历
  5. 二叉树的基本属性
  6. 经典算法题解析
  7. 易错点与注意事项

1. 二叉树基础概念

二叉树是每个节点最多有两个子树的树结构,通常子树被称为“左子树”和“右子树”。

基本术语

  • 根节点:树的起始节点(无父节点)
  • 叶子节点:无左右子树的节点
  • 节点深度:从根到该节点的边数
  • 节点高度:从该节点到最深叶子节点的边数
  • 树的高度:根节点的高度

特殊二叉树

  • 满二叉树:所有叶子节点都在同一层,且非叶子节点都有两个子节点
  • 完全二叉树:除最后一层外,其余层全满,最后一层节点靠左排列
  • 平衡二叉树:左右子树高度差不超过1的二叉树

2. 环境准备与目录结构

为了系统学习二叉树操作,建议使用以下目录结构:

binary_tree/
├── basic/                 # 基础操作
│   ├── tree_node.h        # 节点定义
│   ├── tree_construct.c   # 构建二叉树
│   └── tree_traversal.c   # 遍历操作
├── problems/              # 算法题目
│   ├── invert_tree.c      # 翻转二叉树
│   ├── is_balanced.c      # 判断平衡二叉树
│   ├── is_same_tree.c     # 检查两棵树是否相同
│   ├── is_subtree.c       # 另一棵树的子树
│   └── is_symmetric.c     # 对称二叉树
└── test/                  # 测试代码└── main.c

3. 二叉树的构建

3.1 节点定义

首先定义二叉树节点结构(tree_node.h):

#ifndef TREE_NODE_H
#define TREE_NODE_Htypedef char TreeDataType;  // 可根据需要修改数据类型typedef struct TreeNode {TreeDataType data;struct TreeNode* left;  // 左子树struct TreeNode* right; // 右子树
} TreeNode;#endif

3.2 手动构建二叉树

通过手动创建节点并连接(tree_construct.c):

#include "tree_node.h"
#include <stdlib.h>
#include <stdio.h>// 创建单个节点
TreeNode* createNode(TreeDataType data) {TreeNode* node = (TreeNode*)malloc(sizeof(TreeNode));if (node == NULL) {perror("malloc failed");return NULL;}node->data = data;node->left = NULL;node->right = NULL;return node;
}// 手动构建示例二叉树
TreeNode* buildManualTree() {// 创建节点TreeNode* node1 = createNode('1');TreeNode* node2 = createNode('2');TreeNode* node3 = createNode('3');TreeNode* node4 = createNode('4');TreeNode* node5 = createNode('5');TreeNode* node6 = createNode('6');// 连接节点node1->left = node2;node1->right = node4;node2->left = node3;node4->left = node5;node4->right = node6;return node1;
}

3.3 先序遍历字符串构建二叉树

通过先序遍历序列(含空节点标记)构建二叉树:

#include "tree_node.h"
#include <stdlib.h>// 先序遍历字符串创建二叉树('#'表示空节点)
TreeNode* buildTreeFromPreorder(const char* preStr, int* index) {// 终止条件:遇到空节点或字符串结束if (preStr[*index] == '#' || preStr[*index] == '\0') {(*index)++;return NULL;}// 创建当前节点TreeNode* node = createNode(preStr[*index]);(*index)++;// 递归构建左右子树node->left = buildTreeFromPreorder(preStr, index);node->right = buildTreeFromPreorder(preStr, index);return node;
}

重点解析

  1. 使用index指针跟踪字符串遍历位置,确保递归过程中共享同一个计数器
  2. 空节点用#标记,是重构二叉树的关键(仅靠先序遍历无法唯一确定二叉树)
  3. 递归顺序严格遵循"根->左->右"的先序规则

4. 二叉树的遍历

遍历是二叉树最基本的操作,分为深度优先(DFS)和广度优先(BFS)两大类。

4.1 深度优先遍历(DFS)

先序遍历(根->左->右)
void preorderTraversal(TreeNode* root) {if (root == NULL) {printf("# ");  // 打印空节点标记return;}printf("%c ", root->data);  // 访问根节点preorderTraversal(root->left);  // 遍历左子树preorderTraversal(root->right); // 遍历右子树
}
中序遍历(左->根->右)
void inorderTraversal(TreeNode* root) {if (root == NULL) {printf("# ");return;}inorderTraversal(root->left);   // 遍历左子树printf("%c ", root->data);      // 访问根节点inorderTraversal(root->right);  // 遍历右子树
}
后序遍历(左->右->根)
void postorderTraversal(TreeNode* root) {if (root == NULL) {printf("# ");return;}postorderTraversal(root->left);  // 遍历左子树postorderTraversal(root->right); // 遍历右子树printf("%c ", root->data);       // 访问根节点
}

4.2 遍历示例

对于如下二叉树:

      1/   \2     4/     / \3     5   6

三种遍历结果:

  • 先序:1 2 3 # # # 4 5 # # 6 # #
  • 中序:# 3 # 2 # 1 # 5 # 4 # 6 #
  • 后序:# # 3 # 2 # # 5 # # 6 # 1

5. 二叉树的基本属性

5.1 节点数量

int getNodeCount(TreeNode* root) {if (root == NULL) return 0;// 根节点数 + 左子树节点数 + 右子树节点数return 1 + getNodeCount(root->left) + getNodeCount(root->right);
}

5.2 树的高度

int getTreeHeight(TreeNode* root) {if (root == NULL) return 0;// 左子树高度与右子树高度的最大值 + 1(当前节点)int leftHeight = getTreeHeight(root->left);int rightHeight = getTreeHeight(root->right);return 1 + (leftHeight > rightHeight ? leftHeight : rightHeight);
}

5.3 查找节点

TreeNode* findNode(TreeNode* root, TreeDataType target) {if (root == NULL) return NULL;if (root->data == target) return root;// 先在左子树查找,找到则返回TreeNode* leftResult = findNode(root->left, target);if (leftResult != NULL) return leftResult;// 左子树未找到,在右子树查找return findNode(root->right, target);
}

易错点
查找节点时容易遗漏左子树的返回结果判断,直接写成:

findNode(root->left, target);  // 错误:未处理返回值
return findNode(root->right, target);

正确做法是先判断左子树是否找到,再查找右子树

6. 经典算法题解析

6.1 翻转二叉树

题目:翻转一棵二叉树(左右子树交换)

TreeNode* invertTree(TreeNode* root) {if (root == NULL) return NULL;// 交换左右子树TreeNode* temp = root->left;root->left = root->right;root->right = temp;// 递归翻转左右子树invertTree(root->left);invertTree(root->right);return root;
}

解析
采用后序遍历思想,先翻转左右子树,再交换当前节点的左右指针,时间复杂度O(n),空间复杂度O(h)(h为树高)。

6.2 判断平衡二叉树

题目:判断一棵二叉树是否为平衡二叉树(任意节点的左右子树高度差不超过1)

// 辅助函数:计算树的高度
int getHeight(TreeNode* root) {if (root == NULL) return 0;int left = getHeight(root->left);int right = getHeight(root->right);return 1 + (left > right ? left : right);
}// 判断是否为平衡二叉树
bool isBalanced(TreeNode* root) {if (root == NULL) return true;// 计算左右子树高度差int leftHeight = getHeight(root->left);int rightHeight = getHeight(root->right);if (abs(leftHeight - rightHeight) > 1) return false;// 递归判断左右子树return isBalanced(root->left) && isBalanced(root->right);
}

优化版本(避免重复计算):

// -1表示不平衡
int checkHeight(TreeNode* root) {if (root == NULL) return 0;int left = checkHeight(root->left);if (left == -1) return -1;  // 左子树不平衡int right = checkHeight(root->right);if (right == -1) return -1; // 右子树不平衡if (abs(left - right) > 1) return -1;return 1 + (left > right ? left : right);
}bool isBalanced(TreeNode* root) {return checkHeight(root) != -1;
}

6.3 检查两棵树是否相同

题目:判断两棵二叉树是否结构相同且节点值相等

bool isSameTree(TreeNode* p, TreeNode* q) {// 都为空则相同if (p == NULL && q == NULL) return true;// 一个为空一个不为空则不同if (p == NULL || q == NULL) return false;// 节点值不同则不同if (p->data != q->data) return false;// 递归判断左右子树return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}

解析
采用同步遍历思想,同时检查两个树的节点,时间复杂度O(min(m,n)),空间复杂度O(min(h1,h2))。

6.4 对称二叉树

题目:判断一棵二叉树是否是镜像对称的

// 辅助函数:判断两个子树是否对称
bool isSymmetricHelper(TreeNode* left, TreeNode* right) {if (left == NULL && right == NULL) return true;if (left == NULL || right == NULL) return false;if (left->data != right->data) return false;// 左子树的左与右子树的右对称,左子树的右与右子树的左对称return isSymmetricHelper(left->left, right->right) && isSymmetricHelper(left->right, right->left);
}bool isSymmetric(TreeNode* root) {if (root == NULL) return true;return isSymmetricHelper(root->left, root->right);
}

6.5 另一棵树的子树

题目:判断s是否为t的子树(结构和节点值完全一致)

// 检查两棵树是否相同(复用前面的函数)
bool isSameTree(TreeNode* p, TreeNode* q);bool isSubtree(TreeNode* root, TreeNode* subRoot) {if (root == NULL) return false;// 检查当前节点为根的树是否与subRoot相同if (isSameTree(root, subRoot)) return true;// 检查左子树或右子树是否包含subRootreturn isSubtree(root->left, subRoot) || isSubtree(root->right, subRoot);
}

7. 易错点与注意事项

  1. 空指针处理
    所有递归函数必须先判断root == NULL的情况,否则会导致访问空指针崩溃。

  2. 内存泄漏
    动态分配的节点需要手动释放,释放顺序应为:先释放左右子树,再释放当前节点:

    void freeTree(TreeNode* root) {if (root == NULL) return;freeTree(root->left);freeTree(root->right);free(root);  // 最后释放当前节点
    }
    
  3. 递归终止条件
    忘记设置递归终止条件会导致栈溢出,如计算树高时必须处理root == NULL的情况。

  4. 参数传递
    构建树时的index必须通过指针传递,否则递归过程中无法正确共享计数器。

  5. 二叉树唯一性
    仅通过先序/中序/后序遍历中的一种无法唯一确定二叉树,必须结合空节点标记或两种遍历序列。

总结

本文从二叉树的基础概念出发,讲解了二叉树的构建、遍历方法和基本属性计算,并通过6道经典算法题展示了二叉树的常见操作。掌握这些内容可以为后续学习更复杂的树结构(如二叉搜索树、红黑树)打下坚实基础。

http://www.dtcms.com/a/364015.html

相关文章:

  • 利用Base64传输二进制文件并执行的方法(适合没有ssh ftp等传输工具的嵌入式离线场景)
  • TDK InvenSense CH201距离传感器
  • Photoshop用户必看:让你的PSD像JPG一样可预览
  • vim中常见操作及命令
  • 趣说IT职场30:跨团队会议话术合集:优雅反对、不留记录
  • 使用DataLoader加载本地数据
  • Elasticsearch 核心特性与应用指南
  • 【js】Promise.try VS try-catch
  • 研发文档分散在本地和邮件里如何集中管理
  • 面试必避坑:MySQL 自增 ID 用尽问题深度解析与应对策略
  • XML在线格式化 - 加菲工具
  • 双Token实战:从无感刷新到安全防护,完整流程+代码解析
  • 魔域服务器多少钱一个月?魔域服务器配置要求及推荐
  • Vue 3.5 重磅新特性:useTemplateRef 让模板引用更优雅、更高效!
  • 服务器托管需要注意什么事项?
  • 人工智能助力流感疫苗选择:MIT 团队推出 VaxSeer 系统
  • MySQL注意事项与规范
  • 开发AI编程工具的方案分析
  • SPI片选踩坑实录(硬件片选和软件片选)
  • Nacos配置文件攻防思路总结|揭秘Nacos被低估的攻击面|挖洞技巧
  • Python 基础核心概念与实战代码示例(含数据类型、变量、流程控制、数据结构、函数与文件操作)
  • # Shell 文本处理三剑客:awk、sed 与常用小工具详解
  • 如何修改 Docker 默认网段(网络地址池)配置:以使用 10.x.x.x 网段为例
  • 2024 年 AI 产业格局复盘:头部企业竞逐方向与中小玩家生存破局点
  • 跨境电商账号风控核心:IP纯净度与浏览器指纹的防护策略
  • 基于单片机车流车速检测系统设计
  • 90%的C++ 程序员都忽略了这个容器——unordered_multiset,让我们来看看开源项目中怎么使用的
  • 最小二乘法之线性回归篇(普通最小二乘OLS、加权最小二乘WLS、广义最小二乘GLS)-原理讲解
  • 毕业项目推荐:69-基于yolov8/yolov5/yolo11的轴承缺陷检测识别系统(Python+卷积神经网络)
  • Python入门教程之类型转换