【C语言】二叉树链式结构的实现,详解

0.前言

二叉树的基本操作的实现基本离不开一个思想------分治算法。

分治算法的基本思想是将一个规模为n的问题分解为k个规模较小的子问题,这些子问题相互独立且与原问题相同。递归地解这些子问题,然后将各子问题的解合并得到原问题的解。这样,通过逐步缩小问题的规模,可以显著降低解决问题的复杂度。

1.一颗二叉树的创建

在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。先此处手动快速创建一棵简单的二树,快速进入二叉树操作学习,后面会详细说明二叉树真正的创建方式

一颗二叉树分为根、左子树、右子树,该二叉树根1的左边链接根2,根2的左边链接根3,根1的右边链接根4,根4的左边链接根5,根4的右边链接根6。

cpp 复制代码
typedef int BTDataType;
typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;
BTNode* BuyNode(BTDataType x)
{
	BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	newnode->left = newnode->right = NULL;
	newnode->data = 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;
}

2.二叉树的三种遍历方式

掌握二叉树的三种遍历方式非常重要,详细思路可看文章:二叉树的三种遍历,这里只展示代码。

2.1前序遍历

cpp 复制代码
void BinaryTreePrevOrder(BTNode* root)
{
	if (root == NULL)
	{

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

2.2中序遍历

cpp 复制代码
void BinaryTreeInOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	BinaryTreeInOrder(root->left);
	printf("%d ", root->data);
	BinaryTreeInOrder(root->right);
}

2.3后序遍历

cpp 复制代码
void BinaryTreePostOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	BinaryTreePostOrder(root->left);
	BinaryTreePostOrder(root->right);
	printf("%d ", root->data);
}

3.二叉树的销毁

基本思路:和后序遍历类似,先销毁根节点的左节点,再销毁根节点的右节点,再销毁根节点。

如果按前序遍历的方式销毁,先释放掉根节点,那就找不到他的左节点和右节点。当然也可以先把要是否掉的根节点存起来,但是相较于后序遍历销毁麻烦。

3.1传一级指针

此处有个缺陷,在函数里面将根节点释放掉后置为空,并不能将实参变为空。也就是形参的改变影响不了实参。我们还需在函数外面将实参置为空。

cpp 复制代码
void BinaryTreePDestory(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	BinaryTreePDestory(root->left);
	BinaryTreePDestory(root->right);
	free(root);
}

3.2传二级指针

此处形参的改变将会影响实参,不需要额外再将实参置空

cpp 复制代码
void BinaryTreePPDestory(BTNode** root)
{
	if (*root==NULL)
	{
		return;
	}
	BinaryTreePPDestory(&((*root)->left));
	BinaryTreePPDestory(&((*root)->right));
	free(*root);
	*root = NULL;
}

4.二叉树节点个数

求二叉树节点个数,通常可以通过递归的方式来实现。递归的基本思想是:对于给定的二叉树,其节点总数等于左子树的节点数加上右子树的节点数,再加上根节点本身(1)。根节点为空是返回0.

cpp 复制代码
int BinaryTreeSize(BTNode* root)
{
	if (root == NULL)
		return 0;
	return BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}

5.二叉树叶子节点个数

叶子节点是没有左节点并且没有右节点的节点。

思路:如果当前节点为空返回0,当前节点存在且无左节点和右节点则是叶子节点返回1。如果当前节点不是叶子节点(即它有左子节点或右子节点),则递归地计算其左子树的叶子节点个数和右子树的叶子节点个数。将左子树的叶子节点个数和右子树的叶子节点个数相加,得到的结果即为以当前节点为根的子树的叶子节点总数。

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);
}

6.二叉树第k层节点个数

思路:知道k-1层节点的左右节点一共有多少个就是二叉树第k层节点个数。当前节点为空返回0,当k等于1时说明到达第k层返回1。不等于空,且k > 1说明第k层的节点在子树里面,转换成子问题求解

cpp 复制代码
int BinaryTreeLevelKSize(BTNode* root, int k)
{
    assert(k > 0);
	if (root == NULL)
	{
		return 0;
	}
	if (k == 1)
	{
		return 1;
	}
	return BinaryTreeLevelKSize(root->left, k - 1) 
         + BinaryTreeLevelKSize(root->right, k - 1);
}

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

7.1只是判断该值是否在二叉中

当前节点为空返回false,当前节点的值等于x返回true,没有找到则递归到左子树里面找,没找到再去右子树找

cpp 复制代码
bool TreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
		return false;
	if (root->data == x)
		return true;
	return TreeFind(root->left, x) || TreeFind(root->right, x);
}

