【数据结构与算法】二叉树链式存储结构的实现 超详细递归展开图

小编个人主页详情<—请点击
小编个人gitee代码仓库<—请点击
数据结构与算法系列专栏<—请点击
倘若命中无此运,孤身亦可登昆仑,送给屏幕面前的读者朋友们和小编自己!
在这里插入图片描述



一、二叉树链式存储结构的概念讲解

二叉树的链式存储结构是用链表来表示一颗二叉树,使用链表链接树节点,通常链表节点有三个成员变量,即左右指针和一个变量,左指针用于存储当前节点的左孩子节点的地址,右指针用于存储当前节点的右孩子节点的地址,变量用于存储数据

如果没有左孩子或对应的右孩子,举例:叶子节点没有左孩子和右孩子,那么对应的左右指针指向空即可,如2和3节点的左右指针指向了空

在这里插入图片描述

  1. 这里将存储的元素类型使用typedef进行重命名,提高代码的可维护性
  2. 对结构体名称进行重命名,便于书写
  3. 在结构体内定义我们所需要的三个成员变量,即左指针,右指针,变量即可
typedef int BTDataType;

typedef struct BTNode
{
    
    
	struct BTNode* left;
	struct BTNode* right;
	BTDataType data;
}BTNode;

二、二叉树的多文件管理

为了便于对二叉树进行维护,这里我们采用多文件管理
在这里插入图片描述

  • BTree.h包括了头文件,结构体的定义,函数声明
  • BTree.c包括了函数功能的实现
  • Test.c包括了函数接口测试的基本框架

下面是小编要实现的函数接口总览,往下阅读,跟上小编的节奏,下面小编将进行细致的讲解

//申请节点
BTNode* BuyNode(BTDataType x);
//前序遍历
void PreOrder(BTNode* root);
//中序遍历
void InOrder(BTNode* root);
//后序遍历
void PostOrder(BTNode* root);
//二叉树的总结点个数
int TreeSize(BTNode* root);
//二叉树的深度
int TreeHeight(BTNode* root);
//查找第K层节点的节点个数
int TreeKLevel(BTNode* root, int k);
//查找值为x的节点
BTNode* TreeFind(BTNode* root, BTDataType x);
//层序遍历
void LevelOrder(BTNode* root);
//判断是否为完全二叉树
bool TreeComplete(BTNode* root);
//二叉树的销毁
void TreeDestory(BTNode* root);

三、二叉树链式存储结构的实现

注意:

  1. 由于二叉树结构的特殊性,所以二叉树是采用递归的形式进行遍历

3.1二叉树节点的申请

  1. 使用malloc申请节点并检查
  2. 将节点的赋值为我们的目标数据x
  3. 由于节点初始仅仅是申请,并未有任何链接关系,所以这里我们将左右指针置空
  4. 返回节点的指针即可
BTNode* BuyNode(BTDataType x)
{
    
    
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	if (node == NULL)
	{
    
    
		perror("malloc error");
		return NULL;
	}

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

	return node;
}

3.2手搓一个简单的二叉树

  1. 由于我们已经有了节点的申请函数,那么我们就可以手搓一个二叉树了
  2. 申请6个节点分别将指针传给6个节点指针
  3. 根据下图关系我们将节点链接起来即可
  4. 后续小编进行测试的二叉树即为我们手搓的二叉树
    在这里插入图片描述
	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;
	node2->left = node3;
	node1->right = node4;
	node4->left = node5;
	node4->right = node6;

3.3二叉树的前序遍历

  1. 如果二叉树的根节点为空,为了便于我们观察细节,这里我们打印NULL,再返回即可
  2. 二叉树的前序遍历是先访问当前根节点再访问左子树,左子树访问完成后再访问右子树
  3. 访问根节点采用打印data数据的形式表示,接下来依次递归调用左子树、右子树即可完成前序遍历
    在这里插入图片描述
