首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【数据结构】链式结构二叉树详解

【数据结构】链式结构二叉树详解

作者头像
小年糕是糕手
发布2026-01-14 17:20:24
发布2026-01-14 17:20:24
290
举报
文章被收录于专栏:C++学习C++学习
一、概念与结构

用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址, 其结构如下:

代码语言:javascript
复制
typedef int BTDataType;
//二叉链
typedef struct BinaryTreeNode {
	BTDataType data;//当前结点值域
	struct BinaryTreeNode* left;//指向当前结点左孩子
	struct BinaryTreeNode* right;//指向当前结点右孩子
}BTNode;

二叉树的创建方式比较复杂,为了更好的步入到二叉树内容中,我们先手动创建一棵链式二叉树:

代码语言:javascript
复制
int main()
{
	test01();
	return 0;
}

BTNode* BuyBTNode(int val)
{
	BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	newnode->val = val;
	newnode->left = NULL;
	newnode->right = NULL;
	return newnode;
}
BTNode* CreateTree()
{
	BTNode* n1 = BuyBTNode(1);
	BTNode* n2 = BuyBTNode(2);
	BTNode* n3 = BuyBTNode(3);
	BTNode* n4 = BuyBTNode(4);
	BTNode* n5 = BuyBTNode(5);
	BTNode* n6 = BuyBTNode(6);
	BTNode* n7 = BuyBTNode(7);
	n1->left = n2;
	n1->right = n4;
	n2->left = n3;
	n4->left = n5;
	n4->right = n6;
	n5->left = n7;
	return n1;
}

回顾二叉树的概念,二叉树分为空树和非空二叉树,非空二叉树由根结点、根结点的左子树、根结点的右子树组成的:

根结点的左子树和右子树分别又是由子树结点、子树结点的左子树、子树结点的右子树组成的,因此二叉树定义是递归式的,后序链式二叉树的操作中基本都是按照该概念实现的。

二、前中后序遍历

下面我将用三种遍历规则来为大家实现下图中的二叉树:

2.1、前序遍历
2.1.1、遍历规则

前序遍历(Preorder Traversal 亦称先序遍历):访问根结点的操作发生在遍历其左右子树之前,访问顺序为:根节点,左子树,右子树。

简而言之三个字:根左右(根在前,亦称前序)

2.1.2、代码实现
代码语言:javascript
复制
typedef char BTDataType;
//二叉链
typedef struct BinaryTreeNode {
	BTDataType data;//当前结点值域
	struct BinaryTreeNode* left;//指向当前结点左孩子
	struct BinaryTreeNode* right;//指向当前结点右孩子
}BTNode;

//前序遍历 -- 根左右
void PreOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	printf("%c ", root->data);
	PreOrder(root->left);
	PreOrder(root->right);
}
2.1.3、图解
2.2、中序遍历
2.2.1、遍历规则

中序遍历(Inorder Traversal):访问根结点的操作发生在遍历其左右子树之中(间),访问顺序为:左子树,根节点,右子树。

简而言之三个字:左根右(根在中间,亦称中序)

2.2.2、代码实现
代码语言:javascript
复制
typedef char BTDataType;
//二叉链
typedef struct BinaryTreeNode {
	BTDataType data;//当前结点值域
	struct BinaryTreeNode* left;//指向当前结点左孩子
	struct BinaryTreeNode* right;//指向当前结点右孩子
}BTNode;

//中序遍历 -- 左根右
void InOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	InOrder(root->left);
	printf("%c ", root->data);
	InOrder(root->right);
}
2.2.3、图解
2.3、后序遍历
2.3.1、遍历规则

后序遍历(Postorder Traversal):访问根结点的操作发生在遍历其左右子树之后,访问顺序:左子树,右子树,根结点。

简而言之三个字:左右根(根在最后,亦称后续)

2.3.2、代码实现
代码语言:javascript
复制
typedef char BTDataType;
//二叉链
typedef struct BinaryTreeNode {
	BTDataType data;//当前结点值域
	struct BinaryTreeNode* left;//指向当前结点左孩子
	struct BinaryTreeNode* right;//指向当前结点右孩子
}BTNode;

//后序遍历 -- 左右根
void PostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	PostOrder(root->left);
	PostOrder(root->right);
	printf("%c ", root->data);
}
2.3.3、图解
函数栈帧图(以前序为例子):
三、二叉树中的一系列操作
3.1、Tree.h
代码语言:javascript
复制
#pragma once
#define _CRT_SECURE_NO_WARNINGS

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


typedef char 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 BinaryTreeDestory(BTNode** root);
3.2、Tree.c
代码语言:javascript
复制
#include"Tree.h"

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

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

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

// ⼆叉树结点个数 -- 有效结点个数
// 结点总数 = 1 + 左子树结点个数 + 右子树结点个数
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层结点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{
	if (root == NULL)
	{
		return 0;
	}
	//判断是否为第k层
	if (k == 1)
	{
		return 1;
	}
	return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}

// ⼆叉树的深度/⾼度
// 根结点 + MAX(左子树高度,右子树高度)
int BinaryTreeDepth(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	int leftDep = BinaryTreeDepth(root->left);
	int rightDep = BinaryTreeDepth(root->right);
	//根节点 + MAX(左子树高度,右子树高度)
	return 1 + (leftDep > rightDep ? leftDep : rightDep);
}

// ⼆叉树查找值为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 != 0)
	{
		return leftFind;
	}
	BTNode* rightFind = BinaryTreeFind(root->right, x);
	if (rightFind != 0)
	{
		return rightFind;
	}
	return NULL;
}

