[数据结构初阶]二叉树

我们在前两篇博客中主要介绍了堆及其应用,针对的对象堆是完全二叉树,存储方式采用顺序结构存储的方式。

那么好的,这篇博客我们浅谈二叉树的链式存储,针对的对象是二叉树,并不局限于完全二叉树了!


我们先来回顾以下二叉树的定义:

一棵二叉树是结点的一个有限集合,该集合:
1. 或者为空(就是说空树也是二叉树)。
2. 不是空树:由一个根节点加上两棵别称为左子树和右子树的二叉树组成。 

说简单点呢,二叉树是一颗特殊的树,这颗树的度最大为2,就像是对这颗树的节点进行了计划生育,最多只能生两个节点宝宝。 

从图可以看出:

1. 二叉树不存在度大于2的结点。
2. 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树。

3.二叉树的子树也都是二叉树,既然是二叉树就可以为空树。

从概念中可以看出,二叉树定义是递归式的:二叉树被拆成根节点、左子树和右子树,子树又被拆成根节点、左子树和右子树……直到拆成空树停止。因此后序基本操作中基本都是按照该概念实现的。


讲二叉树的链式存储,鼠鼠我需要构建一颗链式二叉树,鼠鼠先用硬编码的方式构建一颗二叉树,真正的链式二叉树构建是用递归构建的,鼠鼠后面讲:

typedef char BTDataType;

typedef struct BTNode
{
	BTDataType data;
	struct BTNode* leftchild;
	struct BTNode* rightchild;
}BTNode;

//动态申请二叉树节点
BTNode* CreateBinaryTreeNode(BTDataType data)
{
	BTNode* tmp = (BTNode*)malloc(sizeof(BTNode));
	if (tmp == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	tmp->data = data;
	tmp->leftchild = NULL;
	tmp->rightchild = NULL;
	return tmp;
}

int main()
{
	BTNode* node1 = CreateBinaryTreeNode('A');
	BTNode* node2 = CreateBinaryTreeNode('B');
	BTNode* node3 = CreateBinaryTreeNode('C');
	BTNode* node4 = CreateBinaryTreeNode('D');
	BTNode* node5 = CreateBinaryTreeNode('E');
	BTNode* node6 = CreateBinaryTreeNode('F');
	BTNode* node7 = CreateBinaryTreeNode('G');
	BTNode* node8 = CreateBinaryTreeNode('H');
	BTNode* node9 = CreateBinaryTreeNode('I');
	node1->leftchild = node2;
	node1->rightchild = node6;
	node2->leftchild = node3;
	node3->leftchild = node4;
	node4->rightchild = node5;
	node6->leftchild = node7;
	node7->leftchild = node8;
	node8->rightchild = node9;
	return 0;
}

对于链式二叉树来说是由一个个节点构成的,我们定义节点如BTNode所示,很简单就不解释了。用也是很简单的逻辑我们写出了动态申请二叉树节点这个函数,在main函数上我们就可以调用该函数构建任意我们想要的链式二叉树,如代码所示我们构建的链式二叉树想象图为:


1.二叉树的遍历

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

1.1.前序遍历、中序遍历和后序遍历

二叉树的递归结构遍历有:前序/中序/后序的递归结构遍历:

1. 前序遍历(Preorder Traversal 亦称先序遍历):访问根结点的操作发生在遍历其左右子树之前。
2. 中序遍历(Inorder Traversal):访问根结点的操作发生在遍历其左右子树之中(间)。
3. 后序遍历(Postorder Traversal):访问根结点的操作发生在遍历其左右子树之后。

就拿上面我们构建的二叉树来说:

前序遍历依次访问的节点是:ABCD#E####FGH#I#### 。

前序遍历划分层次为:

中序遍历依次访问的节点是:#D#E#C#B#A#H#I#G#F#。

中序遍历划分层次为:

后序遍历依次访问的节点是:###ED#C#B#H##I#G#FA。

后序遍历划分层次为:

其实并不是很复杂,拿前序遍历来说,我们知道二叉树是递归式定义的,每棵二叉树都可以拆成根节点、左子树和右子树……这样子一直拆,知道拆到空树为止。只要保证每棵二叉树访问顺序都是根节点——左子树——右子树。

注:这里的#表示访问到了空节点。

那么我们用递归实现的代码如下,我们通过前序/中序/后序遍历访问节点用于打印节点数据,这样我们可以很好的证实我们访问的顺序是符合上面分析的:

// 二叉树前序遍历
void BinaryTreePrevOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("# ");
		return;
	}
	//根节点
	printf("%c ", root->data);
	//左子树
	BinaryTreePrevOrder(root->leftchild);
	//右子树
	BinaryTreePrevOrder(root->rightchild);
}

//二叉树中序遍历
void BinaryTreeInOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("# ");
		return;
	}
	//左子树
	BinaryTreeInOrder(root->leftchild);
	//根节点
	printf("%c ", root->data);
	//右子树
	BinaryTreeInOrder(root->rightchild);
}

// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("# ");
		return;
	}
	//左子树
	BinaryTreePostOrder(root->leftchild);
	//右子树
	BinaryTreePostOrder(root->rightchild);
	//根节点
	printf("%c ", root->data);
}

 完整代码如下,将下面三个文件放到一个工程下面运行即可出结果:

1.BinaryTree.h

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

typedef char BTDataType;

typedef struct BTNode
{
	BTDataType data;
	struct BTNode* leftchild;
	struct BTNode* rightchild;
}BTNode;

//动态申请二叉树节点
BTNode* CreateBinaryTreeNode(BTDataType data);

// 二叉树前序遍历
void BinaryTreePrevOrder(BTNode* root);

//二叉树中序遍历
void BinaryTreeInOrder(BTNode* root);

// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root);

2.BinaryTree.c

#define _CRT_SECURE_NO_WARNINGS

#include"BinaryTree.h"

//动态申请二叉树节点
BTNode* CreateBinaryTreeNode(BTDataType data)
{
	BTNode* tmp = (BTNode*)malloc(sizeof(BTNode));
	if (tmp == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	tmp->data = data;
	tmp->leftchild = NULL;
	tmp->rightchild = NULL;
	return tmp;
}

// 二叉树前序遍历
void BinaryTreePrevOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("# ");
		return;
	}
	//根节点
	printf("%c ", root->data);
	//左子树
	BinaryTreePrevOrder(root->leftchild);
	//右子树
	BinaryTreePrevOrder(root->rightchild);
}

