【初阶数据结构】第七站:二叉树(附完整代码和注释)


前言

有树的入门和堆的实现前文的铺垫,我们已经了解了二叉树的基本结构,本章将完成对二叉树遍历、构造等的函数的讲解和实现。

如对树的结构不够了解请跳转链接: 树的入门和堆的实现


一、二叉树函数一览

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi);
// 二叉树销毁
void BinaryTreeDestory(BTNode* root);
// 二叉树节点个数
int BinaryTreeSize(BTNode* root);
// 二叉树节点个数2(现代写法)
int BinaryTreeSize2(BTNode* root);
// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root);
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k);
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);
// 二叉树前序遍历 
void BinaryTreePrevOrder(BTNode* root);
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root);
// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root);
// 层序遍历
void BinaryTreeLevelOrder(BTNode* root);
// 判断二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* root);

在这里插入图片描述

二、二叉树的结构

前文言二叉树不过有计划生育的普通树(它每个节点至多两个孩子),也正因为这样我们很容易用结构体去表示、实现它的节点,而不过多的浪费我们的指针域(结构体的指针成员)。以下是它的节点的结构体

#define BTDataType char 
typedef struct BinaryTreeNode
{
	BTDataType _data;//存储数据的变量
	struct BinaryTreeNode* _left;
	struct BinaryTreeNode* _right;
}BTNode;

三、二叉树各函数实现

1.通过前序遍历数组构建二叉树

这里采用了递归的方式构建,我们再来复习一下递归的实现思路:(“递”+“归”)①“递”:即重复调用函数自身、不断创建栈帧的过程,②“归”:即重复调用自身的过程被某种情况打断,从而有一些“递”的较深的函数结束,最终连锁导致一个递归函数结束的过程。

// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi)
{

	if (a[*pi ] == '#'||*pi>=n)
	{
		//printf(" NULL ");
		(*pi)++;
		return NULL;
	}
		
	BTNode* cur = (BTNode*)malloc(sizeof(BTNode));
	//printf(" %c ", a[*pi]);
	cur->_data = a[(*pi)++];//这里一定注意运算符的优先级,后置++优先级高于*,忘记加()无法得到理想的值
	//遍历数组用前序递归创建二叉树
	cur->_left= BinaryTreeCreate(a, n, pi);
	cur->_right = BinaryTreeCreate(a, n, pi);
	return cur;
}

所以我们要先写好“递”,即以上函数中的:
BTNode* cur = (BTNode*)malloc(sizeof(BTNode));
cur->_left = BinaryTreeCreate(a, n, pi); //重复调用函数自身创造孩子节点并连接起来
cur->_right = BinaryTreeCreate(a, n, pi);

再写“归”的部分:
①当访问到“#”即返回NULL作为上一层函数中创造的节点cur的孩子
if (a[*pi ] == ‘#’||*pi>=n)
{
(*pi)++;
return NULL;
}

②当某一层函数中创造的节点的两个孩子都是NULL时,该层函数即将结束,返回自身节点cur作为上一层函数的孩子之一
return cur;

在这里插入图片描述

2.销毁二叉树

同样递归销毁,我们先写“递”的过程:
调用函数自身对子节点先后销毁
BinaryTreeDestory(root->_left);
BinaryTreeDestory(root->_right);

再写“归”:
①当节点为空(NULL)我们无法对其删除:
if (root == NULL)
return;

②或者函数正常结束

// 二叉树销毁
void BinaryTreeDestory(BTNode* root)
{
	if (root == NULL)
		return;
	BinaryTreeDestory(root->_left);
	BinaryTreeDestory(root->_right);
	free(root);
}

3.统计二叉树节点个数

①第一种统计写法:

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

这种方法中规中矩,我们需要创建一个全局变量num来统计节点个数,用前序“递”、“归”遍历二叉树,当节点不为空对num++,

①第二种现代统计写法:

//二叉树节点个数2
int BinaryTreeSize2(BTNode* root)
{
	return root==NULL ? 0:BinaryTreeSize2(root->_left) + BinaryTreeSize2(root->_right)+1;
}

现代现代,虽也牺牲了一些可读性,但三目运算符很多时候的妙用细细分析后还是很令人惊叹的。
一个表达式便同时拥有了“递”“归”两种过程:①“递”:这个“递”的思维方式虽常见但很奇妙。在这里,我们每个节点只需要向父节点返回其分支之下的所有节点个数再加上自己一个的数字(打个比方:一家公司的上下级等级森严,一天公司最大的boss要统计公司人数,我也是公司一员,无论我的职位是什么,我只需要向我的上一级传递我手下的所有人数再加我一个人就好,如果所有的人都这么做,最终到达boss手里数字的便是公司中除了boss以外的所有人数了。)②“归”:节点为空即返回0(公司最底层的人没有手下,他问手下人数就只能问空气(NULL)了,空气当然回复你0个人),不为空返回自己所属分支的节点个数加自己一个(不为空则还是有这个人存在的,就所有手下给的人数之和再加自己一个给上级)

4.统计叶子节点的个数

// 二叉树叶子节点个数
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);
}

5.统计二叉树第k层节点个数

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

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

