分享好友 编程语言首页 频道列表

C语言实现二叉树链式结构的示例详解

C/C++教程  2023-02-09 05:110

前言

前面我们已经对堆进行学习,堆就是一个顺序结构的二叉树,把数组看成二叉树,下面一起学习一下链式结构的二叉树,这里是用递归实现功能

1. 链式二叉树结构

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

2. 二叉树的遍历

首先,我要说明一下递归实现;递归实现一般分为三个步骤(递归三要素):初步明确函数功能,限制条件,找到实现此功能的等式

单项递归和二叉树递归(多项递归)的区别?

单项递归并没有分支,多项递归是有分支的,这就意味着二叉树更看中整体,单项递归更看重分治。

单项递归和二叉树递归的共同点?

都是分治思想,子问题再分子问题再分子问题的思想

2.1 前序遍历

思想:把树看成整体:根、左子树、右子树,先遍历根再走左子树再走右子树

void BinaryTreePrevOrder(BTNode* root)
{
    //根的情况(到底的限制条件)
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}

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

2.2 中序遍历

思想:把树看成整体:根、左子树、右子树,先遍历左子树再走根再走右子树

void BinaryTreeInOrder(BTNode* root)
{
    //根的情况(到底的限制条件)
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}

	BinaryTreeInOrder(root->left);
	printf("%c ", root->data);
	BinaryTreeInOrder(root->right);
}

2.3 后序遍历

void BinaryTreePostOrder(BTNode* root)
{
    //根的情况(到底的限制条件)
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}

	BinaryTreePostOrder(root->left);
	BinaryTreePostOrder(root->right);
	printf("%c ", root->data);
}

2.4 层序遍历

思想:出上一层的同时带着下一层入队列

C语言实现二叉树链式结构的示例详解

C语言实现二叉树链式结构的示例详解

C语言实现二叉树链式结构的示例详解

C语言实现二叉树链式结构的示例详解

C语言实现二叉树链式结构的示例详解

C语言实现二叉树链式结构的示例详解

//链式队列的结构
typedef struct BinaryTreeNode* QueueDataType;
typedef struct QueueNode
{
	QueueDataType data;
	struct QueueNode* next;
}QueueNode;
//因为要直接得到队头的元素和队尾的元素
typedef struct QueueLinkList
{
	QueueNode* head; //队头
	QueueNode* tail; //队尾
	int size; //元素总数
}QLL;
//队列初始化
void QLLInit(QLL* queue)
{
	assert(queue);

	queue->head = NULL;
	queue->tail = NULL;
	queue->size = 0;
}
//进队
void QLLPush(QLL* queue, QueueDataType x)
{
	assert(queue);

	QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
	if (newnode == NULL)
	{
		perror("QLLPush:malloc is failed!\n");
		exit(-1);
	}

	newnode->data = x;
	newnode->next = NULL;

	if (queue->head == NULL)
	{
		queue->head = queue->tail = newnode;
	}
	else
	{
		queue->tail->next = newnode;
		queue->tail = newnode;
	}

	queue->size++;
}
//出队
void QLLPop(QLL* queue)
{
	assert(queue != NULL);
	assert(QLLEmpty(queue) != true);
		
	//只有一个结点时
	if (queue->head->next == NULL)
	{
		free(queue->head); //free的是这个结点的空间,并不是指针
		queue->head = queue->tail = NULL;
	}
	else
	{
		//通常情况
		QueueNode* del = queue->head;
		queue->head = queue->head->next;
		free(del);
		//无需置空
	}

	queue->size--;
}
//拿取队头数据
QueueDataType QLLFront(QLL* queue)
{
	assert(queue != NULL);
	assert(QLLEmpty(queue) != true);

	return queue->head->data;
}
//判空
bool QLLEmpty(QLL* queue)
{
	assert(queue);

	//return queue->size == 0;
	return queue->head == NULL && queue->tail == NULL;
}
//销毁
void QLLDestroy(QLL* queue)
{
	assert(queue);

	QueueNode* cur = queue->head;
	while (cur != NULL)
	{
		QueueNode* del = cur;
		cur = cur->next;
		free(del);
		del = NULL;
	}

	queue->head = queue->tail = NULL;
	queue->size = 0;
}