//二叉树中序遍历
void BinaryTreeInOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("# ");
		return;
	}
	//左子树
	BinaryTreeInOrder(root->leftchild);
	//根节点
	printf("%c ", root->data);
	//右子树
	BinaryTreeInOrder(root->rightchild);
}

// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("# ");
		return;
	}
	//左子树
	BinaryTreePostOrder(root->leftchild);
	//右子树
	BinaryTreePostOrder(root->rightchild);
	//根节点
	printf("%c ", root->data);
}

3.test.c

#include"BinaryTree.h"

int main()
{
	BTNode* node1 = CreateBinaryTreeNode('A');
	BTNode* node2 = CreateBinaryTreeNode('B');
	BTNode* node3 = CreateBinaryTreeNode('C');
	BTNode* node4 = CreateBinaryTreeNode('D');
	BTNode* node5 = CreateBinaryTreeNode('E');
	BTNode* node6 = CreateBinaryTreeNode('F');
	BTNode* node7 = CreateBinaryTreeNode('G');
	BTNode* node8 = CreateBinaryTreeNode('H');
	BTNode* node9 = CreateBinaryTreeNode('I');
	node1->leftchild = node2;
	node1->rightchild = node6;
	node2->leftchild = node3;
	node3->leftchild = node4;
	node4->rightchild = node5;
	node6->leftchild = node7;
	node7->leftchild = node8;
	node8->rightchild = node9;

	// 二叉树前序遍历
	 BinaryTreePrevOrder(node1);
	 printf("\n");

	//二叉树中序遍历
	 BinaryTreeInOrder(node1);
	 printf("\n");

	// 二叉树后序遍历
	 BinaryTreePostOrder(node1);
	 printf("\n");

	return 0;
}

运行结果:

我们看跟分析的结果一模一样。不过一般访问到空树的时候我们是不对其进行操作的,因为空树没有存储数据,没有操作的必要,这里遇到空树的时候打印'#'是为了更好验证分析! 

 鼠鼠在这里顺便提一嘴,前序遍历是一种深度优先遍历(dfs)。

1.2.层序遍历

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

拿这棵二叉树来说,层序遍历访问节点的顺序是ABFCGDHEI。

问题来了,我们如何实现层序遍历? 

其实不难,这里我们不用递归实现,借用之前实现的队列即可。主要思想是 先将节点A的地址入队列,这样节点A地址就在对头了,再读取对头数据(即节点A地址)并将节点A地址出队列,通过读取的对头数据(即节点A地址)将A节点的左右子节点地址依次入队列(若为空指针就不入队列)。这样节点B的地址就在对头了,再读取对头数据(即节点B地址)并将节点B地址出队列,通过读取的对头数据(即节点B地址)将B节点的左右子节点地址依次入队列(若为空指针就不入队列)。这样节点F地址就在对头了,再……循环直到队列为空就停止这样就实现了层序遍历。

说通俗点就是A带B和F,B带C,F带G,C带D,G带H,D带E,H带I,当I出队列时队列就空了,那么就遍历完了!

层序遍历的代码如下,按照层序遍历访问节点打印出数据:

// 层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	QueuePush(&q, root);
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		printf("%c ", front->data);
		if (front->leftchild)
		{
			QueuePush(&q, front->leftchild);
		}
		if (front->rightchild)
		{
			QueuePush(&q, front->rightchild);
		}
	}
	QueueDestroy(&q);
}

我们用到了队列,只要将先前实现好的队列头文件和源文件复制粘贴到当前工程下,再包含队列头文件即可使用队列。当然因为我们在队列中存储的数据是节点的指针,所以头文件中的QDatatype要有所更改。完整代码如下,将下面五个文件放到一个工程下面就可以运行出结果:

1.queue.h

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

#include"BinaryTree.h"

typedef BTNode* QDatatype;

typedef struct QNode
{
	QDatatype _data;
	struct  QNode* _next;
}QNode;

typedef struct Queue
{
	int k;
	QNode* head;
	QNode* tail;
}Queue;

//初始化队列
void QueueInit(Queue* q);

//队尾入数据
void QueuePush(Queue* q, QDatatype data);

//对头出数据
void QueuePop(Queue* q);

//获取队列对头元素
QDatatype QueueFront(Queue* q);

//获取队列队尾元素
QDatatype QueueBack(Queue* q);

//获取队列中有效元素个数
int QueueSize(Queue* q);

//检测队列是否为空,如果为空返回非零结果,非空返回0
bool QueueEmpty(Queue* q);

//销毁队列
void QueueDestroy(Queue* q);

2.queue.c

#define _CRT_SECURE_NO_WARNINGS
#include"queue.h"

void QueueInit(Queue* q)
{
	assert(q);
	q->head = q->tail = NULL;
	q->k = 0;
}

void QueuePush(Queue* q, QDatatype data)
{
	assert(q);
	QNode* tmp = (QNode*)malloc(sizeof(QNode));
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}
	tmp->_data = data;
	tmp->_next = NULL;
	if (q->tail == NULL)
	{
		q->head = q->tail = tmp;
	}
	else
	{
		q->tail->_next = tmp;
		q->tail = tmp;
	}
	q->k++;
}

void QueuePop(Queue* q)
{
	assert(q);
	assert(q->k > 0);
	QNode* next = q->head->_next;
	free(q->head);
	q->head = next;
	if (q->head == NULL)
	{
		q->tail = NULL;
	}
	q->k--;
}

QDatatype QueueFront(Queue* q)
{
	assert(q);
	assert(q->k > 0);
	return q->head->_data;
}

QDatatype QueueBack(Queue* q)
{
	assert(q);
	assert(q->k > 0);
	return q->tail->_data;
}

int QueueSize(Queue* q)
{
	assert(q);
	return q->k;
}

bool QueueEmpty(Queue* q)
{
	assert(q);
	return q->tail == NULL;
}

void QueueDestroy(Queue* q)
{
	assert(q);
	QNode* tmp = q->head;
	while (tmp)
	{
		QNode* next = tmp->_next;
		free(tmp);
		tmp = next;
	}
	q->k = 0;
	q->head = q->tail = NULL;
}

3.BinaryTree.h

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

typedef char BTDataType;

typedef struct BTNode
{
	BTDataType data;
	struct BTNode* leftchild;
	struct BTNode* rightchild;
}BTNode;

//动态申请二叉树节点
BTNode* CreateBinaryTreeNode(BTDataType data);

// 层序遍历
void BinaryTreeLevelOrder(BTNode* root);

4.BinaryTree.c

#define _CRT_SECURE_NO_WARNINGS

#include"BinaryTree.h"
#include"queue.h"

