当前位置: 首页 > article >正文

数据结构——链式二叉树的实现与分治编程思维(c语言实现)

目录

前言:

1.前置说明

2.链式二叉树的遍历 

2.1 前序,中序及后续遍历

2.2 前序遍历实现

2.3 中序遍历实现 

2.4 后续遍历实现 

3.结点个数以及高度等 

3.1 结点个数

3.2 结点高度 

3.3 叶子结点的个数 


前言:

   在之前的学习中,我们初步学习了二叉树的概念和实现二叉树的顺序结构,最主要的是使用二叉树的顺序结构建堆,从而实现堆排序,这一章我们要学习的是二叉树的另一个结构——二叉树的链式结构,与顺序结构不同的是,顺序结构的底层是一个数组,链式结构是使用递归将多个结点链接起来组成的二叉树,讲到这里,递归还不是很熟悉的小伙伴需要回去复习递归的知识才能更好的理解链式二叉树的实现,话不多是,我们马上开始这一期的学习吧。

1.前置说明

  在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。由于现在大家对二叉树结构掌握还不够深入,为了降低大家学习成本,此处手动快速创建一棵简单的二叉树,快速进入二叉树操作学习,等二叉树结构了解的差不多时,我们会过头再来研究二叉树真正的创建方式。

   使用结构体来定义二叉树的结点,里面包含了它的数据和它的左子树,右子树,我们不知道将来会存什么类型的数据在结点中,所以使用typedef关键字来对它的数据类型改名,现在我们使用的是int类型,如果我们以后要使用char类型,只需要将第一行的int改成char就能实现了,如果我们不使用这个操作,将来要更改数据类型时,只能在各个函数中一个一个改,几十行几百行代码我们要改类型工作量还不是很大,如果是几万行几十万行其中的工作量有多大可想而知。

