C语言初阶DS——二叉树的实现(简单易懂)

目录

0.前言

1.二叉树的分类

1、完全二叉树

2、满二叉树

3、只有左(右)子树:

4、平衡二叉树

2.二叉树的实现(搜索树)

1、二叉树的功能声明

2、二叉树函数的实现

1、二叉树创建函数

2、二叉树销毁函数

3、三个类型节点函数

4、前中后序遍历

5、查找值为x的节点函数

3、二叉树代码总结


0.前言

        二叉树在数据结构(即DS)当中有很重要的意义,在整个初阶DS当中二叉树的难度和占比都是很大一部分的,二叉树的单独基础概念我会单独开一篇文章去讲述,同时这样也方便大家不用往下划找的头疼,废话不多说我们立刻开始。

1.二叉树的分类

1、完全二叉树

  • 假设二叉树的高度为h,除第 h 层外,其它各层 (1 -> h-1) 的结点数都达到最大个数,第h层有叶子结点,并且叶子结点都是从左到右依次排布,这就是完全二叉树。如图:
  • 注意!!完全二叉树一定是从左到右依次排布,如果从左到右当中出现了没有左子树,但是存在右子树的情况,那么这个就不是完全二叉树。
  • 同时也是一种完全二叉树!

2、满二叉树

        满二叉树在国内的定义与国际上满二叉树的定义有些稍微不同,那么我们就按照国内对满二叉树的定义来。

  • 国内对满二叉树定义:除了叶子节点外每一个节点都有它的左右节点并且叶子节点都在二叉树的最底层。

Tip:满二叉树也是一种特殊的完全二叉树!

3、只有左(右)子树:

定义:只有左边或者右边一直延伸的树。如图:

4、平衡二叉树

  • 定义:假设一棵树是空树或者它的任意一个节点上的左右子树高度差的绝对值不超过1,我们就叫它为平衡二叉树。如图:

2.二叉树的实现(搜索树)

1、二叉树的功能声明

        首先我们要实现一个二叉树,我们得要知道二叉树要实现的什么样的功能,所以我们先定下要实现基本的搜索功能。如下

						//数组	  //传入的数字 //数组下标
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi);
// 二叉树销毁
void BinaryTreeDestory(BTNode** root);
// 二叉树节点个数
int BinaryTreeSize(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);

二叉树创建、查找节点个数以及核心的前中后序的遍历。

那么什么是前中后序遍历呢?

  • 前序遍历:访问节点内容->左节点->右节点,依次按照这样的顺序访问。
  • 中序遍历:访问左节点->节点内容->右节点,依次按照这样的顺序访问。
  • 后序遍历:访问左节点->右节点->节点内容,依次按照这样的顺序访问。

如果不知道前中后序遍历的话一定要记下来!!!十分重要!!!!

2、二叉树函数的实现

1、二叉树创建函数

我们来把二叉树节点该有的内容首先来声明好

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

这里我们为什么要将char进行重命名为BTDatatype呢?

1、这样方便我们后续写函数的时候更加方便清晰认清楚这是二叉树里面的内容

2、在我们写函数的时候也许我们会写出char,int这类的变量,那这样不就和我们要写进二叉树的数值内容混乱了吗?所以这样写更加清晰明了,还不会乱。

首先我们第一个实现二叉树的创建BinaryTreeCreate函数。我们知道创建二叉树就需要数组将内容传入到二叉树里面来,那么我们就需要一个指针来接收数组,数组里面的内容以及数组下标也要传入,所以BinaryTreeCreate有3个变量

BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi)
{
	assert(a);
	assert(pi);
	if (a[*pi] == '#')//这里的"#"假设代表为二叉树里面的空节点
	{
		(*pi)++;
		return NULL;
	}
	else
	{
		BTNode* root = (BTDataType*)malloc(sizeof(BTNode));
		if (root == NULL)
		{
			perror("malloc failed!");
			exit(-1);
		}
		root->_data = a[*pi];//赋值
		(*pi)++;//下标+1
		root->_left = BinaryTreeCreate(a, n, pi);//走左节点
		root->_right = BinaryTreeCreate(a, n, pi);//走右节点
		return root;
	}
}

        首先用assert检查传入的指针是否正常,不正常就报错。接着检查数组当前下标指的内容是否为#(即为空),为#就让下标++,走到下一个位置,然后返回上一级。这里为什么要返回上一级呢?

        这里遍历二叉树我们采取递归的方法,如果这颗树是个空树,那就不需要遍历了,直接返回空。如果不是空,我们就会继续往下走,一直走到树的最底层叶子节点下左右两个节点,那么这个时候很多情况(可能)都是空节点,这样的话直接回到上一层的节点,简单的来说二叉树的连接是从最底层的叶子节点自下而上把节点连接起来,但是赋值是第一次遍历(自上而下)就已经遍历好了。

        在我们创建好节点以后将数组赋值到节点里面,接着让这个节点左节点和右节点利用递归依次连接起来,最后返回这个新创建的节点。

        下面有张图可能会有利于帮助你理解:

2、二叉树销毁函数

我们按照声明的顺序来,接下来就是BinaryTreeDestory()销毁函数,我们可以看到我这里用了一个二级指针,为什么用二级指针呢?

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

        我们可以看到在我们声明的结构体里头,我们的左节点和右节点利用的是指针来进行连接的,但是我们进入销毁函数的时候用的是指针接收的,这样就很清晰了,我们用指针接收,当我们对这个指针进行解引用的时候,拿到的结构体里面的成员,我们还需要对left(right)进行解引用来找到左右节点

        好了那么我们知道以后就很容易把销毁函数写出来了