//动态申请二叉树节点
BTNode* CreateBinaryTreeNode(BTDataType data)
{
	BTNode* tmp = (BTNode*)malloc(sizeof(BTNode));
	if (tmp == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	tmp->data = data;
	tmp->leftchild = NULL;
	tmp->rightchild = NULL;
	return tmp;
}

// 层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	QueuePush(&q, root);
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		printf("%c ", front->data);
		if (front->leftchild)
		{
			QueuePush(&q, front->leftchild);
		}
		if (front->rightchild)
		{
			QueuePush(&q, front->rightchild);
		}
	}
	QueueDestroy(&q);
}

5.test.c

#include"queue.h"
#include"BinaryTree.h"

int main()
{
	BTNode* node1 = CreateBinaryTreeNode('A');
	BTNode* node2 = CreateBinaryTreeNode('B');
	BTNode* node3 = CreateBinaryTreeNode('C');
	BTNode* node4 = CreateBinaryTreeNode('D');
	BTNode* node5 = CreateBinaryTreeNode('E');
	BTNode* node6 = CreateBinaryTreeNode('F');
	BTNode* node7 = CreateBinaryTreeNode('G');
	BTNode* node8 = CreateBinaryTreeNode('H');
	BTNode* node9 = CreateBinaryTreeNode('I');
	node1->leftchild = node2;
	node1->rightchild = node6;
	node2->leftchild = node3;
	node3->leftchild = node4;
	node4->rightchild = node5;
	node6->leftchild = node7;
	node7->leftchild = node8;
	node8->rightchild = node9;
    
    //层序遍历
	BinaryTreeLevelOrder(node1);
	return 0;
}

运行结果如下:

 结果确实如我们分析所料,一层一层遍历访问节点并打印出节点存储的数据。

我们看上面运行结果来说是将所有节点数据全部打印到一行上了,那我们如何将所有节点数据分层打印出来呢?就是说第一行打印A;第二行打印B、F ;第三行打印C、G;第四行打印D、H;第五行打印E、I。

这个本质也是层序遍历,自是要控制好打印换行的时机罢了,我们看看代码:

//分层的层序遍历
void _BinaryTreeLevelOrder(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	QueuePush(&q, root);
	int size = 1;
	while (!QueueEmpty(&q))
	{
		while (size--)
		{
			BTNode* front = QueueFront(&q);
			printf("%c ", front->data);
			QueuePop(&q);
			if (front->leftchild)
			{
				QueuePush(&q, front->leftchild);
			}
			if (front->rightchild)
			{
				QueuePush(&q, front->rightchild);
			}
		}
		printf("\n");
		size = QueueSize(&q);
	}
	QueueDestroy(&q);
}

这是其中一种办法,用一个size变量来记录每层的数据个数,那么每当size减到0就打印换行即可。 

 完整代码如下,将下面5个文件放到同一个工程下面运行即可得出结果:

1.queue.h

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

#include"BinaryTree.h"

typedef BTNode* QDatatype;

typedef struct QNode
{
	QDatatype _data;
	struct  QNode* _next;
}QNode;

typedef struct Queue
{
	int k;
	QNode* head;
	QNode* tail;
}Queue;

//初始化队列
void QueueInit(Queue* q);

//队尾入数据
void QueuePush(Queue* q, QDatatype data);

//对头出数据
void QueuePop(Queue* q);

//获取队列对头元素
QDatatype QueueFront(Queue* q);

//获取队列队尾元素
QDatatype QueueBack(Queue* q);

//获取队列中有效元素个数
int QueueSize(Queue* q);

//检测队列是否为空,如果为空返回非零结果,非空返回0
bool QueueEmpty(Queue* q);

//销毁队列
void QueueDestroy(Queue* q);

2.queue.c

#define _CRT_SECURE_NO_WARNINGS
#include"queue.h"

void QueueInit(Queue* q)
{
	assert(q);
	q->head = q->tail = NULL;
	q->k = 0;
}

void QueuePush(Queue* q, QDatatype data)
{
	assert(q);
	QNode* tmp = (QNode*)malloc(sizeof(QNode));
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}
	tmp->_data = data;
	tmp->_next = NULL;
	if (q->tail == NULL)
	{
		q->head = q->tail = tmp;
	}
	else
	{
		q->tail->_next = tmp;
		q->tail = tmp;
	}
	q->k++;
}

void QueuePop(Queue* q)
{
	assert(q);
	assert(q->k > 0);
	QNode* next = q->head->_next;
	free(q->head);
	q->head = next;
	if (q->head == NULL)
	{
		q->tail = NULL;
	}
	q->k--;
}

QDatatype QueueFront(Queue* q)
{
	assert(q);
	assert(q->k > 0);
	return q->head->_data;
}

QDatatype QueueBack(Queue* q)
{
	assert(q);
	assert(q->k > 0);
	return q->tail->_data;
}

int QueueSize(Queue* q)
{
	assert(q);
	return q->k;
}

bool QueueEmpty(Queue* q)
{
	assert(q);
	return q->tail == NULL;
}

void QueueDestroy(Queue* q)
{
	assert(q);
	QNode* tmp = q->head;
	while (tmp)
	{
		QNode* next = tmp->_next;
		free(tmp);
		tmp = next;
	}
	q->k = 0;
	q->head = q->tail = NULL;
}

3.BinaryTree.h

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

typedef char BTDataType;

typedef struct BTNode
{
	BTDataType data;
	struct BTNode* leftchild;
	struct BTNode* rightchild;
}BTNode;

//动态申请二叉树节点
BTNode* CreateBinaryTreeNode(BTDataType data);

//分层的层序遍历
void _BinaryTreeLevelOrder(BTNode* root);

4.BinaryTree.c

#define _CRT_SECURE_NO_WARNINGS

#include"BinaryTree.h"
#include"queue.h"

