二叉树的创建、销毁及其功能、结构

目录

一、如何理解二叉树的结构

二、构建二叉树需要的基础代码

三、代码

二叉树的遍历:前序 中序 后序

二叉树结点个数

求叶子节点的个数

求二叉树的高度:左右子树高度的最大值

 二叉树第k层(LevelK)结点个数  

查找元素,返回节点指针

二叉树的层序遍历:队列实现(非递归)

二叉树的销毁:最好用后序

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


一、如何理解二叉树的结构

对于二叉树的结构,是递归的最典型的例子。其递归过程,就是二叉树的树枝层层展开的过程。

每棵树,都可以看成根节点 和 左右子树的结合体。

对于根节点的左右子树而言,当左右子树为空树时,也就代表着 某条支路的递归完成,此时往往需要依次return。

若返回类型是不void类型,还需要额外的一次return,返回到上一次调用此函数处,作为返回值。

递归:子问题 + 终结调节

二、构建二叉树需要的基础代码

1.自由构建


typedef int BTDateType;			//int 在前

typedef struct BinaryTreeNode
{
	BTDateType date;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right; 

}BTNode;



BTNode* BuyNode(int x)			//获得树的节点:链表型
{
	BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));

	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}

							//如果前边已经有一次返回,则可保证此处代码一定不符合前面的 if 返回条件

		//找到堆上的空间,并进行修改,因此后续的函数在使用时,才能保证date、left、right是处理好的结果

	newnode->date = x;			
	newnode->left = newnode->right = NULL;

	return newnode;

}

//建好树,返回树根


BTNode* CreatBinaryTree()			//已经有buy-node函数
{
	BTNode* node1 = BuyNode(1);
	BTNode* node2 = BuyNode(2);
	BTNode* node3 = BuyNode(3);
	BTNode* node4 = BuyNode(4);
	BTNode* node5 = BuyNode(5);
	BTNode* node6 = BuyNode(6);
	BTNode* node7 = BuyNode(7);

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

	return node1;

}

需要构建树节点 、 定义date的数据类型、获得新节点的方法、根据自己的思路,自由构建一棵树。

三、代码

二叉树的遍历:前序 中序 后序


void PrevOrder(BTNode* root)
{
		//二叉树不能轻易assert空

	if (root == NULL)		//结束条件
	{
		printf("N ");
		return;
	}

	//此处root一定不为空


	printf("%d ", root->date);
	
	PrevOrder(root->left);		//子问题		。 在物理上,就是函数栈帧不断创建和销毁,销毁之后,上一层函数,继续往下执行!	
	PrevOrder(root->right);

				//不能写换行,否则每次递归都会打印换行
}
void InOrder(BTNode* root)
{
		//二叉树不能轻易assert空

	if (root == NULL)		//结束条件
	{
		printf("N ");
		return;
	}

	//此处root一定不为空


	
	InOrder(root->left);		//子问题
	printf("%d ", root->date);
	InOrder(root->right);

}


void PostOrder(BTNode* root)
{
		//二叉树不能轻易assert空

	if (root == NULL)		//结束条件
	{	
		printf("N ");
		return;			//表示本层函数栈帧销毁了
	}

	//此处root一定不为空


	
	PostOrder(root->left);		//子问题
	PostOrder(root->right);
	printf("%d ", root->date);



}


二叉树结点个数


int BTreeSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}

	int leftsize = BTreeSize(root->left);
	int rightsize = BTreeSize(root->right);

	return leftsize + rightsize + 1;

}

需要注意的是

int leftsize = BTreeSize(root->left);
int rightsize = BTreeSize(root->right);

这就是不断调用递归的过程

并且返回类型是int类型,需要额外的一次返回

    return leftsize + rightsize + 1;

求叶子节点的个数


int BTreeLeafSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
			//此处root一定不为空
	if (root->left == NULL && root->right == NULL)			//不能写连等!
	{
		return 1;
	}

	return BTreeLeafSize(root->left) + BTreeLeafSize(root->right);		//会在此处不断返回本层的结果,返回到上一层的栈帧里的函数调用处。

}

    if (root->left == NULL && root->right == NULL)            //不能写连等!
    {
        return 1;
    }

需要注意的是,递归的不断调用过程中,就是二叉树不断分叉的过程

    return BTreeLeafSize(root->left) + BTreeLeafSize(root->right);

此段代码,可以不断使函数返回上一层

求二叉树的高度:左右子树高度的最大值


int BTreeHeight(BTNode* root)
{

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

	int left_height = BTreeHeight(root->left) + 1;		//得 + 1,根节点也是高度
	int right_height = BTreeHeight(root->right) + 1;

	return left_height > right_height ? left_height : right_height;

}

int left_height = BTreeHeight(root->left) + 1;        //得 + 1,根节点也是高度
int right_height = BTreeHeight(root->right) + 1;

应该记录返回值,防止多次递归调用。

 二叉树第k层(LevelK)结点个数  

//子问题:转化成左子树的第 K - 1层 和 右子树的第 K - 1层
//结束条件(所有的返回条件): K == 1    且节点不为空  或者节点为空


