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

📖 前言:让节点不再孤单,链条连接一切!在这里,数据结构玩起了“链式反应”!本期我们将深入探讨其结构特点及实现。


🎓 作者:HinsCoder
📦 作者的GitHub:代码仓库
📌 往期文章&专栏推荐:

  1. 【C语言详解】专栏
  2. 【数据结构详解】专栏
  3. 【C语言详解】——函数栈帧的创建与销毁(动图详解)
  4. 【数据结构详解】——线性表之顺序表(多图详解)
  5. 【数据结构详解】——线性表之单链表(动图详解)
  6. 【数据结构详解】——线性表之双向链表(动图详解)

🕒 1. 二叉树的链式存储结构

在这里插入图片描述
二叉树的链式结构可以存储任意(包括:普通二叉树,满二叉树,完全二叉树等),而二叉树的顺序结构(堆)存储普通二叉树会造成空间浪费。

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

二叉树可分两种:

  1. 空树
  2. 非空树:根节点、根节点的左子树、根节点的右子树组成

在这里插入图片描述

以上图为例,将其用代码实现:

// 二叉树的创建
BTNode* CreateTree()
{
	BTNode* n1 = (BTNode*)malloc(sizeof(BTNode));
	assert(n1);
	BTNode* n2 = (BTNode*)malloc(sizeof(BTNode));
	assert(n2);
	BTNode* n3 = (BTNode*)malloc(sizeof(BTNode));
	assert(n3);
	BTNode* n4 = (BTNode*)malloc(sizeof(BTNode));
	assert(n4);
	BTNode* n5 = (BTNode*)malloc(sizeof(BTNode));
	assert(n5);
	BTNode* n6 = (BTNode*)malloc(sizeof(BTNode));
	assert(n6);

	n1->data = 1;
	n2->data = 2;
	n3->data = 3;
	n4->data = 4;
	n5->data = 5;
	n6->data = 6;
	
	n1->left = n2;
	n1->right = n4;
	n2->left = n3;
	n2->right = NULL;
	n3->left = NULL;
	n3->right = NULL;
	n4->left = n5;
	n4->right = n6;
	n5->left = NULL;
	n5->right = NULL;
	n6->left = NULL;
	n6->right = NULL;
	
	return n1;
}

综上:二叉树定义是递归式的,我们可以通过递归的方式去遍历整个二叉树。

🕒 2. 二叉树的遍历

🕘 2.1 前序遍历

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

访问顺序为:根 → 左子树 → 右子树

⚡ 注意:上述所说的根节点不仅仅指的是整棵树的根节点,也指每一个节点(因为每一个节点都可以当作一个根节点来看待)。即一棵完整的树,被拆分成多个子树看待。

请添加图片描述
在这里插入图片描述

在这里插入图片描述
ps:对函数递归展开不熟悉的同学可以像上图这样多画画递归展开图哦~

// 二叉树前序遍历
void PreOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}

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

在这里插入图片描述

学会前序遍历后,我们可以通过给定一个字符串进行创建二叉树,该字符串中含有需要我们构建的二叉树的所有节点。

注意:字符串中的 # 表示空树,即上一个节点没有左子树或右子树。

// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* CreateTree(BTDataType* a, int* pi)
{
	if (a[*pi] == '#')
	{
		(*pi)++;
		return NULL;
	}

	// 创建根节点
	BTNode* root = (BTNode*)malloc(sizeof(BTNode));
	if (root == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	root->data = a[*pi];
	(*pi)++;

	// 创建左右子树
	root->left = CreateTree(a, pi);
	root->right = CreateTree(a, pi);

	return root;
}

🕘 2.2 中序遍历

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

访问顺序为:左子树 → 根 → 右子树

请添加图片描述

// 二叉树中序遍历
void InOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}

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

请添加图片描述

🕘 2.3 后序遍历

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

访问顺序为:左子树 → 右子树 → 根

请添加图片描述

// 二叉树后序遍历
void PostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}

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

在这里插入图片描述

🕘 2.4 层序遍历