//动态申请二叉树节点
BTNode* CreateBinaryTreeNode(BTDataType data)
{
	BTNode* tmp = (BTNode*)malloc(sizeof(BTNode));
	if (tmp == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	tmp->data = data;
	tmp->leftchild = NULL;
	tmp->rightchild = NULL;
	return tmp;
}

//分层的层序遍历
void _BinaryTreeLevelOrder(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	QueuePush(&q, root);
	int size = 1;
	while (!QueueEmpty(&q))
	{
		while (size--)
		{
			BTNode* front = QueueFront(&q);
			printf("%c ", front->data);
			QueuePop(&q);
			if (front->leftchild)
			{
				QueuePush(&q, front->leftchild);
			}
			if (front->rightchild)
			{
				QueuePush(&q, front->rightchild);
			}
		}
		printf("\n");
		size = QueueSize(&q);
	}
	QueueDestroy(&q);
}

5.test.c

#include"BinaryTree.h"

int main()
{
	BTNode* node1 = CreateBinaryTreeNode('A');
	BTNode* node2 = CreateBinaryTreeNode('B');
	BTNode* node3 = CreateBinaryTreeNode('C');
	BTNode* node4 = CreateBinaryTreeNode('D');
	BTNode* node5 = CreateBinaryTreeNode('E');
	BTNode* node6 = CreateBinaryTreeNode('F');
	BTNode* node7 = CreateBinaryTreeNode('G');
	BTNode* node8 = CreateBinaryTreeNode('H');
	BTNode* node9 = CreateBinaryTreeNode('I');
	node1->leftchild = node2;
	node1->rightchild = node6;
	node2->leftchild = node3;
	node3->leftchild = node4;
	node4->rightchild = node5;
	node6->leftchild = node7;
	node7->leftchild = node8;
	node8->rightchild = node9;

	//分层的层序遍历
	 _BinaryTreeLevelOrder(node1);
	return 0;
}

运行结果是没问题的:

 鼠鼠在这里顺便提一嘴,层序遍历是一种广度优先遍历(bfs)。


 2.二叉树节点个数

拿这棵树来说,节点个数是9个:

那我们该如何求呢?

当然我们可以用层序遍历,每出一个数据就计数一次,但是鼠鼠这里用的是递归的写法,思想是:当传入空树(就是传入节点指针为空指针)就返回0;不为空树就返回左子树节点个数+右子树节点个数+1即可。

我们看代码来体会一下:

//二叉树节点个数
int BinaryTreeSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	return  BinaryTreeSize(root->leftchild) + BinaryTreeSize(root->rightchild) + 1;
}

 我们看看完整代码,3个文件放入同一个工程运行可出结果:

1.BinaryTree.h

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

typedef char BTDataType;

typedef struct BTNode
{
	BTDataType data;
	struct BTNode* leftchild;
	struct BTNode* rightchild;
}BTNode;

//动态申请二叉树节点
BTNode* CreateBinaryTreeNode(BTDataType data);

//二叉树节点个数
int BinaryTreeSize(BTNode* root);

2.BinaryTree.c

#define _CRT_SECURE_NO_WARNINGS

#include"BinaryTree.h"

//动态申请二叉树节点
BTNode* CreateBinaryTreeNode(BTDataType data)
{
	BTNode* tmp = (BTNode*)malloc(sizeof(BTNode));
	if (tmp == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	tmp->data = data;
	tmp->leftchild = NULL;
	tmp->rightchild = NULL;
	return tmp;
}

//二叉树节点个数
int BinaryTreeSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	return  BinaryTreeSize(root->leftchild) + BinaryTreeSize(root->rightchild) + 1;
}

3.test.c

#include"BinaryTree.h"

int main()
{
	BTNode* node1 = CreateBinaryTreeNode('A');
	BTNode* node2 = CreateBinaryTreeNode('B');
	BTNode* node3 = CreateBinaryTreeNode('C');
	BTNode* node4 = CreateBinaryTreeNode('D');
	BTNode* node5 = CreateBinaryTreeNode('E');
	BTNode* node6 = CreateBinaryTreeNode('F');
	BTNode* node7 = CreateBinaryTreeNode('G');
	BTNode* node8 = CreateBinaryTreeNode('H');
	BTNode* node9 = CreateBinaryTreeNode('I');
	node1->leftchild = node2;
	node1->rightchild = node6;
	node2->leftchild = node3;
	node3->leftchild = node4;
	node4->rightchild = node5;
	node6->leftchild = node7;
	node7->leftchild = node8;
	node8->rightchild = node9;

	//二叉树节点个数
	printf("%d\n", BinaryTreeSize(node1));
	return 0;
}

看看运行结果,拿捏了:


 3.二叉树叶子节点个数

还是用这棵树来说,这棵树的叶子节点是节点E、I,个数是2。

递归的方法很简单,如果传入空树(就是传入的节点指针是空指针)就返回0;如果传入的树没有左右子节点那么返回1;返回左子树叶子节点个数+右子树叶子节点个数即可。基于这个思想,代码如下:

//二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	if (root->leftchild == NULL && root->rightchild == NULL)
	{
		return 1;
	}
	return BinaryTreeLeafSize(root->leftchild) + BinaryTreeLeafSize(root->rightchild);
}

完整代码是三个文件,将三个文件放入同一个工程下面运行可得结果:

1.BinaryTree.h

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

typedef char BTDataType;

typedef struct BTNode
{
	BTDataType data;
	struct BTNode* leftchild;
	struct BTNode* rightchild;
}BTNode;

//动态申请二叉树节点
BTNode* CreateBinaryTreeNode(BTDataType data);

//二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root);

2.BinaryTree.c

#define _CRT_SECURE_NO_WARNINGS

#include"BinaryTree.h"

//动态申请二叉树节点
BTNode* CreateBinaryTreeNode(BTDataType data)
{
	BTNode* tmp = (BTNode*)malloc(sizeof(BTNode));
	if (tmp == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	tmp->data = data;
	tmp->leftchild = NULL;
	tmp->rightchild = NULL;
	return tmp;
}

//二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	if (root->leftchild == NULL && root->rightchild == NULL)
	{
		return 1;
	}
	return BinaryTreeLeafSize(root->leftchild) + BinaryTreeLeafSize(root->rightchild);
}

3.test.c

#include"BinaryTree.h"

int main()
{
	BTNode* node1 = CreateBinaryTreeNode('A');
	BTNode* node2 = CreateBinaryTreeNode('B');
	BTNode* node3 = CreateBinaryTreeNode('C');
	BTNode* node4 = CreateBinaryTreeNode('D');
	BTNode* node5 = CreateBinaryTreeNode('E');
	BTNode* node6 = CreateBinaryTreeNode('F');
	BTNode* node7 = CreateBinaryTreeNode('G');
	BTNode* node8 = CreateBinaryTreeNode('H');
	BTNode* node9 = CreateBinaryTreeNode('I');
	node1->leftchild = node2;
	node1->rightchild = node6;
	node2->leftchild = node3;
	node3->leftchild = node4;
	node4->rightchild = node5;
	node6->leftchild = node7;
	node7->leftchild = node8;
	node8->rightchild = node9;

	//二叉树叶子节点个数
	printf("%d\n", BinaryTreeLeafSize(node1));
	return 0;
}

