二叉树入门:从概念到代码实现

个人专栏:《数据结构-初阶》《经典OJ题目》《C语言》

欢迎大佬交流

在了解二叉树之前,我们需要有树这个结构的前置知识

一、树和二叉树的概念及结构

1、树的基本概念

树是一种非线性的数据结构,由节点(Node)和边(Edge)组成,具有层次关系;

每个节点包含数据及指向其他节点的边;

树结构常用于表示具有层级关系的数据,如文件系统、组织结构等

2、树的术语

  • 根节点(Root):树的顶层节点,没有父节点。
  • 子节点(Child):一个节点的直接下级节点。
  • 父节点(Parent):一个节点的直接上级节点。
  • 叶子节点(Leaf):没有子节点的节点。
  • 内部节点(Internal Node):至少有一个子节点的非根节点。
  • 兄弟节点(Sibling):具有相同父节点的节点。
  • 深度(Depth):从根节点到该节点的路径长度(边数)。
  • 高度(Height):从该节点到最深叶子节点的路径长度。
  • 度(Degree):一个节点的子节点数量

3、树的分类

  • 二叉树(Binary Tree):每个节点最多有两个子节点(左子节点和右子节点)。
  • 二叉搜索树(BST):左子节点的值小于父节点,右子节点的值大于父节点。
  • 平衡二叉树(AVL树):任意节点的左右子树高度差不超过1。
  • 完全二叉树:除最后一层外,其他层节点全部填满,最后一层从左到右填充。
  • 满二叉树:所有非叶子节点都有两个子节点,且所有叶子节点在同一层

4、二叉树的概念

二叉树是一种非线性数据结构;

由节点组成,每个节点最多有两个子节点;分别称为左子节点和右子节点;

节点之间的关系是父子关系,没有子节点的节点称为叶子节点;

二叉树具有递归性质,即每个子树本身也是一棵二叉树。

5、二叉树的存储结构

二叉树的存储结构通常分为顺序存储和链式存储两种方式:

顺序存储

使用数组存储节点,按照层次遍历的顺序依次存放节点;

对于完全二叉树,这种存储方式可以高效利用空间;

但对于非完全二叉树,可能会浪费较多存储空间。

链式存储

通过节点对象和指针(或引用)实现,每个节点包含数据域和左右指针域;

链式存储更灵活,适合任意形态的二叉树。

堆其实已经帮助我们理解顺序结构的二叉树了

因此,下面我们着重来了解链式结构

二、树的链式结构的实现

注:在当前阶段,我们只是学习二叉树的基本操作,实现方式并非真正创建二叉树的方式

我们直接给出代码

cpp 复制代码
//BinaryTree.h
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

typedef int BTDataType;

typedef struct BinaryTreeNode
{
    BTDataType val;
    struct BinaryTreeNode* left;
    struct BinaryTreeNode* right;
}BTNode;



//test.c
#include "BinaryTree.h"


BTNode* BuyNode(BTDataType x)
{
    BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
    newnode->left = NULL;
    newnode->right = NULL;
    newnode->val = x;

    return newnode;
}

BTNode* CreatBinaryTree()
{
    BTNode* node1 = BuyNode(1);
    BTNode* node2 = BuyNode(2);
    BTNode* node3 = BuyNode(3);
    BTNode* node4 = BuyNode(4);
    BTNode* node5 = BuyNode(5);
    BTNode* node6 = BuyNode(6);

    node1->left = node2;
    node1->right = node4;
    node2->left = node3;
    node4->left = node5;
    node4->right = node6;

    return node1;
}

上述代码实现的树如图所示:

1、二叉树的遍历

a、前序遍历

前序遍历(Preorder Traversal)是二叉树遍历的一种方式;

其访问顺序遵循"根节点→左子树→右子树"的规则

我们通过一张图来模拟实现

分析逻辑:从根节点出发,先递归左子树,再递归右子树

此时遇到空,开始回退到节点3,此时已经输出1, 2, 3

下面我们直接来看模拟后的最终结果

cpp 复制代码
//前序遍历
void PreOrder(BTNode* root)
{
    if (root == NULL) return;

    printf("%d ", root->val);
    PreOrder(root->left);
    PreOrder(root->right);
}

梳理一下逻辑:

对于整棵树的根节点和左右子树来说,先输出根节点,再输出左子树,最后输出右子树

而对于以左子树的根节点来说,同样是先输出根节点,接着再输出左子树的左子树,左子树的右子树;

右子树同理,因此我们便采用递归的方式进行遍历;

什么时候结束呢?

显然是当前节点为空时,直接返回即可;

cpp 复制代码
//前序遍历
void PreOrder(BTNode* root)
{
    if (root == NULL) return;

    printf("%d ", root->val);
    PreOrder(root->left);
    PreOrder(root->right);
}

我们来通过递归展开图来分析前序遍历的流程

b、中序遍历

根据前序遍历的经验,同样我们使用递归来处理

中序遍历即左子树 根节点 右子树

核心逻辑:

当左子树处理完之后,回退到以左子树为根节点时进行打印;