区别其他三种遍历方式,层序遍历采用的是非递归

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

访问顺序为:一层一层,从上到下,每层从左到右

请添加图片描述

💡 具体思路:可以使用一个队列来存储二叉树节点的地址。首先,将根节点加入队列,然后将根节点从队列中移除,同时将根节点的左右子节点(如果有的话)加入队列。这样操作可以确保当一层的所有节点都被访问后,它们的子节点会在队列中准备好被访问。当队列为空时,表示二叉树的所有节点都已经被访问过了。

// 二叉树层序遍历
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("%c ", front->data);

		// 将队顶元素的左右子节点入队列
		if (front->left)
			QueuePush(&q, front->left);
		if (front->right)
			QueuePush(&q, front->right);
	}
	QueueDestory(&q);
}

注意:因为需要使用队列来存储二叉树节点的地址,所以必须将之前编写的队列文件Queue.h和Queue.c加入到当前项目中;还需要在队列结构体定义之前定义二叉树节点的结构体。

🕒 3. 二叉树节点个数

💡 思路1:静态变量法

int TreeSize(BTNode* root)
{
	static int count = 0;	//局部变量记得加static
	if (root == NULL)
		return count;

	count++;		//前序
	TreeSize(root->left);
	TreeSize(root->right);

	return count;
}

⚡ 注意:不可取,因为如果再次调用就会出现问题
在这里插入图片描述

💡 思路2:遍历计数法

// 遍历计数
int count = 0;	//全局变量
void TreeSize2(BTNode* root)
{
	if (root == NULL)
		return;

	count++;
	TreeSize2(root->left);
	TreeSize2(root->right);

	return;
}

在这里插入图片描述
⚡ 注意:每次调用前都要置为0,比较麻烦

💡 思路3:划分子问题

int TreeSize(BTNode* root)
{
	return root == NULL ? 0 :
		TreeSize(root->left) + TreeSize(root->right) + 1;//左+右+自己
}

在这里插入图片描述

🕒 4. 二叉树叶子节点个数

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

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

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

🕒 5. 二叉树高度/深度

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

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

	return lh > rh ? lh + 1 : rh + 1;
}

🕒 6. 二叉树第K层节点个数

// 第K层节点个数
int TreeKLevelSize(BTNode* root, int k)
{
	assert(k > 0);

	if (root == NULL)
	{
		return 0;
	}

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

	// 转换成求子树第k-1层
	return TreeKLevelSize(root->left, k - 1)
		+ TreeKLevelSize(root->right, k - 1);
}

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

思考一下,下面的写法对吗❓

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

	if (root->data == x)
	{
		return root;
	}
	TreeFind(root->left, x);
	TreeFind(root->right, x);
}

这种是不对的,详见图解:
在这里插入图片描述

正确方法:

// 返回x所在的节点
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;
}

🕒 8. 修改二叉树节点

💡 思路:配合查找函数使用

	BTNode* ret = TreeFind(root, 5);
	ret->data *= 10;

在这里插入图片描述

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

// 判断二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* root)
{
    Queue q;
    QueueInit(&q);  // 初始化队列

    // 如果根节点不为空,将其入队
    if (root)
        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)
        {
            QueueDestory(&q);  // 销毁队列
            return false;  // 返回 false 表示不是完全二叉树
        }
    }

    QueueDestory(&q);  // 销毁队列
    return true;  // 返回 true 表示是完全二叉树
}

🕒 10. 销毁二叉树

💡 思路:找到左右子树之后再销毁本身,否则找不到子树。

// 销毁二叉树
void TreeDestroy(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}

	TreeDestroy(root->left);
	TreeDestroy(root->right);
	free(root);
}

OK,以上就是本期知识点“链式二叉树”的知识啦~~ ,感谢友友们的阅读。后续还会继续更新,欢迎持续关注哟📌~
🎉如果觉得收获满满,可以点点赞👍支持一下哟~


❗ 转载请注明出处
作者:HinsCoder
博客链接:🔎 作者博客主页

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值