数据结构:实现链式结构二叉树(Tree) 手把手带你入门数据结构~

微软技术分享
优质创作者: 编程框架技术领域
领域专家: 操作系统技术领域
2026-03-29 11:32:14

一、链式结构二叉树的概念

链式结构二叉树是一种使用链表(或指针)来表示二叉树的数据结构。与数组表示的二叉树不同,链式结构能够动态地管理内存,适合存储动态变化的数据。下面是链式结构二叉树的基本概念:

1. 定义

二叉树:每个节点最多有两个子节点(左子节点和右子节点)。

  • 链式结构:每个节点通过指针链接到其子节点。

2. 节点结构

一个二叉树的节点通常包含三个部分:

数据域:存储节点的数据(如整数、字符等)。

  • 左指针:指向左子节点的指针。
  • 右指针:指向右子节点的指针。

在C语言中,可以用以下结构体定义一个二叉树节点:

typedef struct TreeNode {
    int data;                  // 数据域
    struct TreeNode* left;    // 左子节点指针
    struct TreeNode* right;   // 右子节点指针
} TreeNode;

3. 操作

常见的操作包括:

插入:将新节点插入到树中,通常根据某种顺序(如二叉搜索树的性质)。

  • 删除:从树中删除一个节点,需考虑不同情况(如叶子节点、只有一个子节点的节点、两个子节点的节点)。
  • 遍历:遍历树的节点,包括前序遍历、中序遍历和后序遍历。

4. 优势与劣势

优势
动态内存分配,适合变化频繁的数据。

  • 节省空间:不需要为树的每一层预留空间。
  • 劣势
    访问速度慢于数组,因为需要遍历指针。
  • 存储结构复杂,维护指针可能引入错误。

这种结构在许多算法和数据处理问题中非常有用,比如二叉搜索树、平衡树(如 AVL 树和红黑树)等。

二、链式结构二叉树的实现

1. 树结构的定义

前面说到树的定义,首先这个链表的结点要储存自身的数据,接下来每一个节点都有两个指针。一个指向它的左孩子,另一个指向它的右孩子。

有了这两个指针,树才能连接起来。

在这里插入图片描述

//定义二叉树的链式结构
//二叉树结点的结构

typedef int BTDataType;
typedef struct BinaryTreeNode
{
    BTDataType data;
    struct BinaryTreeNode* left;         //左孩子
    struct BinaryTreeNode* right;        //右孩子
}BTNode;

2. 树的遍历

(1)前序遍历

//前序遍历
void PreOrder(BTNode* root);

什么是前序遍历呢? 我们来看这一张图

在这里插入图片描述

对于1这个结点,他是当前的根节点,他拥有左右两个孩子 2,3

在这里插入图片描述

对于这个2结点当根节点,他也有他的两个孩子----4,NULL.

对于前序遍历来说,就是按照 根----左----右的顺序进行遍历。 如下图所示:

在这里插入图片描述

对于每个根节点来说,它的遍历方法都是 根----左----右,我们有时候可能认为遍历到一个结点的左孩子或者右孩子就要回去,其实是不对的。因为它的孩子的遍历方式也是 根----左----右

因此,前序遍历是递归实现的。

//前序遍历 ------根左右
void PreOrder(BTNode* root)
{
    if (root == NULL)
    {
        return;
    }
    
    printf("%d ", root->data);
    PreOrder(root->left);
    PreOrder(root->right);
}

在这里插入图片描述

我们结合代码,再从函数栈帧的角度看一下递归具体的实现

在这里插入图片描述

(2)中序遍历

//中序遍历------左根右
void InOrder(BTNode* root);

不同于中序遍历,中序遍历的顺序是 左----根----右

在这里插入图片描述

对于每一个节点,先遍历它的左孩子,在遍历它自己,最后遍历它的右孩子

如下图所示:

在这里插入图片描述

故中序遍历的顺序是4–2–1–3.

