【数据结构】二叉树(C语言实现)

news2024/11/23 20:14:16

文章目录

  • 一、树的概念及结构
    • 1.树的概念
    • 2.树的相关概念名词
    • 3.树的表示
    • 4.树在实际中的运用
  • 二、二叉树概念及结构
    • 1.二叉树的概念
    • 2.特殊的二叉树
    • 3.二叉树的性质
    • 4.二叉树的存储结构
  • 三、二叉树链式结构的实现
    • 1.结构的定义
    • 2.构建二叉树
    • 3.二叉树前序遍历
    • 4.二叉树中序遍历
    • 5.二叉树后序遍历
    • 6.二叉树层次遍历
    • 7.二叉树节点个数
    • 8.二叉树叶子节点个数
    • 9.二叉树第k层节点个数
    • 10.二叉树的高度
    • 11.在二叉树中查找值为x的节点
    • 12.判断二叉树是否是完全二叉树
    • 13.销毁二叉树
  • 四、完整代码
    • 1.BTree.h
    • 2.BTree.c
    • 3.test.c
    • 4.Queue.h
    • 5.Queue.c

一、树的概念及结构

1.树的概念

树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。

在这里插入图片描述

在这里插入图片描述

树有一个特殊的结点,称为根结点,根节点没有前驱结点,除根节点外,其余结点被分成(M>0)个互不相交的集合T1、T2、……、Tm,其中每一个集合Ti(1<= i <= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继节点

因此,树是递归定义的。

在这里插入图片描述

【注意】:树形结构中,子树之间不能有交集,否则就不是树形结构

在这里插入图片描述

在上面前三个图中,节点直接形成了回路,所以不能称为数,应该称为图。

2.树的相关概念名词

在这里插入图片描述

节点的度:一个节点含有的子树的个数称为该节点的度; 如上图:A的为6

叶节点或终端节点:度为0的节点称为叶节点; 如上图:B、C、H、I…等节点为叶节点

非终端节点或分支节点:度不为0的节点; 如上图:D、E、F、G…等节点为分支节点

双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点

孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点

兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:B、C是兄弟节点

树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为6

节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推

树的高度或深度:树中节点的最大层次; 如上图:树的高度为4

堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:H、I互为兄弟节点

节点的祖先:从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先

子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙

森林:由m(m>0)棵互不相交的树的集合称为森林;

3.树的表示

树结构相对线性表就比较复杂了,要存储表示起来就比较麻烦了,既然保存值域,也要保存结点和结点之系,实际中树有很多种表示方式如:双亲表示法孩子表示法孩子双亲表示法以及孩子兄弟表示法等。我们这里就简单的了解其中最常用的孩子兄弟表示法

typedef int DataType;
struct Node
{
    struct Node* _firstChild1; // 第一个孩子结点
    struct Node* _pNextBrother; // 指向其下一个兄弟结点
    DataType _data; // 结点中的数据域
}

在这里插入图片描述

4.树在实际中的运用

树在我们实际生活中的应用之一就是用于表示文件系统的目录:

在这里插入图片描述

二、二叉树概念及结构

1.二叉树的概念

一棵二叉树是结点的一个有限集合,该集合:

1.或者为空

2.由一个根节点加上两棵别称为左子树和右子树的二叉树组成

在这里插入图片描述

从上图可以看出:

1.二叉树不存在度大于2的结点

2.二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树

【注意】对于任意的二叉树都是由以下几种情况复合而成的:

在这里插入图片描述

现实中的二叉树:

在这里插入图片描述

在这里插入图片描述

2.特殊的二叉树

1.满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是 2^k-1,则它就是满二叉树。

2.完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树

在这里插入图片描述

3.二叉树的性质

1.若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有 2^(i-1)个结点

2.若规定根节点的层数为1,则深度为h的二叉树的最大结点数是 2^h-1

3.对任何一棵二叉树, 如果度为0其叶结点个数为 n0,度为2的分支结点个数为n2 ,则有n0n2+1

解析:如图所示:

在这里插入图片描述

当二叉树只有一个节点的时候,叶子节点数为1,度为2的分支节点数为0,此时叶子节点数比度为2的节点数多1

当我们增加一个度为1的分支节点的时候,会消耗一个叶子节点,但同时又会产生一个新的叶子节点,所以增加度为1的分支节点时叶子节点的数量不变

当我们增加一个度为2的节点的时候,我们会同时产生一个叶子节点,所以叶子节点数始终比度为2的分支节点多1

4.若规定根节点的层数为1,具有n个结点的满二叉树的深度,h=log(n+1). (是log以2为底,n+1为对数)

高度为h的完全二叉树:2^(h-1) <= N <= 2^h-1

​ logN+1 <= h <=log(N+1)

5… 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对于序号为i的结点有:

1.若i>0,i位置节点的双亲序号:(i-1)/2;i=0,i为根节点编号,无双亲节点

2.若2i+1<n,左孩子序号:2i+1,2i+1>=n否则无左孩子

3.若2i+2<n,右孩子序号:2i+2,2i+2>=n否则无右孩子

概念和性质相关选择题

1.某二叉树共有 399 个结点,其中有 199 个度为 2 的结点,则该二叉树中的叶子结点数为( )

A 不存在这样的二叉树

B 200

C 198

D 199

【解析】B 根据结论:度为0的节点数比度为2的节点数多1可得n0=200

2.在具有 2n 个结点的完全二叉树中,叶子结点个数为( )

A n

B n+1

C n-1

D n/2

【解析】A 通过完全二叉树的概念我们知道,完全二叉树只存在三种节点,分别为度为0,度为1和度为2的节点,其中度为1的节点要么不存在要么只有一个;又根据度为0的节点数比度为2的节点数多1这个结论,我们可得n0+n1+n0-1=2n,我们知道n0*,n1都为整数,又2n为偶数,我们可知*n1=1;n0=n;

3.一棵完全二叉树的节点数位为531个,那么这棵树的高度为( )

A 11

B 10

C 8

D 12

【解析】B 我们知道 2^(h-1) <= N <= 2^h-1 ,所以h为10

4.二叉树的存储结构

二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构。

1.顺序存储

顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储,二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。

在这里插入图片描述

2.链式存储

二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。链式结构又分为二叉链和三叉链,当前我们学习中一般都是二叉链,红黑树等会用到三叉链。

在这里插入图片描述

在这里插入图片描述

typedef int BTDataType;
// 二叉链
struct BinaryTreeNode
{
    struct BinTreeNode* _pLeft; // 指向当前节点左孩子
    struct BinTreeNode* _pRight; // 指向当前节点右孩子
    BTDataType _data; // 当前节点值域
}
// 三叉链
struct BinaryTreeNode
{
    struct BinTreeNode* _pParent; // 指向当前节点的双亲
    struct BinTreeNode* _pLeft; // 指向当前节点左孩子
    struct BinTreeNode* _pRight; // 指向当前节点右孩子
    BTDataType _data; // 当前节点值域
}

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

1.结构的定义

// 符号和结构的定义
typedef int BTDataType;
typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;

2.构建二叉树

由于二叉树不能进行增加和删除操作,所以一般都是给定一个字符串或者一个数组,该字符串或者数组有我们创建二叉树所需要的所有节点,我们根据字符串或者数组的内容来构建二叉树

【注意】字符串或者数组中 # 表示空节点,即上一个节点没有左孩子或者右孩子

// 通过前序遍历的数组 1 2 3 # # 4 5 # # 6 ##构建二叉树
BTNode* BinaryTreeCreat(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 = BinaryTreeCreat(a, pi);
	root->right = BinaryTreeCreat(a, pi);

	return root;
}

3.二叉树前序遍历

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

在这里插入图片描述

按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历:

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

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

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

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

前序遍历递归图解

在这里插入图片描述

在这里插入图片描述

//二叉树先序遍历
void PreOrder(BTNode* root)
{
	//如果是空树则返回NULL
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}

	printf("%d ", root->data);   //访问根节点
	PreOrder(root->left);        //先序遍历左子树
	PreOrder(root->right);       //先序遍历右子树
}