int BTreeLevelKSize(BTNode* root, int k)
{

	assert(k != 0);

	//if (!root)		//不能这么写,  ! 操作符 是逻辑操作符,只能对bool类型进行操作

	if (root == NULL)
		return 0;

	if (k == 1)
		return 1;

	int left_k_size = BTreeLevelKSize(root->left, k - 1);		//不断递归调用就是不断分叉,最后直到 子树变成空
	int right_k_size = BTreeLevelKSize(root->right, k - 1);

	return left_k_size + right_k_size;

}

查找元素,返回节点指针

        //查找:本质是遍历        选前序查找!!!

//查找:左树找到,就返回左树,不用去查找右树


BTNode* BTreeFind(BTNode* root, BTDateType x)
{
	if (root == NULL)		//走到尾
		return NULL;

	if (root->date == x)
		return root;

				//root一定不为空

	BTNode* left = BTreeFind(root->left, x);

	if (left)		//如果左树不为空(为root)		//左边找到就直接返回left,之前每一层就都不去右边找了,效率更高!
		return left;

	BTNode* right = BTreeFind(root->right, x);

	if (right)
		return right;



			//到此处之后,一定两者都为空!即没找到。

	return NULL;		
}

左树找到之后,直接返回左树的指针,不去右树查找

二叉树的层序遍历:队列实现(非递归)

首先应该实现一个队列。

对于队列的DateType类型,应该是tree节点的指针,而不是节点的date。(以便找到left和right树)


void LevelOrder(BTNode* root)
{
	Q q;
	QueueInit(&q);

	if (root)	
		QueuePush(&q, root);			//往队列插入根节点的指针	,队列存储的date是指针!!!

	while (!QueueEmpty(&q)) 	//队列不为空
	{

		//出数据

		BTNode* front = QueueFront(&q);			//front是数据,first是节点

		QueuePop(&q);	//只是pop掉了队列的一个指针变量,front指针仍然可以找到root节点

		printf("%d ", front->date);

		//出完一个数据之后,将左右孩子push到队列中。

		if (front->left)		
			QueuePush(&q, front->left);		//Push时,push的是指针类型!!! (以便父找子)

		if (front->right)
			QueuePush(&q, front->right);

	}

	putchar(10);

	QueueDestroy(&q);

}

思想:

利用队列的先进先出 

二叉树的父节点出队列pop时,将父节点的孩子节点带入push队列。先pop父节点,再push子树。

        if (front->left)        
            QueuePush(&q, front->left);        //Push时,push的是指针类型!!! (以便父找子)

        if (front->right)
            QueuePush(&q, front->right);

当将二叉树的所有有效数据都push到队列之后,此时不push空指针,只进行pop操作。

二叉树的销毁:最好用后序


void BTreeDestroy(BTNode* root)
{

	if (root == NULL)
		return;

	//后续遍历

	BTreeDestroy(root->left);		//递归过程	在销毁左子树时,就会在递归中不断用到free过程
	BTreeDestroy(root->right);

	free(root->right);				//销毁过程	无需置空
}

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

利用队列先进先出 及 完全二叉树数据连续(数据连续)的性质

步骤:

1.导入所有节点指针,不断出数据

2.数据出到空时,停止出数据。

3.再次出数据,当遇到非空指针时,则为false;若出完没有遇到,则为true。

4.return之前,应该先destroy。


bool BinaryTreeComplete(BTNode* root)
{
	Q q;
	QueueInit(&q);
	
	if (root != NULL)
		QueuePush(&q, root);	//存储的是节点的指针

	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);		//出数据  如果是完全二叉树,则可以出掉所有数据。
		QueuePop(&q);

		if (front)
		{
			QueuePush(&q, front->left);
			QueuePush(&q, front->right);
		}

		else	//front为空时,此时队列(若为完全二叉树)一定只剩下NULL
			break;

	}


	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);		//继续出数据
		QueuePop(&q);

		if (front != NULL)
		{
			QueueDestroy(&q);
			return false;	//返回之前先销毁
		}

	}

	QueueDestroy(&q);
	return true;	



	QueueDestroy(&q);
}

需要注意的是,当第 i 层pop掉之后,队列中的元素是 第 i + 1 层的数据。

如果是完全二叉树,当第一次循环停止时,此时一定只剩下NULL

第一层循环:

首先将树的节点,导入到队列中

在导入数据时,不同于层序遍历 

层序遍历不会导入空节点

if (front->left)        
            QueuePush(&q, front->left);        //Push时,push的是指针类型!!! (以便父找子)

        if (front->right)
            QueuePush(&q, front->right);

但是判断是否是完全二叉树时,应该导入空节点

    if (front)
        {
            QueuePush(&q, front->left);
            QueuePush(&q, front->right);
        }


        else    //所有数据全部弹出
            break;

当所有非空数据pop掉之后,循环结束

第二层循环:

不断pop空指针,当出现非空指针时,销毁、return false。

  • 31
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值