//层序遍历实现
void BinaryTreeLevelOrder(BTNode* root)
{
	QLL queue;
	QLLInit(&queue);
    //根先入队列
	if (root != NULL) {
		QLLPush(&queue, root);
	}
	//队列不为NULL的时候进行出队头带下层数据入队操作
	while (QLLEmpty(&queue) != true)
	{
        //出队头操作
		BTNode* front = QLLFront(&queue);
		printf("%c ", front->data);
		QLLPop(&queue);
        //带下一层进队
		if (front->left != NULL)
		{
			QLLPush(&queue, front->left);
		}
		if (front->right != NULL)
		{
			QLLPush(&queue, front->right);
		}
	}
	printf("\n");
	QLLDestroy(&queue);
}

说明:为什么递归不画图来解决呢?

多项递归画图是很难理解的,因为他不是我们逻辑上想的,就拿前序遍历来说,首先是根,再遍历左子树再遍历右子树这样循环来走,但是在实际递归中逻辑是左子树走到底,直到NULL时返回访问右子树,如果说是画图是理解不了二叉树递归的,这里我们就要扣住树的结构:根、左子树、右子树,这样是我们逻辑上的实现,并不是实际中的过程实现,这里我需要说明一下,画图是为了在原有基础上来进行纠错,这里纠正的错也是和根的限制条件有关,这里我还会出几期二叉树的相关练习,到时候希望大佬们看看就能理解了二叉树递归!

3. 常见功能

3.1 二叉树结点个数

递归三要素解决问题

  • 首先二叉树想到整体结构:根、左子树、右子树
  • 函数功能:求二叉树结点的个数
  • 限制条件:根为NULL的时候就不是一个结点(起初的结束条件:针对根来说)
  • 等式:计算左子树中的结点个数和右子树的结点个数,最后加上根这个结点
int BinaryTreeSize(BTNode* root)
{
	if (root == NULL) {
		return 0;
	}

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

代码分析

上述列出来的思路就是实现思路,这里注意的是树的整体结构,我一直扣的就是整体结构,等式中写的也是整体结构的逻辑;这里来看代码很简单就是根和左子树和右子树结构

C语言实现二叉树链式结构的示例详解

为什么不写子结构:根、左孩子、右孩子?

原因就是如果写成子结构的话就不是整体,而是把整体分为多个相同的结构来讨论,这里就不是整体观念就很容易陷进去,为什么二叉树递归难,难就难在你没扣住整体,而是扣住的是子结构,如果扣住子结构那就很容易陷进去,只要陷进去了就不是我们自己想的逻辑,而是实际递归的过程逻辑,实际递归的过程逻辑和我们想的逻辑有很大的区别

为什么首先要有个前提:树的结构:根、左子树、右子树?

原因很简单,我们考虑整体就少不了这个结构,这是我们首先要考虑的问题;另外也是因为这里三要素中的实现是离不开这个整体结构的,如果离开了整体结构就又被陷进去了

限制条件是怎么来限制的?

首先我们考虑的结构就是树的整体结构:根、左子树、右子树,我们不可能是来对左右子树来限制吧,因为左右子树中有很多结点,从整体上来说是考虑不到的,另外你只要考虑左右子树中的结点恭喜你,你又被陷进去出不来了哈哈,所以这里的限制条件是针对根来讲的:也就是根的起初的结束条件以及和题意的联系

3.2 二叉树叶子结点个数

递归三要素解决问题

  • 前提:树的结构:根、左子树、右子树
  • 函数功能:求二叉树叶子节点的个数
  • 限制条件:root=NULL的时候就不是叶子结点,根的左右孩子是空的时候但根不是空的时候就是叶子结点
  • 等式:在左右子树中找叶子节点,左右子树中的叶子结点之和也就是树的叶子结点
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);
}

代码分析

