链式结构二叉树的实现

前言:

相较于用顺序结构实现二叉树而言,用链式结构实现二叉树在编程上更为容易,但其方法往往难以想到,因为用到的是递归的思想。

因此此篇中包含大量有关递归函数推导过程的分析。

此外用链式结构实现二叉树,二叉树不一定是完全二叉树。

链式二叉树的创建:

1、头文件代码:

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>

typedef int TreeDataType;

typedef struct BinaryTreeNode
{
	TreeDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;

}BTNode;

BTNode* BuynewNode(TreeDataType x);

2、测试文件代码:

#define  _CRT_SECURE_NO_WARNINGS
#include "tree.h"

void TreeTest()
{
	BTNode* node1 = BuynewNode(1);
	BTNode* node2 = BuynewNode(2);
	BTNode* node3 = BuynewNode(3);
	BTNode* node4 = BuynewNode(4);
	//BTNode* node5 = BuynewNode(5);
	//BTNode* node6 = BuynewNode(6);

	node1->left = node2;
	node2->left = node4;
	node1->right = node3;
	//node2->right = node5;
	//node3->right = node6;
}

int main()
{
	TreeTest();
	return 0;
}

3、申请新节点自定义函数代码:

BTNode* BuynewNode(TreeDataType x)
{
	BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
	assert(newnode);
	newnode->data = x;
	newnode->left = newnode->right = NULL;
	return newnode;
}

4、二叉树结构图:

通过上述代码,建立了一个如下图的链式二叉树结构:

下图用于二次验证

有关链式二叉树自定义函数的实现:

1、头文件声明:

// ⼆叉树结点个数
int BinaryTreeSize(BTNode* root);
// ⼆叉树叶⼦结点个数
int BinaryTreeLeafSize(BTNode* root);
// ⼆叉树第k层结点个数
int BinaryTreeLevelKSize(BTNode* root, int k);
//⼆叉树的深度/⾼度
int BinaryTreeDepth(BTNode* root);
// ⼆叉树查找值为x的结点
BTNode* BinaryTreeFind(BTNode* root, TreeDataType x);
// ⼆叉树销毁
void BinaryTreeDestory(BTNode** root);

2、求二叉树结点个数

代码:

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

递归过程:

注:红线——调用过程;蓝线——返回过程

分析:当第一次进入函数体时,root 非空 ;调用 return 语句中的 BinaryTreeSize 函数;循环调用,直至节点来到 4 位置处,此时再次调用 BinaryTreeSize 函数时,满足函数中的 if  条件,因此返回 0,再次回到 4 位置处,此时调用同行语句中的第二个 BinaryTreeSize 函数,同样满足 if  条件,返回 0 ,此时 节点 4 位置处的 return 语句后的值为 1+0+0 = 1,因此返回 1 给 节点3  ,

此时 节点3 第一个 BinaryTreeSize函数 的值已知,开始调用第二个 BinaryTreeSize函数 ,而 节点3 的右节点为 0 ,因此返回值为 0 ,此时 节点3 的return 行语句的返回值已知为:1+1+0 = 2

将 2 返回给 节点 1 ,此时 节点1 中的return行第一个 BinaryTreeSize函数 值已知,接下来执行第二个 BinaryTreeSize函数 ,同理可以得到他的返回值应为 1 ,最终递归函数回到 节点1 的位置处,此时 return 的返回值为 1+2+1 = 4,即为链式二叉树的节点总个数

结果验证:

二次验证:

3、求二叉树叶子节点个数

叶子节点的定义:没有左右子节点的节点被称为叶子节点

代码:

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

递归过程:

分析:

进入自定函数主体后,因为不满足 if 语句,会一直递归到 节点4 位置处,此时满足第二个 if 语句 返回1,此时指向 节点2 位置

节点2 的右节点为 NULL,因此再次返回1,此时指向 节点1 的位置,return行中,左节点 已遍历完,值为 1,此时调用第二个函数,同理能够得到返回值应为 1,且再次指向 节点1 的位置处,

此时 节点1 return行的返回值为 1+1 = 2;最后将2传递到主函数当中。

结果验证:

二次验证:

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

代码:

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

递归过程:

进入函数后,一路递归至节点4处,此时满足第二个 if 条件;返回1,此时指向 节点2

节点2 的右节点为NULL,满足第一个 if 条件,返回0,此时指向 节点1 ,开始调用return行 第二个函数,同理可得返回值应为 0 ,因此最终返回给主函数的值应当为 1;

结果验证:

二次验证:

5、求二叉树深度/高度

代码:

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

递归过程:

分析:此处重点是比较根节点左右子节点的深度,而不是求和,使用三目操作符来比较左右子节点的深度,来确定最终的返回值

当进入函数时,会递归直至 节点4 ,此时再次递归函数时,会满足第一个 if 条件,返回0, 节点4 的 左节点个数 = 右节点个数 ,返回1,此时指向 节点2 ,开始统计 节点2 右节点个数,根据上图分析,个数为0 因此 节点2 的 左节点个数 > 有节点个数 ,返回 1 + 1 = 2,此时指向 节点1 的位置处,同理分析,可以得知 节点1 的右节点深度为 1,因此有 左节点个数 > 有节点个数,最终返回到主函数的值为 1+2 = 3

结果验证:

我们更改节点指向,使其深度为4,二次验证结果如下:

6、二叉树查找值