typedef int BTDateType;
typedef struct BinaryTreeNode
{
	BTDateType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;
BTNode* creratNode()
{
	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;

	return node1;
}

注意:上述代码并不是创建二叉树的方式,真正创建二叉树方式后序详解重点讲解 

再看二叉树基本操作前,再回顾下二叉树的概念,二叉树是
1. 空树
2. 非空:根结点,根结点的左子树、根结点的右子树组成的。

从概念中可以看出,二叉树定义是递归式的,因此后序基本操作中基本都是按照该概念实现的。

2.链式二叉树的遍历 

2.1 前序,中序及后续遍历

学习二叉树结构,最简单的方式就是遍历。所谓二叉树遍历(Traversal)是按照某种特定的规则,依次对二叉树中的结点进行相应的操作,并且每个结点只操作一次。访问结点所做的操作依赖于具体的应用问题。 遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。

按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历:
1. 前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。
2. 中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。
3. 后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。 

     由于被访问的结点必是某子树的根,所以N(Node)、L(Left subtree)和R(Right subtree)又可解释为根、根的左子树和根的右子树。NLR、LNR和LRN分别又称为先根遍历、中根遍历和后根遍历。

     也就是说,前序遍历的访问顺序依次为:根结点,左子树,右子树。相应的,中序遍历的顺序依次为:左子树,根节点,右子树后序遍历的顺序依次为:左子树,右子树,根节点

我们有一棵二叉树:

假设空结点为N:

前序遍历:1   2   3   N   N   N   4   5   N   N   6   N   N

中序遍历:N   3   N   2   N   1   N   5   N   4   N   6   N 

后序遍历:N   N   3   N   2   N   N   5  N   N   6    4   1

其中N就是我们在访问一些结点的左右子树时发现是空树时的标记

2.2 前序遍历实现

  我们在前面已经简单的实现了一棵树,现在就可以使用这棵树实现一些像遍历,计算结点个数这样的操作了。

   前序遍历的代码实现也比较简单,使用递归实现前序遍历,我们要先访问根结点,再去访问左子树和右子树,就是先打印根节点的数据,然后依次调用自己传左子树和右子树实现递归,如果遇到空树,我们就打印N,然后使用return马上走出这个函数栈帧,回到上一个函数栈帧,直到回到根结点为止。

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

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

图解:

2.3 中序遍历实现 

   中序遍历的实现与前序遍历的原理大致相同,在前序遍历原有代码的基础上改变顺序就可以实现了:

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

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

2.4 后续遍历实现 

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

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

前序遍历结果:1 2 3 4 5 6
中序遍历结果:3 2 1 5 4 6
后序遍历结果:3 2 5 6 4 1

程序运行结果:

3.结点个数以及高度等 

3.1 结点个数

   计算结点个数这里很容易掉入一个坑中,计算结点个数必然要使用递归,多次调用同一个函数,但是我们的每一次调用,都会建立一个独立的栈帧,而函数结束之后,函数栈帧也随之销毁,函数栈帧里的变量也不会存在,所以就不能使用局部变量来计算,我们自然而然就想到使用静态变量来计算,因为静态变量是存在于于堆区的,函数栈帧销毁不会将静态变量回收,所以有计算节点个数的函数:

size_t TreeSize(BTNode* root)
{
	static size_t size;
	if (root == NULL)
	{
		return 0;
	}
	else
		size++;

	TreeSize(root->left);
	TreeSize(root->right);
	return size;
}

这个函数能不能算出结点个数呢,我们当前是这样一棵树:

共六个结点,来看程序运行结果:

这样一看是不是很正确呢,但是如果我们调用两次:

    二叉树的结点个数又变成12了,到这我们也发现问题所在了,由于size是静态变量,在内存中只存在一份,在第二次调用时size的值就是6,而二叉树的结点是6个,就这样size的值变成了12,所以这样的方法是行不通的。所以我们使用另一种方法——分治。什么叫分治呢,假如某个学校的校长想要统计一下在校师生人数,他就会通知各个学院的院长让他们统计人数,而各个学院的院长通知各个班的班主任,班主任通知各个班的班长,将人数统计出来加上自己就是人数,这样是不是就方便了许多呢:

所以我们计算二叉树结点的函数应该是这样:

size_t TreeNodeCount(BTNode* root)
{
	return root == NULL ? 0 : TreeNodeCount(root->left) +
		TreeNodeCount(root->right) + 1;
}

将自己与左右子树的结点个数加起来就是总的结点个数:

3.2 结点高度 

   计算结点高度也是一个带着坑的问题,这样的问题也需要使用递归实现,我们的方法是:将左右子树的高度分别算出来,然后将大的那个加一就是整棵树的高度,但是我们在计算二叉树高度时,需要将它们的结果存起来,如果不存起来,我们每次要拿到这个结果都要递归一次,如果这棵树高一点的话,其中的计算量是非常恐怖的,所以我们要使用这种将结果存起来的方法:

size_t TreeHeight(BTNode* root)
{
	if (root == NULL)
		return 0;
	size_t leftheigh = TreeHeight(root->left);
	size_t rightheigh = TreeHeight(root->right);

	return leftheigh > rightheigh ? leftheigh + 1 : rightheigh + 1;
}

我们当前树的高度是3,来看看程序运行结果吧:

3.3 叶子结点的个数 

  如何计算叶子结点的个数呢,我们发现只有叶子结点没有左右子树,所以只要一个结点的左右子树为空我们就认为它是叶子结点,发现叶子结点我们就返回1,如果当前结点不是叶子结点我们就去计算左右子树的叶子结点个数,需要注意的是,如果我们访问的是一棵空树,程序马上就会崩溃,所以要加个判断:如果是一棵空树就马上返回0结束这个栈帧,这样程序就不会崩溃了。

size_t TreeLelfSize(BTNode* root)
{
	//如果是空树,要马上返回零,否则走到下一行代码会崩溃
	if (root == NULL)
		return 0;

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

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

这是我们当前的二叉树:

可以看到叶子结点有3个,来看看程序运行结果吧:

 我将代码放在下面,感兴趣的话可以试试哦:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
static size_t size;
typedef int BTDateType;
typedef struct BinaryTreeNode
{
	BTDateType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;

BTNode* BuyNode(BTDateType x)
{
	BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
	}

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

	return newnode;
}

size_t TreeLelfSize(BTNode* root)
{
	//如果是空树,要马上返回零,否则走到下一行代码会崩溃
	if (root == NULL)
		return 0;

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

	return TreeLelfSize(root->left) + TreeLelfSize(root->right);
}
void FrontOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}

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

BTNode* creratNode()
{
	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;

	return node1;
}

size_t TreeSize(BTNode* root)
{	
	if (root == NULL)
	{
		return 0;
	}
	else
		size++;

	TreeSize(root->left);
	TreeSize(root->right);
	return size;
}
size_t TreeHeight(BTNode* root)
{
	if (root == NULL)
		return 0;
	size_t leftheigh = TreeHeight(root->left);
	size_t rightheigh = TreeHeight(root->right);

	return leftheigh > rightheigh ? leftheigh + 1 : rightheigh + 1;
}

void MiddleOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	MiddleOrder(root->left);
	printf("%d ", root->data);
	MiddleOrder(root->right);
}
size_t TreeNodeCount(BTNode* root)
{
	return root == NULL ? 0 : TreeNodeCount(root->left) +
		TreeNodeCount(root->right) + 1;
}
void BackOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	BackOrder(root->left);
	BackOrder(root->right);
	printf("%d ", root->data);
}
int main()
{
	BTNode* root = creratNode();
	printf("前序遍历二叉树:");
	FrontOrder(root);
	printf("\n");

	printf("中序遍历二叉树:");
	MiddleOrder(root);
	printf("\n");

	printf("后序遍历二叉树:");
	BackOrder(root);
	printf("\n");

	printf("二叉树结点的数量:");
	
	printf("%zd\n", TreeNodeCount(root));

	printf("二叉树的高度为:");
	printf("%zd\n", TreeHeight(root));

	printf("二叉树的叶子节点有%zd个", TreeLelfSize(root));
	return 0;
}