C语言实现二叉树链式结构的示例详解

3.3 第K层结点的个数

递归三要素解决问题

  • 前提:树的结构:根、左子树、右子树
  • 函数功能:求第K层结点的个数
  • 限制条件:root=NULL(起初的结束条件),根所处的是第一层所以K=1的时候返回1(题意结束条件)
  • 等式:在左右子树的第k-1层中的结点个数(因为第一层是根,所以第K-1层才是我们要求的第K层)
int BinaryTreeLevelKSize(BTNode* root, int k)
{
	if (root == NULL)
	{
		return 0;
	}

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

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

代码分析

C语言实现二叉树链式结构的示例详解

3.4 二叉树的深度

递归三要素解决问题

  • 前提:树的结构:根、左子树、右子树
  • 函数功能:求树的深度
  • 限制条件:根为NULL时结束
  • 等式:树的根是第一层,那么我们只用计算出左子树和右子树的哪个深度大就再加上1(根的深度)就是树的深度
int BinaryTreeDepth(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}

	int LeftTreeDepth = BinaryTreeDepth(root->left);
	int RightTreeDepth = BinaryTreeDepth(root->right);

	if (LeftTreeDepth > RightTreeDepth) {
		return LeftTreeDepth + 1;
	}
	else {
		return RightTreeDepth + 1;
	}
}

代码分析

C语言实现二叉树链式结构的示例详解

没进行优化的代码:

int BinaryTreeDepth(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	if (BinaryTreeDepth(root->left) > BinaryTreeDepth(root->right)) {
		return BinaryTreeDepth(root->left) + 1;
	}
	else {
		return BinaryTreeDepth(root->right) + 1;
	}
}

这个代码也是对的,但是时间复杂就要多了1倍,因为判断中用到递归了,找到了并没有记录深度,也就进入判断中的递归,再此递归一次,这样时间复杂度就增了1倍。

3.5 判断是不是树是不是完全二叉树

C语言实现二叉树链式结构的示例详解

思路:

完全二叉树的性质:前K-1层是满二叉树,最后一层是从左到右是连续的

思路:用层序遍历来解决,出上一层的同时带下一层的数据,知道遇到NULL的时候就要进行判断队列中是不是还有不为NULL的值,如果有就不是完全二叉树,没有则是

bool BinaryTreeComplete(BTNode* root)
{
	QLL queue;
	QLLInit(&queue);
	if (root != NULL)
	{
		QLLPush(&queue, root);
	}

	//拿到每层的
	while (QLLEmpty(&queue) != true)
	{
		BTNode* front = QLLFront(&queue);
		QLLPop(&queue);

		//当这层遇到NULL的时候进行判断
		if (front == NULL)
		{
			break;
		}
		else
		{
			QLLPush(&queue, front->left);
			QLLPush(&queue, front->right);
		}
	}
    
	//出到NULL进行检查
	//如果后面有非NULL就不是完全二叉树
	while (QLLEmpty(&queue) != true)
	{
		BTNode* front = QLLFront(&queue);
		QLLPop(&queue);
		//不为NULL说明最后一层不是连续的
		if (front != NULL)
		{
			QLLDestroy(&queue);
			return false;
		}
	}
	
	QLLDestroy(&queue);
	return true;
}

3.6 在二叉树中查找值为x的结点

递归三要素解决问题

前提:树的结构:根、左子树、右子树

函数功能: 在二叉树中查找值为x的结点

限制条件:root=NULL时结束,root->val=x时找到了就结束

等式:在根里面找,在左子树和右子树里面找

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

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

	BTNode* ret1 = BinaryTreeFind(root->left, x);
	if (ret1 != NULL)
	{
		return ret1;
	}

	BTNode* ret2 = BinaryTreeFind(root->right, x);
	if (ret2 != NULL)
	{
		return ret2;
	}

	return NULL;
}

代码分析

C语言实现二叉树链式结构的示例详解

错误列举

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

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

	return BinaryTreeFind(root->left, x) || BinaryTreeFind(root->right, x);
}