//中序遍历------左根右
void InOrder(BTNode* root)
{
    if (root == NULL)
    {
        return;
    }

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

(3)后序遍历

//后序遍历------左右根
void PostOrder(BTNode* root);

区别于前两种遍历方式,后序遍历的顺序是左----右---根

在这里插入图片描述

对于每一个节点,先遍历它的左孩子,在遍历它的右孩子,最后遍历它自己。

如下图所示:

在这里插入图片描述


故后序遍历的顺序为 4–2–3–1.

//后序遍历------左右根
void PostOrder(BTNode* root)
{
    if (root == NULL)
    {
        return;
    }

    PostOrder(root->left);
    PostOrder(root->right);
    printf("%d ", root->data);
}

3. 二叉树结点个数

// ⼆叉树结点个数
// 一个结点的个数等于其左右子树结点个数之和加上它本身
int BinaryTreeSize(BTNode* root);

二叉树的结点个数等于什么呢? 我们很容易就能想到一种方法: 定义一个count来计数,我们呢可以使用中序遍历,每遍历到一个有效的结点,就让count++最后返回count。

这样的方法可不可行呢?

int BinaryTreesize(BTNode* root,int size)
{
  if(root == NULL)
  {
    return 0;
  }
  size++;
  BinaryTreesize(root->left,size);
  BinaryTreesize(root->right,size);
  return size;
}

这样看似解决了问题,但实际上是不行的。 归根结底的原因是形参的传递不能改变实参。

在这里插入图片描述


那我们就有疑问了,既然你形参的改变不影响实参,那我直接传地址啊? 这样可不可行呢?

在这里插入图片描述


看似传了地址就解决了这个问题,但我们如果连续打印两次树中结点个数的话,你会发现节点个数又多加了一次。 这是因为地址的内容被我们改变,下次再用size的初始值就不是0了,需要我们手动释放。因此这种方法也不好。

那我们该怎么做呢? 可以这样想, 一个结点的个数等于其左右子树结点个数之和加上它本身。

在这里插入图片描述


只要分别找到每一个结点左子树和右子树结点之和,就是我们的答案。 将大事化小,当root为NULL的时候结束,不正是递归的思想吗?

// ⼆叉树结点个数
// 一个结点的个数等于其左右子树结点个数之和加上它本身
int BinaryTreeSize(BTNode* root)
{
    if (root == NULL)
    {
        return 0;
    }

    return 1 + BinaryTreeSize(root->left) + BinaryTreeSize(root->right);
}

4. 二叉树叶子结点个数

// ⼆叉树叶⼦结点个数
// 二叉树叶子结点的个数等于二叉树左右子树叶子结点的个数之和
int BinaryTreeLeafSize(BTNode* root);

叶子结点就是树每一个分支最下层的结点,它没有子结点。

类比上述的方法,我们可以将二叉树叶子结点的个数看成二叉树左右子树叶子结点的个数之和。

在这里插入图片描述

// ⼆叉树叶⼦结点个数
// 二叉树叶子结点的个数等于二叉树左右子树叶子结点的个数之和
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);
}

5. 二叉树第k层结点个数

// ⼆叉树第k层结点个数
// ⼆叉树第k层结点个数等于左右子树第K层的结点个数和
// 注意是K == 1
int BinaryTreeLevelKSize(BTNode* root, int k);

第K层的结点个数可以看成左子树第K层和右子树第K层的结点数之和。

在这里插入图片描述

// ⼆叉树第k层结点个数等于左右子树第K层的结点个数和
// 注意是K == 1
int BinaryTreeLevelKSize(BTNode* root, int k)
{
    if (root == NULL)
    {
        return 0;
    }

    if (k == 1)
    {
        return 1;
    }

    return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}

6. 二叉树的深度/高度

// ⼆叉树的深度/⾼度
// 二叉树的深度要分别比较左右子树最深的那一个
// 每一个结点的深度都等于其左右子树中最深的那个
int BinaryTreeDepth(BTNode* root);

二叉树的深度就是二叉树所有完整分支最长的那一个结点的个数。

二叉树的深度就等于左子树和右子树中最长的那一个。每一个结点都可以看成由他的左子树和右子树的最长深度,最终反回到root == NULL;

在这里插入图片描述

// ⼆叉树的深度/⾼度
// 二叉树的深度要分别比较左右子树最深的那一个
// 每一个结点的深度都等于其左右子树中最深的那个
int BinaryTreeDepth(BTNode* root)
{
    if (root == NULL)
    {
        return 0;
    }

    int leftDepth = BinaryTreeDepth(root->left);
    int rightDepth = BinaryTreeDepth(root->right);

    return leftDepth > rightDepth ? leftDepth + 1 : rightDepth + 1;

7. 二叉树查找值为x的结点

// ⼆叉树查找值为x的结点
// 左根右的方式找
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);