4.二叉树中序遍历

//二叉树中序遍历
void InOrder(BTNode* root)
{
	//如果是空树则返回NULL
	if (root == NULL)
	{
		//printf("NULL ");
		return;
	}

	InOrder(root->left);              //中序遍历左子树
	printf("%d ", root->data);        //访问根节点
	//printf("%c ", root->data);
	InOrder(root->right);             //中序遍历右子树
}

5.二叉树后序遍历

/ 二叉树后序遍历
void PostOrder(BTNode* root)
{
	//如果是空树则返回NULL
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	PostOrder(root->left);        //后序遍历左子树
	PostOrder(root->right);       //后序遍历右子树
	printf("%d ", root->data);    //访问根节点
}

6.二叉树层次遍历

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

在这里插入图片描述

相比于其他三种遍历方式,层序遍历采用的是非递归的方式,其具体思路是:

利用一个队列来存储二叉树节点的地址,先让父节点入队列,然后父节点出队列,同时父节点的左右孩子会入队列,如果没有就不入,直到队列为空时结束,这样使得当一层节点全部出队列的时候,下一层的节点刚好全部入队列,当队列为空时,二叉树的节点就全部访问完毕了;

【注意】我们用队列来存储二叉树节点的地址,所以我们需要自己实现一个队列,也可以把我们之前实现写的队列Queue.h和Queue.c加入到当前工程中;此外,我们应该将二叉树节点的结构体需要定义在队列结构体的前面。