运行结果也是轻松拿捏啊:


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

同样的,我们拿这棵树来说,第1层有1个节点,其他层都有2个节点;

拿以B节点为根节点的树来说,全部层都只有1个节点。

 递归的思想也很简单:如果传入的是空树(也就是传入的节点地址为空指针),我们返回0;如果不为空树我们分两种情况:第一种找第1层节点个数的话我们返回1(第1层只有一个根节点必然节点数为1),第二种找第K层节点个数的话相当于找左子树第K-1层节点个数+右子树第K-1层节点个数。好捏,看代码:

// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{
	if (root == NULL)
	{
		return 0;
	}
	if (k == 1)
	{
		return 1;
	}
	return BinaryTreeLevelKSize(root->leftchild, k - 1) + BinaryTreeLevelKSize(root->rightchild, k - 1);
}

那么完整代码如下,放入同一个工程中哦:

1.BinaryTree.h

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

typedef char BTDataType;

typedef struct BTNode
{
	BTDataType data;
	struct BTNode* leftchild;
	struct BTNode* rightchild;
}BTNode;

//动态申请二叉树节点
BTNode* CreateBinaryTreeNode(BTDataType data);

// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k);

2.BinaryTree.c

#define _CRT_SECURE_NO_WARNINGS

#include"BinaryTree.h"

//动态申请二叉树节点
BTNode* CreateBinaryTreeNode(BTDataType data)
{
	BTNode* tmp = (BTNode*)malloc(sizeof(BTNode));
	if (tmp == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	tmp->data = data;
	tmp->leftchild = NULL;
	tmp->rightchild = NULL;
	return tmp;
}

// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{
	if (root == NULL)
	{
		return 0;
	}
	if (k == 1)
	{
		return 1;
	}
	return BinaryTreeLevelKSize(root->leftchild, k - 1) + BinaryTreeLevelKSize(root->rightchild, k - 1);
}

3.test.c

#include"BinaryTree.h"

int main()
{
	BTNode* node1 = CreateBinaryTreeNode('A');
	BTNode* node2 = CreateBinaryTreeNode('B');
	BTNode* node3 = CreateBinaryTreeNode('C');
	BTNode* node4 = CreateBinaryTreeNode('D');
	BTNode* node5 = CreateBinaryTreeNode('E');
	BTNode* node6 = CreateBinaryTreeNode('F');
	BTNode* node7 = CreateBinaryTreeNode('G');
	BTNode* node8 = CreateBinaryTreeNode('H');
	BTNode* node9 = CreateBinaryTreeNode('I');
	node1->leftchild = node2;
	node1->rightchild = node6;
	node2->leftchild = node3;
	node3->leftchild = node4;
	node4->rightchild = node5;
	node6->leftchild = node7;
	node7->leftchild = node8;
	node8->rightchild = node9;

	// 二叉树第k层节点个数
	printf("%d %d\n", BinaryTreeLevelKSize(node1, 1), BinaryTreeLevelKSize(node1, 3));
	printf("%d\n", BinaryTreeLevelKSize(node2, 3));
	return 0;
}

看看运行结果:


5.二叉树高度

还是拿这颗二叉树来说,高度为5。

递归实现来说思想就是如果传入的是空树,我们返回0,不为空树的话我们返回左右子树高度中较大者+1(+1是为了加上根节点那层)。

//二叉树高度
int BinaryTreeHeight(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	return (int)fmax(BinaryTreeHeight(root->leftchild), BinaryTreeHeight(root->rightchild)) + 1;
}

完整代码看一看,放入同一个工程可看结果哦:

1.BinaryTree.h

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

typedef char BTDataType;

typedef struct BTNode
{
	BTDataType data;
	struct BTNode* leftchild;
	struct BTNode* rightchild;
}BTNode;

//动态申请二叉树节点
BTNode* CreateBinaryTreeNode(BTDataType data);

//二叉树高度
int BinaryTreeHeight(BTNode* root);

这里要注意需要包含头文件<math.h>,因为我们用到了fmax这个函数。

2.BinaryTree.c

#define _CRT_SECURE_NO_WARNINGS

#include"BinaryTree.h"

//动态申请二叉树节点
BTNode* CreateBinaryTreeNode(BTDataType data)
{
	BTNode* tmp = (BTNode*)malloc(sizeof(BTNode));
	if (tmp == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	tmp->data = data;
	tmp->leftchild = NULL;
	tmp->rightchild = NULL;
	return tmp;
}

//二叉树高度
int BinaryTreeHeight(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	return (int)fmax(BinaryTreeHeight(root->leftchild), BinaryTreeHeight(root->rightchild)) + 1;
}

3.test.c

#include"BinaryTree.h"

int main()
{
	BTNode* node1 = CreateBinaryTreeNode('A');
	BTNode* node2 = CreateBinaryTreeNode('B');
	BTNode* node3 = CreateBinaryTreeNode('C');
	BTNode* node4 = CreateBinaryTreeNode('D');
	BTNode* node5 = CreateBinaryTreeNode('E');
	BTNode* node6 = CreateBinaryTreeNode('F');
	BTNode* node7 = CreateBinaryTreeNode('G');
	BTNode* node8 = CreateBinaryTreeNode('H');
	BTNode* node9 = CreateBinaryTreeNode('I');
	node1->leftchild = node2;
	node1->rightchild = node6;
	node2->leftchild = node3;
	node3->leftchild = node4;
	node4->rightchild = node5;
	node6->leftchild = node7;
	node7->leftchild = node8;
	node8->rightchild = node9;

	// 二叉树高度
	printf("%d\n", BinaryTreeHeight(node1));
	return 0;
}

 看好了,运行结果我只展示一次:


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

找值为x的节点很简单,递归写法来说就是:如果传入空树,返回空指针:如果根节点就是值为x的节点,我们返回这个节点的地址;如果根节点的值不是x,我们在左子树找,找到返回,找不到就去右子树找,找到返回,找不到就返回空指针。

// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
	{
		return NULL;
	}
	if (root->data == x)
	{
		return root;
	}
	BTNode* find1 = BinaryTreeFind(root->leftchild, x);
	if (find1)
	{
		return find1;
	}
	BTNode* find2 = BinaryTreeFind(root->rightchild, x);
	if (find2)
	{
		return find2;
	}
	return NULL;
}

我们还是用这颗树来试试:

 完整代码如下:

1.BinaryTree.h

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

typedef char BTDataType;

typedef struct BTNode
{
	BTDataType data;
	struct BTNode* leftchild;
	struct BTNode* rightchild;
}BTNode;