我们使用中序遍历的方法递归树就好,直接找到等于x的位置,返回当前结点,没找到就返回NULL;

在这里插入图片描述

// ⼆叉树查找值为x的结点
// 左根右的方式找
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
    if (root == NULL)
    {
        return NULL;
    }

    if (root->data == x)
    {
        return root;
    }

    BTNode* leftFind = BinaryTreeFind(root->left, x);
    if (leftFind)
    {
        return leftFind;
    }

    BTNode* rightFind = BinaryTreeFind(root->right, x);
    if (rightFind)
    {
        return rightFind;
    }

    return NULL;
}

8. 二叉树的销毁

// ⼆叉树销毁
// 以左右根的方式销毁
// 根被销毁了因此要传二级指针
void BinaryTreeDestroy(BTNode** root);

首先,销毁要使用后序遍历的方法,因为如果根结点先被销毁,就一定找不到他的左右孩子了。

接下来一定要用二级指针,因为最终的树的根结点被销毁,我们传过来的root要改变,如果只用一级指针的话形参的改变是无法影响到实参的,因此必须要传一级指针的地址。

在这里插入图片描述

// ⼆叉树销毁
// 以左右根的方式销毁
// 根被销毁了因此要传二级指针
void BinaryTreeDestroy(BTNode** root)
{
    if (*root == NULL)
    {
        return;
    }

    BinaryTreeDestroy(&((*root)->left));
    BinaryTreeDestroy(&((*root)->right));
    free(*root);
    *root = NULL;
}

三、层序遍历

1. 层序遍历的概念

除了先序遍历、中序遍历、后序遍历外,还可以对⼆叉树进⾏层序遍历。设⼆叉树的根结点所在层数为1,层序遍历就是从所在⼆叉树的根结点出发,⾸先访问第⼀层的树根结点,然后从左到右访问第2层上的结点,接着是第三层的结点,以此类推,⾃上⽽下,⾃左⾄右逐层访问树的结点的过程就是层序遍历。

实现层序遍历需要额外借助数据结构:队列

2. 层序遍历的实现

实现层序遍历首先我们要有一个队列。

如下图:

在这里插入图片描述

第一步: 将树根结点插入队列。

在这里插入图片描述


第二步: 取出队头元素,如果它的左右子树不为空就将其按顺序入队。

在这里插入图片描述


第三步: 继续出队头元素,将出队列元素的左右孩子如果不为空让其入队。

在这里插入图片描述


第四步: 继续重复上一步骤,出队头元素,将出队列元素的左右孩子如果不为空让其入队。

此时4这个结点没有左右孩子,因此不用入队

在这里插入图片描述


第五步:重复上述步骤直到队列为空。

这里我们就完成了层序遍历,遍历顺序为 1----2----3----4

在这里插入图片描述

具体的实现为:

// 层序遍历
// 层序遍历需要借助队列,根出队,左右子树放入其中,直到一个左右子树都为空
void LevelOrder(BTNode* root)
{
    Queue q;
    QueueInit(&q);
    //先插入根结点
    QueuePush(&q, root);

    while (!QueueEmpty(&q))
    {
        BTNode* front = QueueFront(&q);
        printf("%d ", front->data);
        QueuePop(&q);

        if (front->left)
        {
            QueuePush(&q, front->left);
        }
        if (front->right)
        {
            QueuePush(&q, front->right);
        }
    }

    QueueDestroy(&q);
}

四、判断二叉树是否为完全二叉树

如何判断二叉树是否是完全二叉树呢?

这里J桑直接给出方法: 同样需要借助队列,不过这次就算是结点的左右孩子无论它是否为NULL,都将它插入队列,循环出队列头,直到跳出第一个循环时,如果队列中所有的元素都是NULL,那么树就是完全二叉树,如果有不为NULL的元素,那他就不是完全二叉树。

第一步: 将树根结点插入队列。

在这里插入图片描述


第二步: 队头元素出队,取其左右孩子入队

在这里插入图片描述


第三步: 重复上述步骤

在这里插入图片描述

在这里插入图片描述


此时循环结束,队列中有非空元素,因此该树不是完全二叉树

具体实现为:

//判断二叉树是否为完全二叉树
//---借助队列,不管他的孩子是不是空都放到队列中,满二叉树结束时一定队列里全为空
bool BinaryTreeComplete(BTNode* root)
{
    Queue q;
    QueueInit(&q);
    QueuePush(&q, root);
    while (!QueueEmpty(&q))
    {
        BTNode* front = QueueFront(&q);
        QueuePop(&q);
        if (front == NULL)
        {
            break;
        }
        QueuePush(&q, front->left);
        QueuePush(&q, front->right);
    }
    //队列不一定为空
    while (!QueueEmpty(&q))
    {
        BTNode* front = QueueFront(&q);
        QueuePop(&q);
        if (front != NULL)
        {
            QueueDestroy(&q);
            return false;
        }
    }
    QueueDestroy(&q);
    return true;
}

总结

到这里我们链式结构的二叉树(BinaryTree)就实现完成啦~

下面是完成代码:(记得写Queue哦~)

//Heap.c
#include"Tree.h"
#include"Queue.h"

//前序遍历 ------根左右
void PreOrder(BTNode* root)
{
    if (root == NULL)
    {
        return;
    }
    
    printf("%d ", root->data);
    PreOrder(root->left);
    PreOrder(root->right);
}

//中序遍历------左根右
void InOrder(BTNode* root)
{
    if (root == NULL)
    {
        return;
    }

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

//后序遍历------左右根
void PostOrder(BTNode* root)
{
    if (root == NULL)
    {
        return;
    }

    PostOrder(root->left);
    PostOrder(root->right);
    printf("%d ", root->data);
}

// ⼆叉树结点个数
// 一个结点的个数等于其左右子树结点个数之和加上它本身
int BinaryTreeSize(BTNode* root)
{
    if (root == NULL)
    {
        return 0;
    }

    return 1 + BinaryTreeSize(root->left) + BinaryTreeSize(root->right);
}

// ⼆叉树叶⼦结点个数
// 二叉树叶子结点的个数等于二叉树左右子树叶子结点的个数之和
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);
}

// ⼆叉树第k层结点个数
// ⼆叉树第k层结点个数等于左右子树第K层的结点个数和
// 注意是K == 1
int BinaryTreeLevelKSize(BTNode* root, int k)
{
    if (root == NULL)
    {
        return 0;
    }

    if (k == 1)
    {
        return 1;
    }

    return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}

// ⼆叉树的深度/⾼度
// 二叉树的深度要分别比较左右子树最深的那一个
// 每一个结点的深度都等于其左右子树中最深的那个
int BinaryTreeDepth(BTNode* root)
{
    if (root == NULL)
    {
        return 0;
    }

    int leftDepth = BinaryTreeDepth(root->left);
    int rightDepth = BinaryTreeDepth(root->right);

    return leftDepth > rightDepth ? leftDepth + 1 : rightDepth + 1;
}

// ⼆叉树查找值为x的结点
// 左根右的方式找
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
    if (root == NULL)
    {
        return NULL;
    }

    if (root->data == x)
    {
        return root;
    }

    BTNode* leftFind = BinaryTreeFind(root->left, x);
    if (leftFind)
    {
        return leftFind;
    }

    BTNode* rightFind = BinaryTreeFind(root->right, x);
    if (rightFind)
    {
        return rightFind;
    }

    return NULL;
}

// ⼆叉树销毁
// 以左右根的方式销毁
// 根被销毁了因此要传二级指针
void BinaryTreeDestroy(BTNode** root)
{
    if (*root == NULL)
    {
        return;
    }

    BinaryTreeDestroy(&((*root)->left));
    BinaryTreeDestroy(&((*root)->right));
    free(*root);
    *root = NULL;
}

// 层序遍历
// 层序遍历需要借助队列,根出队,左右子树放入其中,直到一个左右子树都为空
void LevelOrder(BTNode* root)
{
    Queue q;
    QueueInit(&q);
    //先插入根结点
    QueuePush(&q, root);

    while (!QueueEmpty(&q))
    {
        BTNode* front = QueueFront(&q);
        printf("%d ", front->data);
        QueuePop(&q);

        if (front->left)
        {
            QueuePush(&q, front->left);
        }
        if (front->right)
        {
            QueuePush(&q, front->right);
        }
    }

    QueueDestroy(&q);
}