// 二叉树层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root)
		QueuePush(&q, root);

	while (!QueueEmpty(&q))
	{
		// 取出队头元素
		BTNode* front = QueueFront(&q);
		QueuePop(root);
		printf("%c ", front->data);

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

7.二叉树节点个数

我们采用子问题思路来解决,我们要计算二叉树节点的个数,那么分为左子树的节点个数和右节点的个数再加上根节点 二叉树节点数 = 左子树节点个数+右节点个数+根节点

/计算二叉树节点个数
int TreeSize(BTNode* root)
{
	if (root == NULL)
		return 0;
	// 左子树节点个数+右节点个数+根节点
	return TreeSize(root->left) + TreeSize(root->right) + 1;
}

8.二叉树叶子节点个数

和计算二叉树节点个数方法一样,但是叶子节点要求左孩子为空并且右孩子为空,所以叶子节点数等与左右叶子数之和

//计算二叉树叶子节点个数
int TreeLeafSize(BTNode* root)
{
	//空树返回0
	if (root == NULL)
	{
		return 0;
	}

	//左子树和右子树均为空则为叶子节点
	if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}

	//叶子节点数等与左右叶子数之和
	return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}

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

第k层节点的个数转换成求左右子树的k-1层的节点个数,当k为1 的时候,节点数为1

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

	//空树返回0
	if (root == NULL)
	{
		return 0;
	}

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

	//相对于根是第k层,则相对于根是子树的k-1层!!!
	//换成求子树第k-1层
	return TreeKLevel(root->left, k - 1) + TreeKLevel(root->right, k - 1);

}c

10.二叉树的高度

树的高度等于左子树的高度和右子树的高度的最大值+1

//计算二叉树深度
int TreeHeight(BTNode* root)
{
	//如果是空树返回0
	if (root == NULL)
	{
		return 0;
	}

	int lret = TreeHeight(root->left);  //递归计算左子树的深度记为lret
	int rret = TreeHeight(root->right); //递归计算右子树的深度记为rret
	/*if (lret > rret)
	{
		return lret + 1;
	}
	else
	{
		return rret + 1;
	}*/

	//二叉树的深度为lret和rret的较大者+1
	return lret > rret ? lret + 1 : rret + 1;
}

11.在二叉树中查找值为x的节点