//动态申请二叉树节点
BTNode* CreateBinaryTreeNode(BTDataType data);

// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);

2.BinaryTree.c

#define _CRT_SECURE_NO_WARNINGS

#include"BinaryTree.h"

//动态申请二叉树节点
BTNode* CreateBinaryTreeNode(BTDataType data)
{
	BTNode* tmp = (BTNode*)malloc(sizeof(BTNode));
	if (tmp == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	tmp->data = data;
	tmp->leftchild = NULL;
	tmp->rightchild = NULL;
	return tmp;
}

// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
	{
		return NULL;
	}
	if (root->data == x)
	{
		return root;
	}
	BTNode* find1 = BinaryTreeFind(root->leftchild, x);
	if (find1)
	{
		return find1;
	}
	BTNode* find2 = BinaryTreeFind(root->rightchild, x);
	if (find2)
	{
		return find2;
	}
	return NULL;
}

3.test.c

#include"BinaryTree.h"

int main()
{
	BTNode* node1 = CreateBinaryTreeNode('A');
	BTNode* node2 = CreateBinaryTreeNode('B');
	BTNode* node3 = CreateBinaryTreeNode('C');
	BTNode* node4 = CreateBinaryTreeNode('D');
	BTNode* node5 = CreateBinaryTreeNode('E');
	BTNode* node6 = CreateBinaryTreeNode('F');
	BTNode* node7 = CreateBinaryTreeNode('G');
	BTNode* node8 = CreateBinaryTreeNode('H');
	BTNode* node9 = CreateBinaryTreeNode('I');
	node1->leftchild = node2;
	node1->rightchild = node6;
	node2->leftchild = node3;
	node3->leftchild = node4;
	node4->rightchild = node5;
	node6->leftchild = node7;
	node7->leftchild = node8;
	node8->rightchild = node9;

	// 二叉树查找值为x的节点
	if (BinaryTreeFind(node1, 'H'))
	{
		printf("%c\n", BinaryTreeFind(node1, 'H')->data);

	}
	else
	{
		printf("找不到\n");
	}
	return 0;
}

 运行结果也是预料之中:


 7.通过前序遍历的数组构建二叉树, 叶节点子节点用'#'表示,如"ABD##E#H##CF##G##"

好的呀,我们前面讲过链式二叉树一般不用硬编码的方式构建,一般用递归构建,那么鼠鼠现在就来浅浅介绍一下!

这里的意思就是写一个用递归实现的函数,该函数能实现输入一组二叉树前序遍历得到的数组就能将这棵二叉树构建出来并返回二叉树指针。

用递归实现的思想就是遍历数组元素,若为‘#’就返回空指针;若不为'#'就动态申请一个节点的空间并将该元素存入申请的空间当中,这个空间就是根节点,接着构建左子树和右子树就行。

/*通过前序遍历的数组构建二叉树,
叶节点子节点用'#'表示,如"ABD##E#H##CF##G##"*/
BTNode* BinaryTreeCreate(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->leftchild = BinaryTreeCreate(a, pi);
	root->rightchild = BinaryTreeCreate(a, pi);
	return root;
}

为了印证我们确实是构建出来了这棵树,鼠鼠构建出来后用前序遍历依次访问节点并打印出来,那么完整代码如下三个文件:

1.BinaryTree.h

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

typedef char BTDataType;

typedef struct BTNode
{
	BTDataType data;
	struct BTNode* leftchild;
	struct BTNode* rightchild;
}BTNode;

/*通过前序遍历的数组构建二叉树,
叶节点子节点用'#'表示,如"ABD##E#H##CF##G##"*/
BTNode* BinaryTreeCreate(BTDataType* a, int* pi);

// 二叉树前序遍历
void BinaryTreePrevOrder(BTNode* root);

 2.BinaryTree.c

#define _CRT_SECURE_NO_WARNINGS

#include"BinaryTree.h"


/*通过前序遍历的数组构建二叉树,
叶节点子节点用'#'表示,如"ABD##E#H##CF##G##"*/
BTNode* BinaryTreeCreate(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->leftchild = BinaryTreeCreate(a, pi);
	root->rightchild = BinaryTreeCreate(a, pi);
	return root;
}

// 二叉树前序遍历
void BinaryTreePrevOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("# ");
		return;
	}
	//根节点
	printf("%c ", root->data);
	//左子树
	BinaryTreePrevOrder(root->leftchild);
	//右子树
	BinaryTreePrevOrder(root->rightchild);
}

3.test.c

#include"BinaryTree.h"

int main()
{
	/*通过前序遍历的数组构建二叉树,
叶节点子节点用'#'表示,如"ABD##E#H##CF##G##"*/
	char a[] = "ABD##E#H##CF##G##";
	int i = 0;
	BTNode* root = BinaryTreeCreate(a, &i);
	BinaryTreePrevOrder(root);
	return 0;
}

 运行结果表明确实是将这棵二叉树构建出来了:

那么这棵二叉树的想象图我给读者老爷画一画呀:


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

我们判断二叉树是否是完全二叉树可以有很多方法,鼠鼠这里利用到了层序遍历的思想,但不同的是当左孩子节点或右孩子节点地址为空指针也一样入队列,当出队列遇到空指针就停止。此时若队列中还有非空指针就不是完全二叉树,反之就是完全二叉树。

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

 我们拿上面递归构建的二叉树来试试这个代码,可以看出这棵二叉树不是完全二叉树:

那么试验这棵二叉树所需5个文件如下,有兴趣的老爷可以试试:

1.queue.h

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

#include"BinaryTree.h"

typedef BTNode* QDatatype;

typedef struct QNode
{
	QDatatype _data;
	struct  QNode* _next;
}QNode;

typedef struct Queue
{
	int k;
	QNode* head;
	QNode* tail;
}Queue;

//初始化队列
void QueueInit(Queue* q);

//队尾入数据
void QueuePush(Queue* q, QDatatype data);

//对头出数据
void QueuePop(Queue* q);

//获取队列对头元素
QDatatype QueueFront(Queue* q);

//获取队列队尾元素
QDatatype QueueBack(Queue* q);

//获取队列中有效元素个数
int QueueSize(Queue* q);

//检测队列是否为空,如果为空返回非零结果,非空返回0
bool QueueEmpty(Queue* q);

//销毁队列
void QueueDestroy(Queue* q);

2.queue.c

#define _CRT_SECURE_NO_WARNINGS
#include"queue.h"