// 二叉树查找值为x的节点
//写循环时先写递的过程,再写归的过程,递的过程即在函数体内重复调用该函数,归的过程,即递过程的终止返回情况(当root为空,或找到该值
//或一个节点没有孩子节点时返回)。
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
		return NULL;
	if (root->_data == x)
		return root;

	BTNode*  left=BinaryTreeFind(root->_left, x);
	if (left)
		return left;
	BTNode* right = BinaryTreeFind(root->_right, x);
	if (right)
		return right;
	return NULL;
}

7.二叉树前序遍历

void BinaryTreePrevOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("# ");
		return;
	}
	printf("%c ",root->_data);
	BinaryTreePrevOrder(root->_left);
	BinaryTreePrevOrder(root->_right);
}

在这里插入图片描述
递归遍历调用函数的全过程
在这里插入图片描述

8.二叉树中序遍历

void BinaryTreeInOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("# ");
		return;
	}
	BinaryTreeInOrder(root->_left);
	printf("%c ", root->_data);
	BinaryTreeInOrder(root->_right);
}

9.二叉树后序遍历

void BinaryTreePostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("# ");
		return;
	}
	BinaryTreePostOrder(root->_left);
	BinaryTreePostOrder(root->_right);
	printf("%c ", root->_data);
}

10.层序遍历

层序遍历的思想就是将根节点放入队列中,利用队列的单向性,每次出队头节点时将它的两个子节点分别入队,这种方式其实是有点像堆的。
在这里插入图片描述
在这里插入图片描述

// 层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{
	Queue A;
	QueueInit(&A);
	if (root)
	{
		Queuepush(&A, root);
	}
	while (!QueueEmpty(&A))
	{
		BTNode* front = QueueTop(&A);
		printf("%c ", front->_data);
		QueuePop(&A);

		if (front->_left)
		{
			Queuepush(&A, front->_left);
		}

		if (front->_right)
		{
			Queuepush(&A, front->_right);
		}
	}
	printf("\n");
	QueueDestroy(&A);
}

四、完整代码

**Btree.h**


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

typedef struct BinaryTreeNode
{
	BTDataType _data;
	struct BinaryTreeNode* _left;
	struct BinaryTreeNode* _right;
}BTNode;




// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi);

// 二叉树销毁
void BinaryTreeDestory(BTNode* root);

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

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

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

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

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

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

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

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

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

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



**Btree.c**




// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi)
{

	if (a[*pi ] == '#'||*pi>=n)
	{
		//printf(" NULL ");
		(*pi)++;
		return NULL;
	}
		
	BTNode* cur = (BTNode*)malloc(sizeof(BTNode));
	//printf(" %c ", a[*pi]);
	cur->_data = a[(*pi)++];
	cur->_left= BinaryTreeCreate(a, n, pi);
	cur->_right = BinaryTreeCreate(a, n, pi);
	return cur;
}



// 二叉树销毁
void BinaryTreeDestory(BTNode* root)
{
	if (root == NULL)
		return;
	BinaryTreeDestory(root->_left);
	BinaryTreeDestory(root->_right);
	free(root);
}

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

//二叉树节点个数2
int BinaryTreeSize2(BTNode* root)
{
	return root==NULL?0:BinaryTreeSize2(root->_left) + BinaryTreeSize2(root->_right)+1;
}



// 二叉树叶子节点个数
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);
}



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


// 二叉树查找值为x的节点
//写循环时先写递的过程,再写归的过程,递的过程即在函数体内重复调用该函数,归的过程,即递过程的终止返回情况(当root为空,或找到该值
//或一个节点没有孩子节点时返回)。
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
		return NULL;
	if (root->_data == x)
		return root;

	BTNode*  left=BinaryTreeFind(root->_left, x);
	if (left)
		return left;
	BTNode* right = BinaryTreeFind(root->_right, x);
	if (right)
		return right;
	return NULL;
}




// 二叉树前序遍历 
void BinaryTreePrevOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("# ");
		return;
	}
	printf("%c ",root->_data);
	BinaryTreePrevOrder(root->_left);
	BinaryTreePrevOrder(root->_right);
}



// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("# ");
		return;
	}
	BinaryTreeInOrder(root->_left);
	printf("%c ", root->_data);
	BinaryTreeInOrder(root->_right);
}



// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("# ");
		return;
	}
	BinaryTreePostOrder(root->_left);
	BinaryTreePostOrder(root->_right);
	printf("%c ", root->_data);
}



// 层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{
	Queue A;
	QueueInit(&A);
	if (root)
	{
		Queuepush(&A, root);
	}
	while (!QueueEmpty(&A))
	{
		BTNode* front = QueueTop(&A);
		printf("%c ", front->_data);
		QueuePop(&A);

		if (front->_left)
		{
			Queuepush(&A, front->_left);
		}

		if (front->_right)
		{
			Queuepush(&A, front->_right);
		}
	}
	printf("\n");
	QueueDestroy(&A);
}

总结

今日完成了二叉树的基本实现、讲解和对递归函数的理解。


本文章为作者的笔记和心得记录,顺便进行知识分享,有任何错误请评论指点:)。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值