我们先在左子树找没有找到再到右子树找,都没有找到则返回NULL;注意的是,上一个节点的返回值将作为下一个节点是否继续找的依据,所以我们要用一个指针保存左右子树查找的返回值,再进行判断。

// 在二叉树中查找值为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 TreeFind(root->right, x);

	// 都找不到则返回空
	retun NULL;
}

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

我们知道,高度为h的完全二叉树,前h-1层都是满二叉树,最后一层不一定是满二叉树,但是最后一层的节点必须是连续的,也就是说,当完全二叉树遇到空节点的时候,后面就不会在出现非空的节点,否则就不是完全二叉树

根据上面完全二叉树的性质,我们可以利用二叉树的层序遍历来判断二叉树是否是完全二叉树,基本思路为对二叉树进行层序遍历,不管节点是否为空都入队列,当队头的元素为空的时候,我们检查队列中的剩余数据是否都是空节点,如果含有非空节点则该树就不是完全二叉树。

// 判断二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root)
		QueuePush(&q, root);
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		if (front == NULL)
			break;
		QueuePop(&q);
		QueuePush(&q, root->left);
		QueuePush(&q, root->right);
	}

	// 遇到空以后,后面全是空,则是完全二叉树
	// 遇到空以后,后面存在非空,则不是完全二叉树
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		if (front != NULL)
		{
			QueueDestroy(&q);
			return false;
		}
		QueuePop(&q);
	}
	return true;
}

13.销毁二叉树

我们不能直接删除根节点,需要采用后续遍历的方式进行依次删除

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

	// 通过后续遍历来销毁节点
	BinaryTreeDestroy(root->left);
	BinaryTreeDestroy(root->right);

	// 此处置空不会影响外面,需要在外面进行置空
	free(root);
}

四、完整代码

1.BTree.h

#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>

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

//创建二叉树
BTNode* CreateTree();
//二叉树先序遍历
void PreOrder(BTNode* root);
//二叉树中序遍历
void InOrder(BTNode* root);
//二叉树后序遍历
void PostOrder(BTNode* root);
// 二叉树层序遍历
void BinaryTreeLevelOrder(BTNode* root);
//计算二叉树节点个数
int TreeSize(BTNode* root);
//计算二叉树深度
int TreeHeight(BTNode* root);
//第K层节点个数
int TreeKLevel(BTNode* root, int k);
//计算二叉树叶子节点个数
int TreeLeafSize(BTNode* root);
//返回x所在的节点
BTNode* TreeFind(BTNode* root, BTDataType x);
//创建二叉树
BTNode* BTreeCreate(char* a, int* pi);
// 判断二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* root);
// 销毁二叉树
void BinaryTreeDestroy(BTNode* root);

2.BTree.c

#include "BTree.h"

//创建二叉树
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);
	BTNode* n7 = (BTNode*)malloc(sizeof(BTNode));
	assert(n7);

	//链接关系
	n1->data = 1;
	n2->data = 2;
	n3->data = 3;
	n4->data = 4;
	n5->data = 5;
	n6->data = 6;
	n7->data = 7;

	n1->left = n2;
	n1->right = n4;
	n2->left = n3;
	n2->right = NULL;
	n4->left = n5;
	n4->right = n6;
	n3->left = NULL;
	n3->right = NULL;
	n5->left = NULL;
	n5->right = NULL;
	n6->left = NULL;
	n6->right = NULL;

	n3->right = n7;
	n7->left = NULL;
	n7->right = NULL;

	return n1;
}

//二叉树先序遍历
void PreOrder(BTNode* root)
{
	//如果是空树则返回NULL
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}

	printf("%d ", root->data);   //访问根节点
	PreOrder(root->left);        //先序遍历左子树
	PreOrder(root->right);       //先序遍历右子树
}

//二叉树中序遍历
void InOrder(BTNode* root)
{
	//如果是空树则返回NULL
	if (root == NULL)
	{
		//printf("NULL ");
		return;
	}

	InOrder(root->left);              //中序遍历左子树
	printf("%d ", root->data);        //访问根节点
	//printf("%c ", root->data);
	InOrder(root->right);             //中序遍历右子树
}