7.2返回第一个是该值的节点

当前节点为空返回,当前节点的值等于x返回该节点,没有找到则递归到左子树里面找,没找到再去右子树找(这里需要注意的是要先把节点存起来,如果没有存起来则返回的时候不会保留)

cpp 复制代码
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
		return;
	if (root->data == x)
		return root;
	BTNode* _left = BinaryTreeFind(root->left, x);
	if (_left)
		return _left;
	BTNode* _right = BinaryTreeFind(root->right, x);
	if (_right)
		return _right;
}

8.层序遍历

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

思路:需要借助队列来实现(C语言需要自己实现队列)

首先,检查根节点是否为空,如果不为空,则将其加入队列。然后,进入一个循环,只要队列不为空,就执行以下操作:

从队列中取出一个节点(队首元素),并访问它(打印它的值),再将队列中队头的元素(也就是该节点Pop掉)

如果该节点有左子节点,则将左子节点加入队列。

如果该节点有右子节点,则将右子节点加入队列。

这个过程会按照从上到下、从左到右的顺序遍历二叉树的所有节点。

cpp 复制代码
void BinaryTreeLevelOrder(BTNode* root)
{
	Queue pq;
	QueueInit(&pq);
	if (root)
	{
		QueuePush(&pq, root);
	}
	while (!QueueEmpty(&pq))
	{
		BTNode* front = QueueFront(&pq);
		printf("%d ", front->data);
		QueuePop(&pq);
		if (front->left)
		{
			QueuePush(&pq, front->left);
		}
		if (front->right)
		{
			QueuePush(&pq, front->right);
		}
	}
	printf("\n");
	QueueDestroy(&pq);
}

9.判断二叉树是否是完全二叉树

完全二叉树:非空 空。

非完全二叉树:非空 空 非空 空

算法的思路通过层次遍历(广度优先搜索)来判断二叉树是否是完全二叉树。

首先,我们初始化一个队列,并将根节点(如果存在)加入队列中。然后,我们进入一个循环,不断地从队列中取出节点,并尝试将其左右子节点加入队列。如果在某个时刻,我们遇到了一个空节点(即该节点不存在)跳出第一个循环另外判断,不再向队列中添加任何子节点。这是因为,在完全二叉树的定义中,一旦开始遇到空节点,其后的所有节点都应该是空的。

在遍历完所有可能存在的节点后(即所有非空节点的子节点都已经被考虑过),我们再次检查队列。如果此时队列为空,说明我们之前的假设成立,即该二叉树是完全二叉树。如果队列不为空,且队列中的节点不为空,那么说明在之前遇到空节点之后,还有非空节点存在,这与完全二叉树的定义相违背,因此该二叉树不是完全二叉树。

简而言之,这个算法通过层次遍历来检查二叉树是否满足完全二叉树的性质:除了最后一层外,每一层都被完全填满,并且所有节点都尽可能地向左对齐。

cpp 复制代码
bool BinaryTreeComplete(BTNode* root)
{
	Queue pq;
	QueueInit(&pq);
	if (root)
	{
		QueuePush(&pq, root);
	}
	while (!QueueEmpty(&pq))
	{
		BTNode* front = QueueFront(&pq);
		QueuePop(&pq);
		//遇到空以后跳出后续判断
		if (front == NULL)
		{
			break;
		}
		QueuePush(&pq, front->left);
		QueuePush(&pq, front->right);
	}
	//是空出队列,遇见非空则说明不是完全二叉树
	while (!QueueEmpty(&pq))
	{
		BTNode* front = QueueFront(&pq);
		QueuePop(&pq);
		if (front)
		{
			QueueDestroy(&pq);
			return false;
		}
	}
	QueueDestroy(&pq);
	return true;
}

10.二叉树的创建

我们可以借助这道题目来理解:二叉树的遍历

二叉树的形状

按前序遍历的方式创建一颗二叉树,三个参数数组,数组长度,数组下标(初始值设为0)

当*pi>=数组越界或者数组元素等于#时数组下标+1且返回空,

把数组元素赋给节点数据,

递归,让数组的数据按照前序遍历的方式依次赋给节点。

cpp 复制代码
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi)
{
	if (*pi >= n || a[*pi] == '#')
	{
		(*pi)++;
		return NULL;
	}
	BTNode* root = (BTNode*)malloc(sizeof(BTNode));
	root->data = a[*pi];
	(*pi)++;

	root->left = BinaryTreeCreate(a, n, pi);
	root->right = BinaryTreeCreate(a, n, pi);
	return root;
}