void QueueInit(Queue* q)
{
	assert(q);
	q->head = q->tail = NULL;
	q->k = 0;
}

void QueuePush(Queue* q, QDatatype data)
{
	assert(q);
	QNode* tmp = (QNode*)malloc(sizeof(QNode));
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}
	tmp->_data = data;
	tmp->_next = NULL;
	if (q->tail == NULL)
	{
		q->head = q->tail = tmp;
	}
	else
	{
		q->tail->_next = tmp;
		q->tail = tmp;
	}
	q->k++;
}

void QueuePop(Queue* q)
{
	assert(q);
	assert(q->k > 0);
	QNode* next = q->head->_next;
	free(q->head);
	q->head = next;
	if (q->head == NULL)
	{
		q->tail = NULL;
	}
	q->k--;
}

QDatatype QueueFront(Queue* q)
{
	assert(q);
	assert(q->k > 0);
	return q->head->_data;
}

QDatatype QueueBack(Queue* q)
{
	assert(q);
	assert(q->k > 0);
	return q->tail->_data;
}

int QueueSize(Queue* q)
{
	assert(q);
	return q->k;
}

bool QueueEmpty(Queue* q)
{
	assert(q);
	return q->tail == NULL;
}

void QueueDestroy(Queue* q)
{
	assert(q);
	QNode* tmp = q->head;
	while (tmp)
	{
		QNode* next = tmp->_next;
		free(tmp);
		tmp = next;
	}
	q->k = 0;
	q->head = q->tail = NULL;
}

3.BinaryTree.h

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

typedef char BTDataType;

typedef struct BTNode
{
	BTDataType data;
	struct BTNode* leftchild;
	struct BTNode* rightchild;
}BTNode;

/*通过前序遍历的数组构建二叉树,
叶节点子节点用'#'表示,如"ABD##E#H##CF##G##"*/
BTNode* BinaryTreeCreate(BTDataType* a, int* pi);

// 判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root);

4.BinaryTree.c

#define _CRT_SECURE_NO_WARNINGS

#include"BinaryTree.h"
#include"queue.h"


/*通过前序遍历的数组构建二叉树,
叶节点子节点用'#'表示,如"ABD##E#H##CF##G##"*/
BTNode* BinaryTreeCreate(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->leftchild = BinaryTreeCreate(a, pi);
	root->rightchild = BinaryTreeCreate(a, pi);
	return root;
}

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

5.test.c

#include"BinaryTree.h"
#include"queue.h"

int main()
{
	/*通过前序遍历的数组构建二叉树,
叶节点子节点用'#'表示,如"ABD##E#H##CF##G##"*/
	char a[] = "ABD##E#H##CF##G##";
	int i = 0;
	BTNode* root = BinaryTreeCreate(a, &i);
	if (BinaryTreeComplete(root))
	{
		printf("是完全二叉树\n");
	}
	else
	{
		printf("不是完全二叉树\n");
	}
	return 0;
}

 运行结果确实如我们所料:

当我们换一棵完全二叉树来测试时结果也没问题,代码只要更改main函数的数组a,将数组a改成完全二叉树前序遍历的数组即可,我们来看看:

 没问题的!


9.二叉树销毁

二叉树销毁也用递归来完成,最好的办法时采用后续,因为根节点最后销毁的话方便我们找到左右子树,那么思想就是:先销毁左子树,再销毁右子树,最后销毁根节点即可。

//二叉树销毁
void BinaryTreeDestroy(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	//左子树
	BinaryTreeDestroy(root->leftchild);
	//右子树
	BinaryTreeDestroy(root->rightchild);
	//根节点
	free(root);
}

这个我们就不测试了! 


 10.二叉树的性质

最后鼠鼠介绍一个二叉树的性质,挺重要的:对任何一棵二叉树, 如果度为0其叶结点个数为n, 度为2的分支结点个数为k,则有n=k+1。

比如这道题就用到了这个性质:

3.在具有 2n 个结点的完全二叉树中,叶子结点个数为( )
A n
B n+1
C n-1
D n/2
答案是:A

 因为是二叉树,那么二叉树节点的度只可能为0、1和2。那么设度为0的节点(即叶子节点)个数为X,设度为1的节点个数为Y,设度为2的节点个数为Z,那么X+Y+Z=X+Y+(X-1)=2X+Y-1=2n。

又因为是完全二叉树,那么度为1的节点个数要么是0个,要么是1个。当Y=0时,不可能满足2Z+Y+1=2n,因为2n时偶数;所有Y肯定为1,那么代入2X+1-1=2n即可得出X=n。


感谢阅读,欢迎斧正!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PlayerSettings.WebGL.emscriptenArgs设置无效的问题

1&#xff09;PlayerSettings.WebGL.emscriptenArgs设置无效的问题 2&#xff09;多个小资源包合并为大资源包的疑问 3&#xff09;AssetBundle在移动设备上丢失 4&#xff09;Unity云渲染插件RenderStreaming&#xff0c;如何实现多用户分别有独立的操作 这是第381篇UWA技术知…

MySOL之旅--------MySQL数据库基础( 3 )

本篇碎碎念:要相信啊,胜利就在前方,要是因为一点小事就停滞不前,可能你也不适合获取胜利,成功的路上会伴有泥石,但是走到最后,你会发现身上的泥泞皆是荣耀的勋章! 今日份励志文案: 凡是发生皆有利于我 目录 查询(select) 1.全列查询 2.指定列查询 3.查询字段为表达式 ​编…

PVE系统的安装

一.PVE系统的安装 前置准备环境:windows电脑已安装Oracle VM VirtualBox,电脑支持虚拟化,且已经开启,按住ctrl+shift+ESC打开任务管理器查看是否开启,如果被禁用,可进入BIOS开启虚拟化,重启电脑后再进行后续操作。本步骤选用windows10安装VirtualBox,版本为7.0.8。 …

被拒绝的职场空窗期,到底该怎么办?

打工人的心头刺 最近&#xff0c;一则新闻在网上炸开了锅&#xff1a;一位求职者因职场空窗期超过三个月&#xff0c;竟被无情拒绝应聘。消息一出&#xff0c;瞬间引起了广大职场人的共鸣。在这个快节奏的时代&#xff0c;我们似乎被一种无形的力量推着&#xff0c;不敢休息&am…

高性能代码如何编写?

引言&#xff1a; 性能优化一直是一个至关重要的议题。随着应用程序规模的不断增长和用户对性能的不断提升的要求&#xff0c;开发人员需要更加关注如何编写高性能的代码&#xff0c;以确保应用程序能够在各种情况下都能保持稳定和高效。编写高性能代码需要从多个方面入手&…