// 二叉树后序遍历
void PostOrder(BTNode* root)
{
	//如果是空树则返回NULL
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	PostOrder(root->left);        //后序遍历左子树
	PostOrder(root->right);       //后序遍历右子树
	printf("%d ", root->data);    //访问根节点
}

// 二叉树层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root)
		QueuePush(&q, root);

	while (!QueueEmpty(&q))
	{
		// 取出队头元素
		BTNode* front = QueueFront(&q);
		QueuePop(root);
		printf("%c ", front->data);

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

//int count = 0;//定义全局变量,导致两次调用返回值不一样

//计算二叉树节点个数
int TreeSize(BTNode* root)
{
	//知易行难(不行)遍历计数
	//static int count = 0;//static修饰count成为全局变量,导致两次返回值不一样!!!
	// 第一次打印7,则第二次打印14!!!
	//if (root == NULL)
	//	return count;
	//	
	//++count;
	//TreeSize(root->left);
	//TreeSize(root->right);
	//	
	//	return count;

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

	int lret = TreeSize(root->left);
	int rret = TreeSize(root->right);
	return TreeSize(root->left) + TreeSize(root->right) + 1;*/

	/*if (root == NULL)
	{
		return 0;
	}
	return TreeSize(root->left) + TreeSize(root->right) + 1;*/

	//二叉树的节点个数等于左子树的个数+右子树的深度+1
	return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}


//计算二叉树深度
int TreeHeight(BTNode* root)
{
	//如果是空树返回0
	if (root == NULL)
	{
		return 0;
	}

	int lret = TreeHeight(root->left);  //递归计算左子树的深度记为lret
	int rret = TreeHeight(root->right); //递归计算右子树的深度记为rret
	/*if (lret > rret)
	{
		return lret + 1;
	}
	else
	{
		return rret + 1;
	}*/

	//二叉树的深度为lret和rret的较大者+1
	return lret > rret ? lret + 1 : rret + 1;
}

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

	//空树返回0
	if (root == NULL)
	{
		return 0;
	}

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

	//相对于根是第k层,则相对于根是子树的k-1层!!!
	//换成求子树第k-1层
	return TreeKLevel(root->left, k - 1) + TreeKLevel(root->right, k - 1);

}

//计算二叉树叶子节点个数
int TreeLeafSize(BTNode* root)
{
	//空树返回0
	if (root == NULL)
	{
		return 0;
	}

	//左子树和右子树均为空则为叶子节点
	if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}

	//叶子节点数等与左右叶子数之和
	return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}

//返回x所在的节点
BTNode* TreeFind(BTNode* root, BTDataType x)
{
	//空树返回NULL
	if (root == NULL)
	{
		return NULL;
	}

	//根节点返回root的地址 
	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 TreeFind(root->right, x);

	return NULL;
}


BTNode* BTreeCreate(char* a, int* pi)
{
	//输入字符为‘#’return
	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 = BTreeCreate(a, pi);    //递归创建左子树
	root->right = BTreeCreate(a, pi);  //递归创建右子树

	return root;
}
// 判断二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root)
		QueuePush(&q, root);
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		if (front == NULL)
			break;
		QueuePop(&q);
		QueuePush(&q, root->left);
		QueuePush(&q, root->right);
	}

	// 遇到空以后,后面全是空,则是完全二叉树
	// 遇到空以后,后面存在非空,则不是完全二叉树
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		if (front != NULL)
		{
			QueueDestroy(&q);
			return false;
		}
		QueuePop(&q);
	}
	return true;
}

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

	// 通过后续遍历来销毁节点
	BinaryTreeDestroy(root->left);
	BinaryTreeDestroy(root->right);

	// 此处置空不会影响外面,需要在外面进行置空
	free(root);
}

3.test.c

#include "BTree.h"