接着处理右子树

cpp 复制代码
//中序遍历
void InOrder(BTNode* root)
{
    if (root == NULL) return;

    InOrder(root->left);
    printf("%d ", root->val);
    InOrder(root->right);
}

来测一下

c、后序遍历

后序遍历即左子树 右子树 根节点

核心逻辑:处理完左右子树之后再处理根节点,当右子树回退到根节点时再打印

d、层序遍历

层序遍历(Level Order Traversal)是二叉树遍历的一种方式;

按照树的层级从上到下、从左到右依次访问节点;

这种遍历方式借助队列(Queue)数据结构实现,确保每一层的节点按顺序处理

层序遍历的步骤

  1. 初始化队列:将根节点放入队列中。
  2. 循环处理队列
    • 从队列头部取出一个节点并访问。
    • 若该节点有左子节点,将左子节点加入队列尾部。
    • 若该节点有右子节点,将右子节点加入队列尾部。
  3. 重复步骤2:直到队列为空,遍历结束
cpp 复制代码
//层序遍历
void LevelOrder(BTNode* root)
{
    if (root == NULL) return;

    Queue q;
    QueueInit(&q);

    QueuePush(&q, root);          

    while (!QueueEmpty(&q))
    {
        BTNode* front = QueueFront(&q);
        QueuePop(&q);                    

        printf("%d ", front->val);        

        // 左右孩子入队
        if (front->left)
            QueuePush(&q, front->left);
        if (front->right)
            QueuePush(&q, front->right);
    }

    QueueDestroy(&q);
}

2、二叉树的节点个数及高度

由于二叉树本身非常符合递归的特点,因此我们需要着重考虑递归来解决问题

a、二叉树的节点个数

分析逻辑:

二叉树的高度可以理解成左子树的节点数量 + 右子树的节点数量 + 1(自身节点);

而左子树的节点数量同样是以二叉树的左子树节点为根节点的左子树节点数量 + 右子树节点数量 + 1;

右子树同理!

我们画出递归流程图:

和后序遍历非常相似,我们仿照后序遍历给出代码

cpp 复制代码
//二叉树节点个数
int BinaryTreeSize(BTNode* root)
{
    if (root == NULL) return 0;

    int leftnums = BinaryTreeSize(root->left);
    int rightnums = BinaryTreeSize(root->right);

    return leftnums + rightnums + 1;
}

在此基础上对代码进行优化

cpp 复制代码
//二叉树节点个数
int BinaryTreeSize(BTNode* root)
{
    //if (root == NULL) return 0;

    //int leftnums = BinaryTreeSize(root->left);
    //int rightnums = BinaryTreeSize(root->right);

    //return leftnums + rightnums + 1;

    return root == NULL ? 0 : BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}

b、二叉树叶子节点个数

分析逻辑:

和计算二叉树节点个数一样,依旧采用递归来处理,左子树的叶子节点个数 + 右子树的叶子节点个数即可

我们先画出流程图

cpp 复制代码
//二叉树叶子节点个数
int BinaryTreeLeafsize(BTNode* root)
{
    if (root == NULL) return 0;

    if (root->left == NULL && root->right == NULL) return 1;

    return BinaryTreeLeafsize(root->left) + BinaryTreeLeafsize(root->right);
}

c、二叉树第k层节点个数

分析逻辑:

二叉树的第k层节点个数,即为左子树的第k-1层节点个数 + 右子树的第k-1个数;

因此,当 k == 1时,直接return 1即可

cpp 复制代码
//二叉树第k层节点个数
int TreeLevelKSize(BTNode* root, int k)
{
    if (root == NULL)
        return 0;

    if (k == 1)
        return 1;

    //子问题
    return TreeLevelKSize(root->left, k - 1)
        + TreeLevelKSize(root->right, k - 1);
}

如有不足之处欢迎大佬指出!

相关推荐
星轨初途14 小时前
【C++ 进阶】list 核心机制解析及 vector 巅峰对决
开发语言·数据结构·c++·经验分享·笔记·list
承渊政道14 小时前
我的创作纪念日写在创作第256天:从第一篇C语言博客,到一路向前的自己!
c语言·开发语言·笔记·学习·学习方法
2401_8685347815 小时前
华为系OSPF 配置命令全总结(2026 精简版
网络·数据结构
Brilliantwxx15 小时前
【算法题】 面试级别的二叉树题目OJ复习(下)
数据结构·c++·算法·leetcode·面试·哈希算法·推荐算法
handler0115 小时前
【Linux】五种IO模型详解
linux·运维·服务器·c语言·网络·笔记·php
Rabitebla15 小时前
C++ 继承详解(下):默认成员函数、虚继承底层与设计取舍
c语言·开发语言·数据结构·c++·算法·leetcode
wljy11 天前
二、进制状态转换
linux·运维·服务器·c语言·c++
01_ice1 天前
C语言数据在内存中的存储
c语言·开发语言
bucenggaibian1 天前
《C语言》编程前置:计算机底层逻辑(诞生的基础)
c语言·程序框架·编译运行·内存地址·底层逻辑