为什么逻辑上是对的,但是是错的?

最后的return的意思翻译过来就是在左子树里面找,找到了就返回,不进右子树,如果左子树中没找到就进右子树,找到了返回,如果都没找到就直接返回NULL;逻辑上是对的,但是呢,这里我们返回的是指针,指针的的关系不能用逻辑关系来表达,所以是错的

3.7 拿到每一层的数据

思路

也是围绕层序遍历来写:记录每一层的结点树来出队列就行了,这里也是层序遍历的知识是主要的,就不再进行讨论了

void EveryLayer(BTNode* root)
{
	QLL queue;
	int levelCount = 0;
	QLLInit(&queue);
	if (root != NULL) {
		QLLPush(&queue, root);
		//第一层就是一个数据
		levelCount = 1;
	}

	while (QLLEmpty(&queue) != true)
	{
		while (levelCount--)
		{
			BTNode* front = QLLFront(&queue);
			printf("%c ", front->data);
			QLLPop(&queue);
			if (front->left != NULL)
			{
				QLLPush(&queue, front->left);
			}
			if (front->right != NULL)
			{
				QLLPush(&queue, front->right);
			}
		}
		//下一层的个数
		levelCount = QLLSize(&queue);
		printf("\n");
	}
	
	QLLDestroy(&queue);
}

结合上述题就很容易看出实际上我们写代码来划分的话也是树的整体结构:根、左子树、右子树,时刻把握着树的整体结构,这个结构充分体现在等式中,同时也影响到了限制条件,限制条件中只用管根的结束条件以及形成条件,其他的不用管,这就是在代码中实现的过程,这里我就不在画图,觉得这个过程不能实现我说的对应的功能的时候你就画图去尝试理解一下,谢谢

4. 二叉树的创建和销毁

4.1 二叉树的创建

思路:

这里用到前序遍历来创建,也就是数组的元素按个放进根的数据域中

限制条件:就是当元素为#,代表的是二叉树中的NULL

BTNode* BinaryTreeCreate(BTDataType* a, int* pi)
{
	//形成条件
	if (a[(*pi)] == '#')
	{
		(*pi)++;
		return NULL;
	}

	BTNode* root = (BTNode*)malloc(sizeof(BTNode));
	if (root == NULL)
	{
		perror("BinaryTreeCreate: malloc is failed!\n");
		exit(-1);
	}
	//根
	root->data = a[(*pi)++];

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

	return root;
}
void Test2()
{
	char str[] = { 'A','B','D','#','#','E','#','H','#','#','C','F','#','#','G','#','#' };

	int i = 0;
	BTNode* root = BinaryTreeCreate(str, &i);
}

4.2 二叉树的销毁

//二级指针
void BinaryTreeDestory(BTNode** root)
{
	if ((*root) == NULL)
	{
		return;
	}
	BinaryTreeDestory(&((*root)->left));
	BinaryTreeDestory(&((*root)->right));
	free((*root));
	*root = NULL;
}
void FirstPointBinaryTreeDestory(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}

	FirstPointBinaryTreeDestory(root->left);
	FirstPointBinaryTreeDestory(root->right);
	free(root);
	//root = NULL;(没必要)
}//需要说明的是用这个函数调用后要对root置空

为什么采用后序遍历来销毁二叉树?

因为后序遍历最开始走到的就是左子树的最后一层,然后逐次向上销毁,并不会影响每个结点的指向;如果采用前序遍历呢?采用前序遍历上来就是free掉了根结点,就找到不到这个根结点的左右孩子了

以上就是C语言实现二叉树链式结构的示例详解的详细内容,更多关于C语言二叉树链式结构的资料请关注其它相关文章!

原文地址:https://blog.csdn.net/m0_46343224/article/details/128095808

查看更多关于【C/C++教程】的文章