int main()
{
	//创建二叉树
	BTNode* root = CreateTree();

	//先序遍历二叉树
	PreOrder(root);
	printf("\n");

	//计算二叉树节点个数
	printf("Tree size:%d\n", TreeSize(root));
	printf("Tree size:%d\n", TreeSize(root));

	//计算二叉树高度
	printf("Tree Height:%d\n", TreeHeight(root));

	//计算第k层节点个数
	printf("Tree KLevel:%d\n", TreeKLevel(root, 1));
	printf("Tree KLevel:%d\n", TreeKLevel(root, 2));
	printf("Tree KLevel:%d\n", TreeKLevel(root, 3));
	printf("Tree KLevel:%d\n", TreeKLevel(root, 4));

	//查找x所在的节点
	BTNode* ret = TreeFind(root, 7);
	printf("ret=%p\n", ret);
	printf("retbefore:%d\n", ret->data);

	//修改x所在的节点的值
	ret->data *= 10;
	printf("retafter:%d\n", ret->data);

	//计算二叉树叶子节点个数
	printf("Tree LeafSize:%d\n", TreeLeafSize(root));

	//测试创建二叉树
    //char str[100];       //创建数组
    //scanf("%s", str);    //输入字符
    //int i = 0;           //记录数组的下标

    递归i的值不会改变,所以传i的地址!!!
    //BTNode* root = BTreeCreate(str, &i);
    //InOrder(root);

    return 0;
}

4.Queue.h

#pragma once   //防止头文件被重复包含

//包含头文件
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

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

//结构和符号的定义
typedef int QDataType;  //数据类型重定义

//定义队列的一个节点
typedef struct QueueNode
{
	struct QueueNode* next;
	QDataType data;
}QNode;

typedef struct Queue
{
	QNode* head;   //记录队列的头
	QNode* tail;   //记录队列的尾
	int size;      //记录队列的长度
}Queue;


//函数的声明
//初始化队列
void QueueInit(Queue* pq);
//销毁队列
void QueueDestroy(Queue* pq);
//队尾入队列
void QueuePush(Queue* pq, QDataType x);
//对头出队列
void QueuePop(Queue* pq);
//获取对头元素
QDataType QueueFront(Queue* pq);
//获取队尾元素
QDataType QueueBack(Queue* pq);
//判断队列是否为空
bool QueueEmpty(Queue* pq);
//返回队列元素个数
int QueueSize(Queue* pq);

5.Queue.c

#include "Queue.h"

//初始化队列
void QueueInit(Queue* pq)
{
	assert(pq);

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

//销毁队列
void QueueDestroy(Queue* pq)
{
	assert(pq);

	//遍历删除
	QNode* cur = pq->head;
	while (cur)
	{
		QNode* del = cur;
		cur = cur->next;
		free(del);
	}

	pq->head = pq->tail = NULL;
}

//队尾入队列
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);

	//开辟新节点
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	else
	{
		//节点的数据复制为x,指针置为空
		newnode->data = x;
		newnode->next = NULL;
	}

	//空队列在队列头部
	if (pq->head == NULL)
	{
		pq->head = pq->tail = newnode;
	}
	else
	{
		//尾指针后移
		pq->tail->next = newnode;
		pq->tail = newnode;
	}

	pq->size++;
}

//对头出队列
void QueuePop(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));  //队列为空时不能出队列

	//只有一个元素的时候,出队列之后,头尾指针都置为空
	if (pq->head->next == NULL)
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	else
	{
		QNode* del = pq->head;
		pq->head = pq->head->next;

		free(del);
		del = NULL;
	}

	pq->size--;
}

//获取对头元素
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));

	return pq->head->data;
}

//获取队尾元素
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));

	return pq->tail->data;
}

//判断队列是否为空
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->head == NULL && pq->tail == NULL;
}