void PreOrder(BTNode* root)
{
    
    
	if (root == NULL)
	{
    
    
		printf("NULL ");
		return;
	}

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

3.4二叉树的中序遍历

  1. 如果二叉树的根节点为空,为了便于我们观察细节,这里我们打印NULL,再返回即可
  2. 二叉树的中序遍历是先访问左子树再访问当前的根节点,根节点访问完成后再访问右子树
  3. 接下来依次递归调用左子树、访问根节点打印data数据、右子树即可完成遍历
  4. 这里的递归展开图类似于前序遍历,小编这里不再重复画图
void InOrder(BTNode* root)
{
    
    
	if (root == NULL)
	{
    
    
		printf("NULL ");
		return;
	}

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

3.5二叉树的后序遍历

  1. 如果二叉树的根节点为空,为了便于我们观察细节,这里我们打印NULL,再返回即可
  2. 二叉树的后序遍历是先访问左子树再访问右子树,右子树访问完成后再访问根节点
  3. 接下来依次递归调用左子树、右子树、访问根节点打印data数据即可完成遍历
  4. 这里的递归展开图类似于前序遍历,小编这里不再重复画图
void PostOrder(BTNode* root)
{
    
    
	if (root == NULL)
	{
    
    
		printf("NULL ");
		return;
	}

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

3.6二叉树的总节点的个数

  1. 如果二叉树的根为空代表没有节点,那么节点个数为0,返回0
  2. 如果当前根节点不为空,那么当前根节点的个数为一个节点
  3. 这一个节点加上递归调用当前根的左子树和右子树的返回值个数即为当前二叉树的总结点的个数

在这里插入图片描述

int TreeSize(BTNode* root)
{
    
    
	if (root == NULL)
		return 0;

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

3.7二叉树的高度

  1. 如果二叉树的根为空代表没有节点,那么也就是高度为0,返回0
  2. 递归采用变量保存左子树的高度
  3. 递归采用变量保存左子树的高度
  4. 找出左右子树中高度最大的那一个再加上当前根的高度1即为树的总高度,返回即可
    在这里插入图片描述
int TreeHeight(BTNode* root)
{
    
    
	if (root == NULL)
	{
    
    
		return 0;
	}

	int right = TreeHeight(root->right);
	int left = TreeHeight(root->left);

	return right > left ? right + 1 : left + 1;
}

3.8二叉树查找第K层节点的节点个数

  1. 层级是从1开始算起,那么要断言确保K>0
  2. 如果二叉树的根为空代表没有节点,那么也就是节点个数为0,返回0即可
  3. 如果一共有三层,我们要寻找第3层有几个节点,那么k等于3,对于第一层就相当于找它的第三层此时k为3,对于第二层就相当于找它的第二层此时k为2,对于第三层就相当于找它的第一层此时k为1,此时要的第三层就为我们的目标层第三层,即我们可以采用k作为递归条件,递归到k为1的时候就为我们要找的目标层级,统计个数返回即可
    在这里插入图片描述
int TreeKLevel(BTNode* root, int k)
{
    
    
	assert(k > 0);

	if (root == NULL)
		return 0;

	if (k == 1)
		return 1;

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

3.9查找值为x的节点

修改二叉树指定为x的节点

注意这里的查找接口返回值并不是bool值,而是二叉树节点的指针,这就很精髓了,那么就是说我们可以利用查找函数返回的这个指针进行对二叉树的节点进行修改,一接口两用,既可以查找又可以修改

  1. 当节点为空的时候,连数据都没有,那么必然是找不到x的,所以我们这里返回空代表找不到
  2. 当节点中的存储的data值等于x,返回节点指针即可
  3. 接下来左子树递归,注意这里都是有返回值的,所以递归返回的值我们要采用节点指针来接收,这样才能比较并且层层递进返回
  4. 如果左子树递归返回值不为空那么说明在左子树的递归过程中找到了x节点的指针,返回即可层层向上返回
  5. 右子树也是同理
  6. 当左右子树都走完了还没有返回,那么就说明左右子树中没有我们要查找的值为x的节点,返回NULL即可

在这里插入图片描述

BTNode* TreeFind(BTNode* root, BTDataType x)
{
    
    
	if (root == NULL)
		return NULL;

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

	BTNode* lret = TreeFind(root->left, x);
	if (lret)
		return lret;

	BTNode* rret = TreeFind(root->right, x);
	if (rret)
		return rret;

	return NULL;
}

3.10二叉树的层序遍历

  1. 这里的层序遍历需要使用到队列,即使用先进先出的性质完美契合二叉树的层序遍历
  2. 首先我们拷贝一个队列的实现代码详情点击<—,拷贝完成之后互包头文件,将单个队列的节点存储的数据类型修改为二叉树的指针类型即BTree*
  3. 为什么不存储值而是要存储二叉树节点类型的指针,因为我们要靠二叉树节点指针找到其左右孩子进行遍历,而储存data值仅仅是存值,不能达到我们想要的效果
    在这里插入图片描述
    在这里插入图片描述
  4. 创建队列,初始化队列
  5. 当二叉树的根节点不为空的时候将二叉树的节点插入队列中去
  6. 使用循环当队列不为空的时候我们就先将在队列中的头节点的值取出来保存,出队列,接下来我们利用我们保存的二叉树的指针访问二叉树节点的data值打印数据,打印完之后再将利用我们保存的二叉树的指针找到其左孩子和右孩子不为空的依次入队列
  7. 接下来循环起来我们的二叉树就层序打印完了
    在这里插入图片描述
void LevelOrder(BTNode* root)
{
    
    
	Queue que;
	QueueInit(&que);

	if (root != NULL)
		QueuePush(&que, root);

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

		if (front->left)
			QueuePush(&que, front->left);

		if(front->right)
			QueuePush(&que, front->right);
	}

	QueueDestory(&que);
}

3.11判断是否为完全二叉树

  1. 首先我们来回顾一下什么是满二叉树,假设满二叉树有k层,每一层都为当前节点的最大值即为满二叉树
  2. 完全二叉树,假设完全二叉树有k层,那么前k-1层为满二叉树,第k层的节点是连续的且个数最少为1,最大为当前层的节点的最大值
    在这里插入图片描述
  3. 这里同样也是使用队列进行判断,类比3.10二叉树的层序遍历,那里是如果左右子树中有空的那就不入对应的NULL左右子树,而这里是将左右子树为空的情况全都入队列
  4. 当出队列时遇到第一个为NULL指针的情况就退出
  5. 观察可得,完全二叉树在退出的时候队列中都为空且连续,非完全二叉树在退出的时候队列中不全为空,且非连续

如果二叉树为完全二叉树的情况
在这里插入图片描述
如果二叉树为非完全二叉树的情况
在这里插入图片描述

  1. 注意这里队列的类型是二叉树指针BTree*所以队列中可以存储NULL,为NULL指针,我们同样也可以将空指针出队列进行判断是否为NULL指针
  2. 如果完全二叉树,那么出队列中进行判断全为NULL,如果为非完全二叉树,那么出队列中进行判断会有非空节点,这样我们就可以区分二叉树是否为完全二叉树了
bool TreeComplete(BTNode* root)
{
    
    
	Queue q;
	QueueInit(&q);

	if (root)
		QueuePush(&q,root);

	while (!QueueEmpty(&q))
	{
    
    
		BTNode* first = QueueHead(&q);
		QueuePop(&q);

		if (first == NULL)
		{
    
    
			break;
		}
		else
		{
    
    
			QueuePush(&q, first->left);
			QueuePush(&q, first->right);
		}
	}

	while (!QueueEmpty(&q))
	{
    
    
		BTNode* tmp = QueueHead(&q);
		QueuePop(&q);
		if (tmp)
			return false;
	}

	return true;
}

3.12二叉树的销毁

  1. 当根节点为空的时候我们返回即可
  2. 由于二叉树的节点都是malloc出来的,所以二叉树的销毁我们采用进行free销毁
  3. 后序遍历二叉树的形式是先访问左子树我们可以直接释放左子树,再访问右子树我们可以直接释放右子树,再访问根节点我们可以直接释放根节点,不需要额外的指针来保存相对应根节点的左右子树,那么恰好符合我们的销毁要求
  4. 由于前序是先访问根节点再访问左右子树,访问了根节点,进行释放了根节点,那么左右子树就找不到了,所以需要前序需要指针来保存左右子树才能进行释放
  5. 类似的如果采用中序遍历,那么需要指针来保存右子树,才能进行释放根节点
  6. 前序中序后序相比较而言这里小编采用后续遍历进行二叉树的销毁
  7. 这种销毁方式由于传入的是一级指针,对二叉树的根节点我们没有办法从函数内部拿到存储二叉树根节点指针的地址,即无法从函数内部对二叉树的根节点的指针置空,所以需要操作者在调用完销毁函数后在函数外部手动置空,这是为了避免野指针和非法访问内存
    在这里插入图片描述
void TreeDestory(BTNode* root)
{
    
    
	if (root == NULL)
		return;

	TreeDestory(root->left);
	TreeDestory(root->right);

	free(root);
}

四、二叉树的函数接口测试

  1. 这里我们先构建好我们的节点
    在这里插入图片描述
  2. 依次进行前序,中序,后序访问打印
  3. 计算二叉树的大小并打印
  4. 计算二叉树的高度并打印
  5. 计算二叉树第二层的节点个数并打印
  6. 查找第一个值为3的节点的地址并打印
  7. 查找第一个值为500的节点的地址并打印
  8. 层序遍历二叉树
  9. 该树明显不是完全二叉树,这里我们判断一下是否为完全二叉树并打印
  10. 销毁二叉树并将二叉树根节点置空
void TestBTree()
{
    
    
	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;
	node2->left = node3;
	node1->right = node4;
	node4->left = node5;
	node4->right = node6;

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

	printf("TreeSize=%d\n", TreeSize(node1));
	printf("TreeHeight=%d\n", TreeHeight(node1));
	printf("TreeKLevel=%d\n", TreeKLevel(node1, 2));
	printf("TreeFind=%p\n", TreeFind(node1, 3));
	printf("TreeFind=%p\n", TreeFind(node1, 500));

	LevelOrder(node1);

	printf("TreeComplete=%d", TreeComplete(node1));

	TreeDestory(node1);
	node1 = NULL;
}

结果无误,所有函数接口正确
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  1. 同样的这里还可以将我们的节点的链接关系修改一下使其变成完全二叉树,再调用我们的完全二叉树的判断函数进行判断进行验证
  2. 小编将节点6链接到节点2上,使节点6成为节点2的右子树,这样我们构建的树就构建成为了完全二叉树,判断即可
    在这里插入图片描述
void TestTreeComplete()
{
    
    
	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;
	node2->left = node3;
	node1->right = node4;
	node4->left = node5;
	node2->right = node6;

	printf("TreeComplete=%d\n", TreeComplete(node1));
}

结果正确,接口无误
在这里插入图片描述

五、二叉树链式存储结构的实现源代码

BTree.c

#pragma once

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

typedef int BTDataType;

typedef struct BTNode
{
    
    
	struct BTNode* left;
	struct BTNode* right;
	BTDataType data;
}BTNode;

//申请节点
BTNode* BuyNode(BTDataType x);
//前序遍历
void PreOrder(BTNode* root);
//中序遍历
void InOrder(BTNode* root);
//后序遍历
void PostOrder(BTNode* root);
//二叉树的大小
int TreeSize(BTNode* root);
//二叉树的深度
int TreeHeight(BTNode* root);
//查找第K层节点的节点个数
int TreeKLevel(BTNode* root, int k);
//查找值为x的节点
BTNode* TreeFind(BTNode* root, BTDataType x);
//层序遍历
void LevelOrder(BTNode* root);
//判断是否为完全二叉树
bool TreeComplete(BTNode* root);
//二叉树的销毁
void TreeDestory(BTNode* root);

BTree.c

#define _CRT_SECURE_NO_WARNINGS

#include "BTree.h"
#include "Queue.h"

BTNode* BuyNode(BTDataType x)
{
    
    
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	if (node == NULL)
	{
    
    
		perror("malloc error");
		return NULL;
	}

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

	return node;
}

void PreOrder(BTNode* root)
{
    
    
	if (root == NULL)
	{
    
    
		printf("NULL ");
		return;
	}

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

void InOrder(BTNode* root)
{
    
    
	if (root == NULL)
	{
    
    
		printf("NULL ");
		return;
	}

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

void PostOrder(BTNode* root)
{
    
    
	if (root == NULL)
	{
    
    
		printf("NULL ");
		return;
	}

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

int TreeSize(BTNode* root)
{
    
    
	if (root == NULL)
		return 0;

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

int TreeHeight(BTNode* root)
{
    
    
	if (root == NULL)
	{
    
    
		return 0;
	}

	int right = TreeHeight(root->right);
	int left = TreeHeight(root->left);

	return right > left ? right + 1 : left + 1;
}

int TreeKLevel(BTNode* root, int k)
{
    
    
	assert(k > 0);
	if (root == NULL)
		return 0;
	if (k == 1)
		return 1;
	return TreeKLevel(root->left, k - 1) 
		+ TreeKLevel(root->right, k - 1);
}



BTNode* TreeFind(BTNode* root, BTDataType x)
{
    
    
	if (root == NULL)
		return NULL;
	if (root->data == x)
		return root;
	BTNode* lret = TreeFind(root->left, x);
	if (lret)
		return lret;
	BTNode* rret = TreeFind(root->right, x);
	if (rret)
		return rret;
	return NULL;
}

void LevelOrder(BTNode* root)
{
    
    
	Queue que;
	QueueInit(&que);

	if (root != NULL)
		QueuePush(&que, root);

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

		if (front->left)
			QueuePush(&que, front->left);

		if(front->right)
			QueuePush(&que, front->right);
	}

	printf("\n");
	QueueDestory(&que);
}

bool TreeComplete(BTNode* root)
{
    
    
	Queue q;
	QueueInit(&q);

	if (root)
		QueuePush(&q,root);

	while (!QueueEmpty(&q))
	{
    
    
		BTNode* first = QueueHead(&q);
		QueuePop(&q);

		if (first == NULL)
		{
    
    
			break;
		}
		else
		{
    
    
			QueuePush(&q, first->left);
			QueuePush(&q, first->right);
		}
	}

	while (!QueueEmpty(&q))
	{
    
    
		BTNode* tmp = QueueHead(&q);
		QueuePop(&q);
		if (tmp)
			return false;
	}

	return true;
}

void TreeDestory(BTNode* root)
{
    
    
	if (root == NULL)
		return;
	TreeDestory(root->left);
	TreeDestory(root->right);
	free(root);
}

Test.c

#define _CRT_SECURE_NO_WARNINGS

#include "BTree.h"

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

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

	printf("TreeSize=%d\n", TreeSize(node1));
	printf("TreeHeight=%d\n", TreeHeight(node1));
	printf("TreeKLevel=%d\n", TreeKLevel(node1, 2));
	printf("TreeFind=%p\n", TreeFind(node1, 3));
	printf("TreeFind=%p\n", TreeFind(node1, 500));

	LevelOrder(node1);

	printf("TreeComplete=%d", TreeComplete(node1));

	TreeDestory(node1);
	node1 = NULL;
}

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

	printf("TreeComplete=%d\n", TreeComplete(node1));
}

int main()
{
    
    
	//TestBTree();
	TestTreeComplete();

	return 0;
}

总结

以上就是今天的博客内容啦,希望对读者朋友们有帮助
水滴石穿,坚持就是胜利,读者朋友们可以点个关注
点赞收藏加关注,找到小编不迷路!