11.完整代码(包括队列的实现)

11.1BinaryTree.h

cpp 复制代码
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

typedef int BTDataType;
typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;
#include"Queue.h"

BTNode* BuyNode(BTDataType x);
//二叉树的创建
BTNode* CreatBinaryTree();
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi);
// 二叉树销毁
void BinaryTreePDestory(BTNode* root);
void BinaryTreePPDestory(BTNode** root);
// 二叉树节点个数
int BinaryTreeSize(BTNode* root);
// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root);
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k);
// 二叉树查找值为x的节点
bool TreeFind(BTNode* root, BTDataType x);
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);
// 二叉树前序遍历 
void BinaryTreePrevOrder(BTNode* root);
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root);
// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root);
// 层序遍历
void BinaryTreeLevelOrder(BTNode* root);
// 判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root);

11.2Queue.h

cpp 复制代码
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdbool.h>
#include<stdlib.h>
#include"BinaryTree.h"
typedef struct BinaryTreeNode* QDataType;
typedef struct QueueNode
{
	QDataType val;
	struct QueueNode* next;
}QNode;
typedef struct Queue
{
	QNode* phead;
	QNode* ptail;
	int size;
}Queue;

//初始化
void QueueInit(Queue* pq);
//销毁
void QueueDestroy(Queue* pq);
//判空
bool QueueEmpty(Queue* pq);
//入队列
void QueuePush(Queue* pq, QDataType x);
//出队列
void QueuePop(Queue* pq);
//取队头
QDataType QueueFront(Queue* pq);
//取队尾
QDataType QueueBack(Queue* pq);
//队列长度
size_t QueueLength(Queue* pq);

11.3Queue.c

cpp 复制代码
#include"Queue.h"
//初始化
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}
//销毁
void QueueDestroy(Queue* pq)
{
	assert(pq);
	QNode* cur = pq->phead;
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}
//入队列
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);

	//在队尾入,尾插
	//创建新节点
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	newnode->val = x;
	newnode->next = NULL;

	//空链表
	if (QueueEmpty(pq))
	{
		pq->phead = pq->ptail = newnode;
	}
	else 
	{
		pq->ptail->next = newnode;
		pq->ptail = newnode;
	}
	pq->size++;
}
//出队列
void QueuePop(Queue* pq)
{
	assert(pq);
	//零个节点
	assert(pq->phead);
	//一个节点
	if (pq->phead->next == NULL)
	{
		free(pq->phead);
		pq->phead = pq->ptail = NULL;
	}
	else
	{
		QNode* next = pq->phead->next;
		free(pq->phead);
		pq->phead = next;
	}
	pq->size--;
}
//判空
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->size == 0;
}
//取队头
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(pq->phead);
	return pq->phead->val;
}
//取队尾
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(pq->ptail);
	return pq->ptail->val;
}
//队列长度
size_t QueueLength(Queue* pq)
{
	assert(pq);
	return pq->size;
}

11.4BinaryTree.c

cpp 复制代码
#include"BinaryTree.h"

BTNode* BuyNode(BTDataType x)
{
	BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	newnode->left = newnode->right = NULL;
	newnode->data = 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;
}
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi)
{
	if (*pi >= n || a[*pi] == '#')
	{
		(*pi)++;
		return NULL;
	}
	BTNode* root = (BTNode*)malloc(sizeof(BTNode));
	root->data = a[*pi];
	(*pi)++;

	root->left = BinaryTreeCreate(a, n, pi);
	root->right = BinaryTreeCreate(a, n, pi);
	return root;
}
// 二叉树销毁
//传一级指针
void BinaryTreePDestory(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	BinaryTreePDestory(root->left);
	BinaryTreePDestory(root->right);
	free(root);
}
//传二级指针
void BinaryTreePPDestory(BTNode** root)
{
	if (*root==NULL)
	{
		return;
	}
	BinaryTreePPDestory(&((*root)->left));
	BinaryTreePPDestory(&((*root)->right));
	free(*root);
	*root = NULL;
}
// 二叉树节点个数
int BinaryTreeSize(BTNode* root)
{
	if (root == NULL)
		return 0;
	return BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}
// 二叉树叶子节点个数
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层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{
	assert(k > 0);
	if (root == NULL)
	{
		return 0;
	}
	if (k == 1)
	{
		return 1;
	}
	return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}