//返回队列元素个数
int QueueSize(Queue* pq)
{
	assert(pq);

	/*int count = 0;
	QNode* cur = pq->head;
	while (cur)
	{
		cur = cur->next;
		count++;
	}
	return count;*/


	return pq->size;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/365866.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

QWebEngineView 类 详细使用说明

文章目录 一、前言 二、详述 三、属性 四、公共函数 五、重新实现的公共函数 六、公共槽函数 七、信号 八、保护函数 九、重新实现的受保护函数 10、总结 一、前言 原文链接 QWebEngineView类提供了一个小部件&#xff0c;用于查看和编辑Web文档。 Header: #include < …

【Web安全-MSF记录篇章一】

文章目录前言msfvenom生成远控木马基本系统命令webcam 摄像头命令常用的信息收集脚本注册表设置nc后门开启 rdp&添加用户获取哈希mimikatz抓取密码前言 最近打站&#xff0c;可以感觉到之前的学的渗透知识忘记很多。。。。。多用多看多练&#xff0c;简单回顾一下 msfven…

2023年了,零基础小白转行IT学习Java还有前途吗?

“2023年了&#xff0c;转行IT学习Java是不是已经听过看过很多次了&#xff0c;Java从出现到现在有多少年了呢&#xff1f;掐指一算&#xff0c;Java是1995年由Sun公司推出的一款高级编程语言……距今已有28年了&#xff01; Sun公司都被收购了&#xff0c;莫不是Java也要垮台了…

Android实现Dribbble上动感的Gallery App Icon

先来看看原Dribbble上动感的Gallery App Icon效果图思路拆解一下&#xff0c;还是比较简单&#xff0c;需要绘制的有&#xff1a;圆形背景太阳(圆形)山(三角形)云朵(圆角矩形 三个圆)需要进行的动画&#xff1a;太阳 - 旋转动画山 - 上下平移动画云朵 - 左右平移动画不必绘制圆…

随想录二刷 (双指针法) leetcode 27 26 283 844

双指针法的原理 双指针法相对于暴力解法的优点有以下几点 暴力遍历的时间复杂度会比较高双指针法利用两个指针进行遍历完成双层循环所做的事情 双指针一般有两种方法 同向指针&#xff0c;双向指针 第一题 leetcode 27 移除元素 题目描述 题目分析 采用暴力遍历可以得出结…

vector的基本使用

目录 介绍&#xff1a; vector iterator 的使用 增删查改 增&#xff08;push_back insert&#xff09;&#xff1a; 删(pop_back erase)&#xff1a; swap&#xff1a; vector的容量和扩容&#xff1a; 排序&#xff08;sort&#xff09;&#xff1a; 介绍&#xff…

SpringBoot入门(二)

这里写目录标题一、SpringBoot整合Junit1.1 搭建SpringBoot工程1.2 引入starter-test起步依赖1.3 编写类1.4 测试二、SpringBoot整合mybatis2.1 搭建SpringBoot工程2.2 引入mybatis起步依赖&#xff0c;添加驱动2.3 编写DataSource和MyBatis相关配置2.4 定义表和实体类2.5 编写…

100%BIM学员的疑惑:不会CAD可以学Revit吗?

在新一轮科技创新和产业变革中&#xff0c;信息化与建筑业的融合发展已成为建筑业发展的方向&#xff0c;将对建筑业发展带来战略性和全局性的影响。 建筑业是传统产业&#xff0c;推动建筑业科技创新&#xff0c;加快推进信息化发展&#xff0c;激发创新活力&#xff0c;培育…

apk中代码执行adb指令实现

背景&#xff1a;想要在android apk中直接使用adb指令&#xff0c;从而不需要把手机通过数据线方式连接到电脑&#xff0c;在电脑端执行adb指令。 一、权限相关 想要在apk代码中执行adb命令&#xff0c;涉及到执行权限。 首先手机需要有root权限。其次就算手机已经root了&…

yolov5/6/7系列模型训练日志结果数据对比分析可视化

早在之前使用yolov3和yolov4这类项目的时候可视化分析大都是自己去做的&#xff0c;到了yolov5的时候&#xff0c;变成了一个工具包了&#xff0c;作者全部集成进去了&#xff0c;这里我们以一个具体的结果为例&#xff0c;如下&#xff1a;整个训练过程产生的指标等数据都会自…

11.3 基于Django4的可重用、用户注册和登录系统搭建(优化)

文章目录邮件注册发送邮件功能测试基本的邮件注册功能实现完成注册表单完成注册的业务逻辑密码加密功能实现邮件注册确认创建模型修改视图测试处理邮件确认请求修改登录规则测试邮件注册 根据官方文档进行&#xff1a;https://docs.djangoproject.com/zh-hans/4.1/topics/emai…

什么是智慧实验室?

智慧实验室是利用现代信息技术和先进设备将实验室实现智能化和智慧化的概念。通过将各种数据、信息和资源整合在一起&#xff0c;实现实验室设备的互联互通&#xff0c;数据的实时采集、传输、处理和分析&#xff0c;从而提高实验室的效率、精度和可靠性。一、智慧实验室包含多…

Java~对于代码块与内部类的理解

目录 代码块 普通代码块 构造代码块 静态代码块 内部类 成员内部类 普通内部类 静态内部类 局部内部类 代码块 使用“{}”定义的一段代码成为代码块&#xff0c;代码块分为普通代码块、构造代码块、匿名代码块、同步代码块。 普通代码块 定义在方法中的代码&#x…

【go语言之thrift协议一】

go语言之thrift协议thrift文件shared.thriftSharedStructSharedServiceSharedServiceProcessorSharedServiceGetStructArgsSharedServiceGetStructResulttutorial.thrift基本数据类型引入其他thrift文件自定义类型定义常量enum继承thrift 相对于grpc而言&#xff0c;可能用的不…

逆向-还原代码之max 再画堆栈图 (Interl 64)

// source code #include <stdio.h> void max(int * a, int * b) { if (*a < *b) *a *b; } int main() { int a 5, b 6; max(&a, &b); printf("a, b max %d\n", a); return 0; } // 再画堆栈图 下周一&#xff08;2.27…

JavaEE简单示例——MyBatis关联映射

简单介绍&#xff1a; 在我们之前的案例中&#xff0c;我们进行了简单查询&#xff0c;条件产村&#xff0c;动态SQL的条件查询&#xff0c;但是这些操作都是在一张表中进行的&#xff0c;而在我们之前学习MySQL中还有一个很重要的操作就是多表查询操作&#xff0c;也就是说通…

图像亮度调整

非线性方式 调整图像的方法有很多&#xff0c;最常用的方法就是对图像像素点的R、G、B三个分量同时进行增加&#xff08;减少&#xff09;某个值&#xff0c;达到调整亮度的目的。即改变图像的亮度&#xff0c;实际就是对像素点的各颜色分量值做一个平移。这种方法属于非线性的…

适用于产研团队协作工具有哪些?盘点6大类协同办公软件

团队协作工具在提高团队协作效率、质量和灵活性&#xff0c;降低成本等方面都有着不小的作用。而根据协作内容、团队等特点的不同&#xff0c;团队协作工具可以分为多种类型&#xff0c;常见的包括&#xff1a;即时通讯工具&#xff0c;用于实时交流和沟通&#xff0c;其中又可…

SpringBoot整合JPA+人大金仓(kingbase8)

陈老老老板&#x1f9b8;&#x1f468;‍&#x1f4bb;本文专栏&#xff1a;国产数据库-人大金仓&#xff08;kingbase8&#xff09;&#xff08;主要讲一些人大金仓数据库相关的内容&#xff09;&#x1f468;‍&#x1f4bb;本文简述&#xff1a;本文讲一下Jpa框架整合人大金…

Spring Cloud Nacos源码讲解(三)- Nacos客户端实例注册源码分析

Nacos客户端实例注册源码分析 实例客户端注册入口 流程图&#xff1a; 实际上我们在真实的生产环境中&#xff0c;我们要让某一个服务注册到Nacos中&#xff0c;我们首先要引入一个依赖&#xff1a; <dependency><groupId>com.alibaba.cloud</groupId><…