展开全文
相关推荐
反对 0
举报 0
评论 0
图文资讯
热门推荐
优选好物
更多热点专题
更多推荐文章
Rust应用调用C语言动态库的操作方法
目录外部功能接口FFIUDP套接字的读超时Rust调用C语言动态库中的函数避免重复造***,使用Rust官方C语言库外部功能接口FFI虽然高级(脚本)编程语言的功能丰富,表达能力强,但对底层的一些特殊操作的支持并不完善,就需要以其他编程语言来实现。调用其他编程语

0评论2023-02-09585

Delphi中获取Unix时间戳及注意事项(c语言中time()是按格林威治时间计算的,比北京时间多了8小时)
uses DateUtils;DateTimeToUnix(Now) 可以转换到unix时间,但是注意的是,它得到的时间比c语言中time()得到的时间大了8*60*60这是因为Now是当前时区的时间,c语言中time()是按格林威治时间计算的,北京时间比格林威治时间多了8小时DateTimeToUnix(Now)-8*60*

0评论2023-02-09876

Unicode与UTF-8互转(c语言和lua语言) python utf-8转unicode
1. 基础1.1 ASCII码我们知道, 在计算机内部, 全部的信息终于都表示为一个二进制的字符串. 每个二进制位(bit)有0和1两种状态, 因此八个二进制位就能够组合出 256种状态, 这被称为一个字节(byte). 也就是说, 一个字节一共能够用来表示256种不同的状态, 每个状态

0评论2023-02-09468

R语言之merge详解 c语言merge函数代码
merge是R语言中用来合并数据框的函数merge函数的声明:?1234merge(x, y, by = intersect(names(x), names(y)),      by.x = by, by.y = by, all = FALSE, all.x = all, all.y = all,      sort = TRUE, suffixes = c(".x"

0评论2023-02-09489

R语言调用的C语言源代码查询 R语言 c
R语言使用时可以调用自己写的C代码,但是有些C函数是软件包自带的,怎么查询在使用软件包 kerfdr 时,涉及到一个函数y = .C("massdist", x = as.double(xtrunc), xmass = as.double(tau[trunc]/sum(tau[trunc])), nx = nx, xlo = as.double(lo), xhi = as.dou

0评论2023-02-09487

centos安装与配置R语言 centos配置c语言环境
Linux下安装R语言一、编译安装      由于采用编译安装,所以需要用到gcc编译环境,在编译前check文件时还会用到libXt-devel和readline-devel两个依赖,所以在编译R语言源码时先将这些工具和依赖包准备好。readline-devel 也可以不安装,不安装此包R语言编

0评论2023-02-09905

C语言利用链表实现学生成绩管理系统
链表是一种常见的基础数据结构,结构体指针在这里得到了充分的利用。链表可以动态的进行存储分配,也就是说,链表是一个功能极为强大的数组,他可以在节点中定义多种数据类型,还可以根据需要随意增添,删除,插入节点。链表都有一个头指针,一般以head来表示

0评论2023-02-09722

C语言通过三种方法实现属于你的通讯录
目录一、基础版本1.1 通讯录的个人信息(结构体来实现)1.2通讯录名单1.3人员初始化1.4菜单1.5主函数二、功能的实现2.1、增加人数2.2、删除人数2.3、查找2.4、展示2.5、排序(这里我是通过名字)三、通讯录进阶(设置动态存储)3.1通讯录从静态改为动态3.2通

0评论2023-02-09881

C++集体数据交换实现示例讲解 c语言两个数据交换
目录一、说明二、示例和代码一、说明到目前为止介绍的功能共享一对一的关系:即一个进程发送和一个进程接收。链接是通过标签建立的。本节介绍在多个进程中调用相同参数但执行不同操作的函数。对于一个进程,函数可能会发送数据,对于另一个进程,它可能会接收

0评论2023-02-09687

C语言中#if的使用 c语言中%c
目录#if定义#if使用#if的后面接的是表达式#if defined的使用ifdef的使用结尾#if定义#if和#endif是一组同时使用的,叫做条件编译指令。#if与#define、#include等指令一样是由预处理器这个强大的工具处理的,预处理器可以在编译前处理c程序。#if使用#if的后面接

0评论2023-02-09343

更多推荐