编译Nginx配置QUIC/HTTP3.0

1. 安装BoringSSL sudo apt update sudo apt install -y build-essential ca-certificates zlib1g-dev libpcre3 \ libpcre3-dev tar unzip libssl-dev wget curl git cmake ninja-build mercurial \ libunwind-dev pkg-configgit clone --depth1 https://github.com/google/b…

耐受强酸碱PFA试剂瓶高纯实验级进口聚四氟乙烯材质取样瓶

PFA取样瓶作为实验室中常备器皿耗材之一&#xff0c;主要用来盛放、储存和运输样品&#xff0c;根据使用条件不同&#xff0c;也可叫特氟龙试剂瓶、样品瓶、储样瓶、广口瓶、进样瓶等。广泛应用于半导体、新材料、多晶硅、硅材、微电子等行业。近年来随着新兴行业的快速发展&am…

软考 — 系统架构设计师 - 嵌入式真题

问题1&#xff1a; 可靠度表示系统在规定条件下&#xff0c;规定的时间内不发生失效的概率。 失效率表示系统运行到此时从未出现失效的情况下&#xff0c;单位时间内系统出现失效的概率 问题 2&#xff1a; 动态冗余又称为主动冗余&#xff0c;通过故障检测&#xff0c;故障定…

麒麟系统(kylin)安装ssh后,无法上传文件

1.赋予文件夹权限 chmod 777 filename 2.修改ssh配置文件 vi /etc/ssh/sshd_config 将Subsystem sftp /xxxxx 改为Subsystem sftp internal-sftp 重启服务 sudo service sshd restart 断开ssh连接&#xff0c;重新连接&#xff0c;即可正常上传文件

2012年认证杯SPSSPRO杯数学建模D题(第二阶段)人机游戏中的数学模型全过程文档及程序

2012年认证杯SPSSPRO杯数学建模 D题 人机游戏中的数学模型 原题再现&#xff1a; 计算机游戏在社会和生活中享有特殊地位。游戏设计者主要考虑易学性、趣味性和界面友好性。趣味性是本质吸引力&#xff0c;使玩游戏者百玩不厌。网络游戏一般考虑如何搭建安全可靠、丰富多彩的…

MindOpt APL向量化建模语法的介绍与应用(2)

前言 在数据科学、工程优化和其他科学计算领域中&#xff0c;向量和矩阵的运算是核心组成部分。MAPL作为一种数学规划语言&#xff0c;为这些领域的专业人员提供了强大的工具&#xff0c;通过向量式和矩阵式变量声明以及丰富的内置数学运算支持&#xff0c;大大简化了数学建模…

数学建模-Matlab中randperm函数及其双重进阶版

1.randperm函数的用法 &#xff08;1&#xff09;这种用法就是参数只有一个数字&#xff0c;代表的含义就是随机排列之后打印输出&#xff1b; 我们举例的数字是4&#xff0c;就会把1到4这4个数字随机打乱之后随机输出&#xff0c;每次运行结果都不一样 所有可能的情况是n的…

第十三章 OpenGL ES-RGB、HSV、HSL模型介绍

第十三章 OpenGL ES-RGB、HSV、HSL模型详细介绍 第一章 OpenGL ES 基础-屏幕、纹理、顶点坐标 第二章 OpenGL ES 基础-GLSL语法简单总结 第三章 OpenGL ES 基础-GLSL渲染纹理 第四章 OpenGL ES 基础-位移、缩放、旋转原理 第五章 OpenGL ES 基础-透视投影矩阵与正交投影矩阵…

面试:如何设计一个注册中心?

大家好&#xff0c;我是田哥 上周&#xff0c;一位群里的朋友反馈面试情况&#xff1a; 今天&#xff0c;给大家分享如何设计一个注册中心。其实这个问题&#xff0c;我之前在知识星球里分享过&#xff0c;可能是因为时间比较久了&#xff0c;加上这位朋友加入不久&#xff0c;…

五步搭建:用HelpLook零代码创建企业专属知识库

随着企业的不断发展&#xff0c;拥有一个强大的企业知识库不仅能促进内部沟通&#xff0c;还能展示企业专业形象。HelpLook作为一款简单好用AI知识库搭建系统&#xff0c;只需5步&#xff0c;即可能够零代码帮助企业建立专属知识库。 一、如何从0到1搭建企业知识库&#xff1f;…

ARL资产侦察灯塔系统

1、资产侦察灯塔系统搭建 1.1、系统要求 目前暂不支持 Windows&#xff0c;Linux 和 MAC 建议采用 Docker 运行&#xff0c;系统配置最低 2 核 4G。 由于自动资产发现过程中会有大量的的发包&#xff0c;建议采用云服务器可以带来更好的体验 实验环境&#xff1a; 系统&…

数据结构复习指导之顺序表上基本操作的实现(插入、删除、查找)

文章目录 顺序表基本操作实现 知识总览 1.顺序表的初始化 1.1静态分配顺序表的初始化 1.2动态分配顺序表的初始化 2.插入操作 2.1插入操作流程 2.2插入操作时间复杂度 3.删除操作 3.1删除操作流程 3.2删除操作时间复杂度 4.查找操作 4.1按位查找 4.2按位查找时间…

NetBox4 安装指南-为网络工程师打造的基础设施管理(全面汉化)

介绍 NetBox 是用于建模和记录现代网络的领先解决方案。由 结合 IP 地址管理 &#xff08;IPAM&#xff09; 的传统应用和 具有强大 API 和扩展的数据中心基础架构管理 &#xff08;DCIM&#xff09;&#xff0c; NetBox 为推动网络自动化提供了理想的“事实来源”。 NetBox 在…

弹性云服务器性能对比(内附测试数据),快快网络服务器崭露头角

随着计算技术的不断革新&#xff0c;云服务器已成为企业和个人部署应用与服务的首选。尤其线上业务日益盛行的今天&#xff0c;云服务商的实力更是备受瞩目。对于企业而言&#xff0c;高稳定&#xff0c;存储速度都是不可或缺的基本要求&#xff0c;这些都对公有云的云端编解码…

阿里云服务器部署网站(图文详解)

一&#xff0c;准备工作 1.1&#xff0c;点击&#xff1a;注册阿里云账号 输入&#xff1a;账户名&#xff0c;登录密码&#xff0c;手机号。 1.2&#xff0c;域名注册和备案 详细请参考&#xff1a;阿里云域名购买流程和备案流程 1.3&#xff0c;准备服务器 详细请参考&a…