// 二叉树查找值为x的节点
bool TreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
		return false;
	if (root->data == x)
		return true;
	return TreeFind(root->left, x) || TreeFind(root->right, x);
}
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
		return;
	if (root->data == x)
		return root;
	BTNode* _left = BinaryTreeFind(root->left, x);
	if (_left)
	{
		return _left;
	}
	BTNode* _right = BinaryTreeFind(root->right, x);
	if (_right)
	{
		return _right;
	}
		
}
// 二叉树前序遍历
void BinaryTreePrevOrder(BTNode* root)
{
	if (root == NULL)
	{

		return;
	}
	printf("%d ", root->data);
	BinaryTreePrevOrder(root->left);
	BinaryTreePrevOrder(root->right);
}
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	BinaryTreeInOrder(root->left);
	printf("%d ", root->data);
	BinaryTreeInOrder(root->right);
}
// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	BinaryTreePostOrder(root->left);
	BinaryTreePostOrder(root->right);
	printf("%d ", root->data);
}
// 层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{
	Queue pq;
	QueueInit(&pq);
	if (root)
	{
		QueuePush(&pq, root);
	}
	while (!QueueEmpty(&pq))
	{
		BTNode* front = QueueFront(&pq);
		printf("%d ", front->data);
		QueuePop(&pq);
		if (front->left)
		{
			QueuePush(&pq, front->left);
		}
		if (front->right)
		{
			QueuePush(&pq, front->right);
		}
	}
	printf("\n");
	QueueDestroy(&pq);
}
// 判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root)
{
	Queue pq;
	QueueInit(&pq);
	if (root)
	{
		QueuePush(&pq, root);
	}
	while (!QueueEmpty(&pq))
	{
		BTNode* front = QueueFront(&pq);
		QueuePop(&pq);
		//遇到空以后跳出后续判断
		if (front == NULL)
		{
			break;
		}
		QueuePush(&pq, front->left);
		QueuePush(&pq, front->right);
	}
	//是空出队列,遇见非空则说明不是完全二叉树
	while (!QueueEmpty(&pq))
	{
		BTNode* front = QueueFront(&pq);
		QueuePop(&pq);
		if (front)
		{
			QueueDestroy(&pq);
			return false;
		}
	}
	QueueDestroy(&pq);
	return true;

}

11.5test.c

cpp 复制代码
#include"BinaryTree.h"
int main()
{
	BTNode* root = CreatBinaryTree();
	// 前序遍历
	BinaryTreePrevOrder(root);
	printf("\n");
	// 中序遍历
	BinaryTreeInOrder(root);
	printf("\n");
	// 后序遍历
	BinaryTreePostOrder(root);
	printf("\n");
	// 判断二叉树是否是完全二叉树
	bool flag = BinaryTreeComplete(root);
	printf("%d\n", flag);
	//层序遍历
	BinaryTreeLevelOrder(root);
	//二叉树查找值为x的节点
	bool flag1 = TreeFind(root, 3);
	BTNode* root1 = BinaryTreeFind(root, 3);
	// 二叉树第k层节点个数
	int KSize = BinaryTreeLevelKSize(root, 3);
	printf("%d\n", KSize);
	// 二叉树叶子节点个数
	int leaf = BinaryTreeLeafSize(root);
	printf("%d\n", leaf);
	 //二叉树节点个数
	int size = BinaryTreeSize(root);
	printf("%d\n", size);
	// 销毁
	BinaryTreePPDestory(&root);
	root = NULL;
}

欢迎各位大佬一起学习交流

相关推荐
Mr_Xuhhh1 小时前
递归搜索与回溯算法
c语言·开发语言·c++·算法·github
文军的烹饪实验室1 小时前
ValueError: Circular reference detected
开发语言·前端·javascript
SoraLuna1 小时前
「Mac玩转仓颉内测版12」PTA刷题篇3 - L1-003 个位数统计
算法·macos·cangjie
B20080116刘实1 小时前
CTF攻防世界小白刷题自学笔记13
开发语言·笔记·web安全·网络安全·php
Qter_Sean2 小时前
自己动手写Qt Creator插件
开发语言·qt
xiaoyaolangwj2 小时前
高翔【自动驾驶与机器人中的SLAM技术】学习笔记(十三)图优化SLAM的本质
学习·机器人·自动驾驶
何曾参静谧2 小时前
「QT」文件类 之 QIODevice 输入输出设备类
开发语言·qt
静止了所有花开3 小时前
SpringMVC学习笔记(二)
笔记·学习
爱吃生蚝的于勒3 小时前
C语言内存函数
c语言·开发语言·数据结构·c++·学习·算法
L_cl5 小时前
Python学习从0到1 day26 第三阶段 Spark ④ 数据输出
学习