// ⼆叉树销毁
void BinaryTreeDestory(BTNode** root)
{
	if (*root == NULL)
	{
		return;
	}
	BinaryTreeDestory(&((*root)->left));
	BinaryTreeDestory(&((*root)->right));
	free(*root);
	*root = NULL;
}
3.3、test.c
代码语言:javascript
复制
#include"Tree.h"

BTNode* BuyNode(char x)
{
	BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));

	newnode->data = x;
	newnode->left = newnode->right = NULL;

	return newnode;
}

BTNode* createTree()
{
	BTNode* nodeA = BuyNode('A');
	BTNode* nodeB = BuyNode('B');
	BTNode* nodeC = BuyNode('C');
	BTNode* nodeD = BuyNode('D');
	BTNode* nodeE = BuyNode('E');
	BTNode* nodeF = BuyNode('F');

	nodeA->left = nodeB;
	nodeA->right = nodeC;
	nodeB->left = nodeD;
	nodeC->left = nodeE;
	nodeC->right = nodeF;

	return nodeA;
}

void test01()
{
	BTNode* nodeA = BuyNode('A');
	BTNode* nodeB = BuyNode('B');
	BTNode* nodeC = BuyNode('C');
	BTNode* nodeD = BuyNode('D');
	BTNode* nodeE = BuyNode('E');
	BTNode* nodeF = BuyNode('F');

	nodeA->left = nodeB;
	nodeA->right = nodeC;
	nodeB->left = nodeD;
	nodeC->left = nodeE;
	nodeC->right = nodeF;

	PreOrder(nodeA);
	printf("\n");
	InOrder(nodeA);
	printf("\n");
	PostOrder(nodeA);
	printf("\n");
}

void test02()
{
	BTNode* root = createTree();

	// ⼆叉树结点个数 -- 有效结点个数
	size_t ret1 = BinaryTreeSize(root);
	printf("size:%zu\n", ret1);

	// ⼆叉树叶⼦结点个数
	size_t ret2 = BinaryTreeLeafSize(root);
	printf("leaf size:%zu\n", ret2);

	// ⼆叉树第k层结点个数 
	size_t ret3 = BinaryTreeLevelKSize(root, 3);
	printf("K Level Size:%zu\n", ret3);

	// ⼆叉树的深度/⾼度
	size_t ret4 = BinaryTreeDepth(root);
	printf("Tree Depth:%zu\n", ret4);

	// ⼆叉树查找值为x的结点 
	BTNode* find = BinaryTreeFind(root, 'E');
	if (find)
	{
		printf("找到了!\n");
	}
	else
	{
		printf("未找到!\n");
	}

	// ⼆叉树销毁
	BinaryTreeDestory(&root);
}

int main()
{
	//test01();
	test02();
	return 0;
}
四、层序遍历
4.1、遍历规则

除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根结点所在层数为1,层序遍历就是从所在二叉树的根结点出发,首先访问第一层的树根结点,然后从左到右访问第2层上的结点,接着是第三层的结点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。

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

我们创建队列用来存放二叉树的结点,二叉树的left指针和right指针还是指向原来的位置。

4.2、代码实现
代码语言:javascript
复制
//层序遍历
void LevelOrder(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	QueuePush(&q, root);
	while (!(QueueEmpty(&q)))
	{
		//取队头,打印队头
		BTNode* top = QueueFront(&q);
		printf("%c ", top->data);
		QueuePop(&q);
		//队头结点不为空的孩子结点入队列
		if (top->left)
			QueuePush(&q, top->left);
		if (top->right)
			QueuePush(&q, top->right);
	}

	QueueDestroy(&q);
}

这里使用到了一些队列的相关知识,为大家简单演示一些细节:

五、判断是否为完全二叉树
5.1、思路

1、判断每层结点个数;2、叶子结点是否从左到右依次排序

根节点先入队列,保证队列不为空,循环判断队列是否为空,不为空取队头,出队头,将队头结点的左右孩子都入队列

5.2、代码实现
代码语言:javascript
复制
// 判断⼆叉树是否是完全⼆叉树
bool BinaryTreeComplete(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	QueuePush(&q, root);
	while (!QueueEmpty(&q))
	{
		//取队头,出队头
		BTNode* top = QueueFront(&q);
		QueuePop(&q);
		if (top == NULL)
		{
			//top取到空就直接出队列
			break;
		}
		//将队头结点的左右孩子入队列
		QueuePush(&q, top->left);
		QueuePush(&q, top->right);
	}
	//队列不为空,继续取队列中的队头
	while (!QueueEmpty(&q))
	{
		BTNode* top = QueueFront(&q);
	    QueuePop(&q);
		if (top != NULL)
		{
			//不是完全二叉树
			QueueDestroy(&q);
			return false;
		}
	}
	QueueDestroy(&q);
	return true;
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-10-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、概念与结构
  • 二、前中后序遍历
    • 2.1、前序遍历
      • 2.1.1、遍历规则
      • 2.1.2、代码实现
      • 2.1.3、图解
    • 2.2、中序遍历
      • 2.2.1、遍历规则
      • 2.2.2、代码实现
      • 2.2.3、图解
    • 2.3、后序遍历
      • 2.3.1、遍历规则
      • 2.3.2、代码实现
      • 2.3.3、图解
  • 函数栈帧图(以前序为例子):
  • 三、二叉树中的一系列操作
    • 3.1、Tree.h
    • 3.2、Tree.c
    • 3.3、test.c
  • 四、层序遍历
    • 4.1、遍历规则
    • 4.2、代码实现
  • 五、判断是否为完全二叉树
    • 5.1、思路
    • 5.2、代码实现
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档