//判断二叉树是否为完全二叉树
//---借助队列,不管他的孩子是不是空都放到队列中,满二叉树结束时一定队列里全为空
bool BinaryTreeComplete(BTNode* root)
{
    Queue q;
    QueueInit(&q);
    QueuePush(&q, root);
    while (!QueueEmpty(&q))
    {
        BTNode* front = QueueFront(&q);
        QueuePop(&q);
        if (front == NULL)
        {
            break;
        }
        QueuePush(&q, front->left);
        QueuePush(&q, front->right);
    }
    //队列不一定为空
    while (!QueueEmpty(&q))
    {
        BTNode* front = QueueFront(&q);
        QueuePop(&q);
        if (front != NULL)
        {
            QueueDestroy(&q);
            return false;
        }
    }
    QueueDestroy(&q);
    return true;
}
//Heap.h
#pragma once

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>


//定义二叉树的链式结构
//二叉树结点的结构

typedef int BTDataType;
typedef struct BinaryTreeNode
{
    BTDataType data;
    struct BinaryTreeNode* left;         //左孩子
    struct BinaryTreeNode* right;        //右孩子
}BTNode;


//前序遍历
void PreOrder(BTNode* root);

//中序遍历
void InOrder(BTNode* root);

//后序遍历 
void PostOrder(BTNode* root);

// ⼆叉树结点个数
int BinaryTreeSize(BTNode* root);

// ⼆叉树叶⼦结点个数
int BinaryTreeLeafSize(BTNode* root);

// ⼆叉树第k层结点个数
int BinaryTreeLevelKSize(BTNode* root, int k);

//⼆叉树的深度/⾼度
int BinaryTreeDepth(BTNode* root);

// ⼆叉树查找值为x的结点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);

// ⼆叉树销毁
void BinaryTreeDestroy(BTNode** root);

//层序遍历
void LevelOrder(BTNode* root);

//判断二叉树是否为完全二叉树
bool BinaryTreeComplete(BTNode* root);
//测试样例
#include"Tree.h"

BTNode* buyNode(BTDataType x)
{
    BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
    if (newnode == NULL)
    {
        perror("malloc fail!");
        exit(1);
    }
    newnode->data = x;
    newnode->left = newnode->right = NULL;

    return newnode;
}

void test01()
{
    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 = node3;
    node2->right = node4;
    //node2->right = node5;
    //node3->left = node6;


    //PreOrder(node1);
    //printf("\n");
    //InOrder(node1);
    //printf("\n");
    //PostOrder(node1);

    //int size = 0;
    //BinaryTreeSize(node1, &size);
    //printf("size : %d\n", size);
    ////size = 0;
    //BinaryTreeSize(node1, &size);
    //printf("size : %d\n", size);

    //printf("size:%d\n", BinaryTreeSize(node1));
    //printf("size:%d\n", BinaryTreeSize(node1));

    //printf("size:%d\n", BinaryTreeSize(node1));
    //printf("leaf size: %d\n", BinaryTreeLeafSize(node1));
    //printf("第K层size : %d\n", BinaryTreeLevelKSize(node1, 2));
    //printf("depth/height:%d\n", BinaryTreeDepth(node1));

    //BTNode* find =BinaryTreeFind(node1, 33);
    //printf("%s\n", find == NULL ? "未找到!" : "找到了!");

    //LevelOrder(node1);

    bool ret = BinaryTreeComplete(node1);
    printf("%s\n", ret == false ? "不是完全二叉树" : "是完全二叉树");

    BinaryTreeDestroy(&node1);
}

int main()
{
    test01();
    return 0;
}

谢谢大家~

真相永远只有一个!


文章来源: https://jiyufan-allforlove.blog.csdn.net/article/details/142471486
版权声明: 本文为博主原创文章,遵循CC 4.0 BY-SA 知识共享协议,转载请附上原文出处链接和本声明。


...全文
113 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

6,675

社区成员

发帖
与我相关
我的任务
社区描述
微软技术社区为中国的开发者们提供一个技术干货传播平台,传递微软全球的技术和产品最新动态,分享各大技术方向的学习资源,同时也涵盖针对不同行业和场景的实践案例,希望可以全方位地帮助你获取更多知识和技能。
windowsmicrosoft 企业社区
社区管理员
  • 微软技术分享
  • 郑子铭
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

微软技术社区为中国的开发者们提供一个技术干货传播平台,传递微软全球的技术和产品最新动态,分享各大技术方向的学习资源,同时也涵盖针对不同行业和场景的实践案例,希望可以全方位地帮助你获取更多知识和技能。

予力众生,成就不凡!微软致力于用技术改变世界,助力企业实现数字化转型。

试试用AI创作助手写篇文章吧