本章完。  


http://www.kler.cn/a/284095.html

相关文章:

  • vue2,vue3,uniapp,小程序实现前端url生成二维码
  • 【计网不挂科】计算机网络期末考试(综合)——【选择题&填空题&判断题&简述题】完整题库
  • Linux操作系统之软件安装与包管理器工具
  • AI与OCR:数字档案馆图像扫描与文字识别技术实现与项目案例
  • BEAGLE: Forensics of Deep Learning Backdoor Attack for Better Defense(论文阅读)
  • 网络安全从零开始学习CTF——CTF基本概念
  • sql-labs靶场(41-50)
  • unity脚本
  • 理解 Maven 依赖范围及编译与运行时的需求
  • 无缝 CI/CD:如何在 Windows 环境中使用 Docker 和 Jenkins 自动化部署 .NET 应用
  • 嵌入式全栈开发学习笔记---Linux系统编程(进程控制)
  • 全球城市多边形和点数据集 (GUPPD)
  • 带你手撕面试题——定时器方案:红黑树版
  • OSINT技术情报精选·2024年8月中旬
  • 美容院拓客营销门店管理小程序渠道进行
  • 我的世界实体与生物ID表
  • 前后端传参@RequestParam使用上的一个小坑
  • 代码随想录八股训练营总结篇 2024年8月
  • 爬虫入门urllib 和 request (一)
  • Java+selenium 实现网页缩放的方法:用于解决页面太长部分元素定位不到的问题
  • 企业级NoSql数据库 --- Redis集群
  • Underactuated Robotics - 欠驱动机器人学(三)- 体操机器人、小车摆杆和四旋翼飞行器
  • pyhton - PyHive
  • 金融上云方案中,国产虚拟化产品选型的重点考虑因素、自动化运维建设步骤及如何进行保障数据中心安全需求的存储设计等问题及解决方法|金融行业数字化QA合集③
  • 77. 组合
  • shell脚本编写注意事项