代码:

BTNode* BinaryTreeFind(BTNode* root, TreeDataType x)
{
	if (NULL == root)
	{
		return NULL;
	}
	if (x == root->data)
	{
		return root;
	}
	BTNode* leftfind = BinaryTreeFind(root->left, x);
	if(leftfind)
	{
		return leftfind;
	}
	BTNode* rightfind = BinaryTreeFind(root->right, x);
	if (rightfind)
	{
		return rightfind;
	}
	return NULL;
}

递归分析:

分析:假设我们要查找4

当进入函数时,均不满足 if 条件,直至递归至 节点4 ,满足第二个 if 条件 ,并返回当前节点的地址,此时指向 节点2 的 leftfind 行处,并继续向下执行代码,此时对返回的 leftfind 进行判断,若非空说明接收到了正确的地址,直接返回,此时指向 节点1,同刚才的分析过程,因为 leftfind 接收到的返回值非空,会直接进入第三个 if 条件语句,并返回 leftfind 的地址,最终实现将 对应值的地址返回到主函数中,若在根节点的左子树上没有找到对应值,那么重复刚才的思想去右子树上找,若左右子树均为找到,则返回NULL;

结果验证:

二次验证:

7、链式二叉树的销毁

代码:

void BinaryTreeDestory(BTNode** root)
{
	if (NULL == *root)
	{
		return ;
	}
	BinaryTreeDestory(&((*root)->left));
	BinaryTreeDestory(&((*root)->right));
	free(*root);
	*root = NULL;
}

递归分析:

目的是找到每一个叶子节点,并将其释放并置空。

进入函数时,会递归至 节点4 的位置,再次递归时,满足 if 条件并返回,此时程序继续递归第二个函数,同样满足 if 条件 返回,此时 节点4 的左右子节点均为 NULL ,释放 节点4 并 置空,此时返回 节点2 位置处,重复刚才的操作,直至链表二叉树中所有的节点释放完毕。

结果验证:

销毁前:

销毁后:

链式二叉树的层序遍历

层序遍历不在使用递归的思想,而是通过队列来实现该函数。需要调用先前写过的队列函数,并将其头文件部分修改。修改代码如下:

typedef struct BTNode* QDatatype;//先前节点中存放的是int型数据,现将 存放的是二叉树节点的指针

typedef struct QueueNode
{
	QDatatype data;
	struct QueueNode* next;
}QueueNode;

typedef struct Queue
{
	QueueNode* phead;
	QueueNode* ptail;
	int size;
}Queue;

代码:

void BinaryLevelOrder(BTNode* root)
{
	Queue p;
	InitQueue(&p);
	QueuePush(&p, root);
	while (!BoolEmpty(&p))
	{
		BTNode* front = QueueFront(&p);
		printf("%d->", front->data);
		QueuePop(&p);
		if (front->left)
		{
			QueuePush(&p, front->left);
		}
		if (front->right)
		{
			QueuePush(&p, front->right);
		}
	}
}

思路:

目的是将链表二叉树一层一层的输出,我们通过队列,先将根节点入队,此时进入循环。

分析循环过程:

用 front 接收队头数据(二叉树节点指针),并打印,打印完毕后,pop队头数据,此时队列为空,但通过 front 依然能够访问二叉树中的左右非空子节点,此时将左右子节点依次入队,第一次循环结束。

当队列非空时,进入第二次循环,再次用 front 接收队头数据(二叉树节点指针),此时队头存放的地址已发生改变,(存放的是链表中节点2的地址),循环上述操作,能够把节点2的数据打印并出队列,同时再将节点2对应的左右非空子节点入队,如此循环直至队列为空

结果验证:

二次验证:

判断是否为完全二叉树

思路:

用链式结构实现二叉树时,会有一个明显区别,即:

完全二叉树,当非空数据全部出队时,此时队列中只有NULL数据;

而非完全二叉树,循环出队时(以遇到第一个NULL为循环停止条件),此时队列中还有非空的数据

注:在分析代码前,需要弄清楚一点:存放的数据为NULL  ≠  队列为空

上述分析过程中,可以确定一点,此代码需要将NULL数据一并存放到队列中,作为判定条件,先前说过,队列中存放的是 指针数据 , 那么对于空指针NULL而言,其本身也是数据之一,所以即使队列中全为NULL,那么该队列也为非空。

而队列中的bool判定是针对队列是否为空做出判断的,当把 NULL 数据入队时,队列非空,只不过存储的都是 NULL。

代码:

bool BinaryTreeComplete(BTNode* root)
{
	Queue p;
	InitQueue(&p);
	QueuePush(&p, root);
	while (!BoolEmpty(&p))
	{
		BTNode* front = QueueFront(&p);
		QueuePop(&p);
		if (front == NULL)//当取到第一个NULL数据时,结束循环
		{
			break;
		}
		QueuePush(&p, front->left);
		QueuePush(&p, front->right);
	}
	while (!BoolEmpty(&p))//把队列中剩下的数据全部出队
	{
		BTNode* front = QueueFront(&p);
		if (front != NULL)//若有非空数据,表明不是完全二叉树
		{
			QueueDestroy(&p);
			return false;
		}
		QueuePop(&p);
	}
	QueueDestroy(&p);//循环结束时,表面为完全二叉树
	return true;
}

结果验证:

改变节点连接方式,二次验证:

  • 14
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值