void BinaryTreeDestory(BTNode** root)
{
	if (root != NULL)
	{
		BinaryTreeDestory((*root)->_left);
		BinaryTreeDestory((*root)->_right);
		free(root);
	}
}

        这里采取了后序遍历的思想,先找到树底层的节点,然后自下而上的销毁节点,这样销毁的话就很完美了~~~

3、三个类型节点函数

接下来的三个函数都是用来查找二叉树的节点个数的函数,这些函数多多少少都有一些相似性,那么我们就放在同一个地方讲

int BinaryTreeSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	return 1 + BinaryTreeSize(root->_left) + BinaryTreeSize(root->_right);
}

        节点个数函数我们采用了一个递归的思想,如果是为了方便容易理解,那么我们就可以想象二叉树的底层往上进行统计,如果这个地方有节点,那么就为1,如果是空节点那么就为0。

        最后走到第11步,已经把前面所有节点累计加好了,最后return所有相加的结果。

        接下来就是统计叶子节点函数BinaryTreeLeafSize()

int BinaryTreeLeafSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	if (root->_left == NULL && root->_right == NULL)
	{
		return 1;
	}
	return BinaryTreeSize(root->_left) + BinaryTreeSize(root->_right);
}

        叶子节点个数函数其实和节点个数函数其实有很多相似程度,但是在节点个数函数上加上了节点左右节点为空才为1的限制条件。因为叶子节点是没有左右节点的,所以return里面就不需要加上1,只需要在return遍历左节点和右节点即可,因为返回为1的结果就放在了左右节点里。

        接下来二叉树第K层节点个数的函数BinaryTreeLevelKSize()

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);
}

这个函数也是跟前面的节点函数十分相似的,但是这个函数多了一个k的变量,这个k就是你传入想要知道第几层的节点个数,我们知道递归依次往下进行的时候,我们函数进入会将k值依次-1,直到k == 1的时候,这个就达到了我们想要知道节点的层数,接下来的事情就是把这一层所有节点记录下来,累计相加最后返回即可。

4、前中后序遍历

//前序遍历
void BinaryTreePrevOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	printf("%d ",root->_data);
	BinaryTreePrevOrder(root->_left);
	BinaryTreePrevOrder(root->_right);
}
//中序遍历
void BinaryTreeInOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	BinaryTreeInOrder(root->_left);
	printf("%d ", root->_data);
	BinaryTreeInOrder(root->_right);
}
//后序遍历
void BinaryTreePostOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	BinaryTreeInOrder(root->_left);
	BinaryTreeInOrder(root->_right);
	printf("%d ", root->_data);
}

前序遍历这边首先打印节点内容,然后走左子树,最后走右子树,最终完成遍历。

中序遍历只要把打印内容和左节点换个先后即可。

后序遍历只要把打印内容放在遍历右子树之后即可。

5、查找值为x的节点函数

我们需要一个来查找节点里面内容函数,所以我们需要一个BinaryTreeFind的函数,把二叉树和数值传入到这个函数里面去实现

BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
	{
		return NULL;
	}
	if (root->_data == x)
	{
		return  root;
	}
	BTNode* node = BinaryTreeFind(root->_left, x);
	if (node != NULL)
	{
		return node;
	}
	return BinaryTreeFind(root->_right, x);
}

那么这段代码怎么理解呢?

我们可以分为两个阶段

要是找到了节点内容,就不停的往上返回最后返回根节点,如果没找到内容,就一直找到底层下的空节点。

如果不为空(就是找到节点了)的话就返回找到节点,如果node为空(没有找到节点)那么就返回空。

3、二叉树代码总结

最后我们完成所有二叉树的内容实现,我们把所有的二叉树的内容都在下面

BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi)
{
	assert(a);
	assert(pi);
	if (a[*pi] == '#')
	{
		(*pi)++;
		return NULL;
	}
	else
	{
		BTNode* root = (BTDataType*)malloc(sizeof(BTNode));
		if (root == NULL)
		{
			perror("malloc failed!");
			exit(-1);
		}
		root->_data = a[*pi];//赋值
		(*pi)++;//下标+1
		root->_left = BinaryTreeCreate(a, n, pi);
		root->_right = BinaryTreeCreate(a, n, pi);
		return root;
	}
}

void BinaryTreeDestory(BTNode** root)
{
	if (root != NULL)
	{
		BinaryTreeDestory((*root)->_left);
		BinaryTreeDestory((*root)->_right);
		free(root);
	}
}

int BinaryTreeSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	return 1 + BinaryTreeSize(root->_left) + BinaryTreeSize(root->_right);
}

int BinaryTreeLeafSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	if (root->_left == NULL && root->_right == NULL)
	{
		return 1;
	}
	return BinaryTreeSize(root->_left) + BinaryTreeSize(root->_right);
}

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

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

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

BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
	{
		return NULL;
	}
	if (root->_data == x)
	{
		return  root;
	}
	BTNode* node = BinaryTreeFind(root->_left, x);
	if (node != NULL)
	{
		return node;
	}
	return BinaryTreeFind(root->_right, x);
}

希望我的博客对你有所帮助~~~~

———————————————————————————————————————————

京子小可爱给你加油~~~~~ 

  • 8
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值