链式二叉树的前,中,后序遍历 AND 结点个数及高度等 文末附带全部代码


正文开始

前言

本文旨在介绍二叉树的链式存储中一些函数的实现

博客主页: 酷酷学!!!

更多文章, 期待关注~


前置说明: 在学习二叉树的基本操作前, 需要先创建一棵二叉树, 然后才能学习相关的基本操作. 由于目前阶段对二叉树结构掌握不够深入, 为了降低大家学习成本, 此处手动快速创建一棵简单的二叉树, 快速进入二叉树操作学习, 等二叉树结构了解查差不多时, 我们反过头来再研究二叉树真正的创建方式.

typedef int BTDatatype;

typedef struct BTNode
{
	BTDatatype data;
	struct BTNode* left;
	struct BTNode* right;
}BTNode;

BTNode* BuyNode(int x)
{
	BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	newnode->data = x;
	newnode->left = NULL;
	newnode->right = NULL;

	return newnode;
}


BTNode* CreateTree()
{
	BTNode* node1 = BuyNode(1);
	BTNode* node2 = BuyNode(2);
	BTNode* node3 = BuyNode(3);
	BTNode* node4 = BuyNode(4);
	BTNode* node5 = BuyNode(5);
	BTNode* node6 = BuyNode(6);


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

	return node1;
}

注意: 上述代码并不是创建二叉树的方式, 真正创建二叉树方式期待后续博客

首先实现下面的方法之前我们先来回顾一下二叉树:

什么是二叉树?

  1. 空树
  2. 非空:根结点,根结点的左子树、根结点的右子树组成的。

在这里插入图片描述

从概念中可以看出,二叉树定义是递归式的,因此后序基本操作中基本都是按照该概念实现的。

1. 前序遍历

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

在这里插入图片描述

我们先来看前序遍历:

前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。简单来说遍历的顺序为 根 -> 左子树 ->右子树

递归的本质: 拆成当前问题和子问题, 返回条件: 最小规模的子问题

我们可以把大问题化小, 首先拆成根, 左子树, 右子树的结构, 然后左子树 又可以拆成 根 左子树 右子树的结构, 结束条件为树为空树.

代码实现:

void PrevOrder(BTNode* root) 
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	printf("%d ", root->data);
	PrevOrder(root->left);
	PrevOrder(root->right);
}

运行结果:
在这里插入图片描述

前序遍历递归图解:

在这里插入图片描述

如图我们可以看出:

前序遍历依次访问的数据为:
1 2 3 4 5 6
如果加上对空树的访问, NULL简写为N:
1 2 3 N N N 4 5 N N 6 N N

下面为递归展开图, 不太理解的伙伴可以参考:

在这里插入图片描述

2. 中序遍历

中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。

即, 每棵树 先访问左子树, 在访问根, 最后访问右子树

void InOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	InOrder(root->left);
	printf("%d ", root->data);
	InOrder(root->right);
}

递归展开图:

在这里插入图片描述

3. 后续遍历

后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。

即, 每一棵树,先访问左子树, 在访问右子树, 最后访问根

void PostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	PostOrder(root->left);
	PostOrder(root->right);
	printf("%d ", root->data);
}

4. 二叉树结点的个数

求二叉树结点的个数, 这里初学者很容易先到使用遍历来求结点个数,如下:

// 错误示范
int TreeSize(BTNode* root)
{
	static int size = 0;
	if (root == NULL)
		return 0;
	else
		++size;

	TreeSize(root->left);
	TreeSize(root->right);

	return size;
}

首先做个铺垫再来讲解这段代码的问题

静态全局变量和静态局部变量的区别在于作用域和生存周期。

静态全局变量: 静态全局变量在整个文件中都是可见的,即其作用域为整个文件。静态全局变量只能被当前文件内的函数访问,其他文件无法访问。静态全局变量的生存周期为整个程序的执行期间,即在程序启动时分配内存,在程序结束时释放内存。

静态局部变量: 静态局部变量只在定义它的函数内部可见,即其作用域为定义它的函数内部。静态局部变量的生存周期为整个程序的执行期间,即在程序启动时分配内存,在程序结束时释放内存。与普通局部变量不同的是,静态局部变量只会在第一次进入函数时初始化一次,之后每次进入函数都会保留上一次的值。

静态全局变量和静态局部变量都可以被修改,但是有一些区别:

静态全局变量: 静态全局变量可以被当前文件内的任何函数修改,因为其作用域为整个文件。其他文件无法直接修改静态全局变量。但是,由于其作用域广泛,可能会被不同函数多次修改,导致程序的可维护性降低。

静态局部变量: 静态局部变量只能在定义它的函数内部被修改,其他函数无法直接修改静态局部变量。由于其作用域限制在函数内部,静态局部变量对于其他函数来说是不可见的,因此可以更好地控制变量的访问权限。此外,静态局部变量在函数调用之间保持其值不变,可以用于在函数调用之间保持状态或者记录某些信息。

因为每次调用函数size都是在栈区开辟的局部变量, 当函数结束后局部变量也会被系统回收, 这里用了static来修饰size, 看似没什么问题, 但是如果多次调用函数问题就来了

在这里插入图片描述
那么如何解决, 我们可以使用全局变量:

int size = 0;
int TreeSize(BTNode* root)
{
	if (root == NULL)
		return 0;
	else
		++size;

	TreeSize(root->left);
	TreeSize(root->right);

	return size;
}

void TreeSize(BTNode* root, int* psize)
{
	if (root == NULL)
		return 0;
	else
	++(*psize);

	TreeSize(root->left, psize);	
	TreeSize(root->right, psize);
}

但是上述的写法虽然可以, 但是每次调用之前都需要重置size的值,过于麻烦

int size = 0;
TreeSize(root, &size);
printf("TreeSize:%d\n",size);

size = 0;
TreeSize(root, &size);
printf("TreeSize:%d\n", size);

最好的解决方案还是回归本质, 因为二叉树本身就是递归定义的, 我们采用递归的思想, 分而治之, 问题就迎刃而解

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

首先分为左子树和右子树, 每一棵树都有左子树和右子树, 我们只需要统计每一棵树的左子树和右子树总结点个数加上自己.为空就等于0, 不为空左子树结点个数加上右子树结点个数, 返回给上一层
在这里插入图片描述

5. 二叉树叶子结点个数

叶子结点的个数, 空树就返回0, 递归的思想, 如果左子树和右子树都是空, 则就是叶子节点, 返回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);
}

6. 二叉树的高度

还是采用递归的思想, 一棵树的高度等于左子树的高度与右子树的高度较高的那个树加+1, 结束条件空树就是0.

在这里插入图片描述

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

7. 二叉树第K层结点的个数

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-1层结点的个数, 如果k==1则返回1.

在这里插入图片描述

8. 二叉树查找值为x的结点

BTNode* BinaryTreeFind(BTNode* root, BTDatatype x)
{
	if (root == NULL)
	{
		return NULL;
	}
	if (root == x)
	{
		return root;
	}
	BTNode* ret1 = BinaryTreeFind(root->left, x);
	if (ret1)
	{
		return ret1;
	}
	BTNode* ret2 = BinaryTreeFind(root->right, x);
	if (ret2)
	{
		return ret2;
	}
	return NULL;

}

这里要返回一个结点, 因为是递归调用函数, 所以返回值会逐层返回, 将树划分为子问题, 现在如果为NULL,则返回NULL, 如果找到了就返回该结点, 没找到就调用函数去查找左子树与右子树, 如果左子树找到了就返回左子树的结点不需要去右子树查找了, 如果都没找到则返回NULL.

全部代码

Tree.h

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

typedef int BTDatatype;

typedef struct BTNode
{
	BTDatatype data;
	struct BTNode* left;
	struct BTNode* right;
}BTNode;

BTNode* BuyNode(int x);

void PrevOrder(BTNode* root);
void InOrder(BTNode* root);
void PostOrder(BTNode* root);

//二叉树结点的个数
int BinaryTreeSize(BTNode* root);
//二叉树叶子结点个数
int BinaryTreeLeafSize(BTNode* root);
//二叉树的高度
int TreeHeight(BTNode* root);

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

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

Tree.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Tree.h"

BTNode* BuyNode(int x)
{
	BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	newnode->data = x;
	newnode->left = NULL;
	newnode->right = NULL;

	return newnode;
}

void PrevOrder(BTNode* root) 
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	printf("%d ", root->data);
	PrevOrder(root->left);
	PrevOrder(root->right);
}

void InOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	InOrder(root->left);
	printf("%d ", root->data);
	InOrder(root->right);
}

void PostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	PostOrder(root->left);
	PostOrder(root->right);
	printf("%d ", root->data);
}

int BinaryTreeSize(BTNode* root)
{
	return root == NULL ? 0 : BinaryTreeSize(root->left) + BinaryTreeSize(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);
}

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

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

BTNode* BinaryTreeFind(BTNode* root, BTDatatype x)
{
	if (root == NULL)
	{
		return NULL;
	}
	if (root == x)
	{
		return root;
	}
	BTNode* ret1 = BinaryTreeFind(root->left, x);
	if (ret1)
	{
		return ret1;
	}
	BTNode* ret2 = BinaryTreeFind(root->right, x);
	if (ret2)
	{
		return ret2;
	}
	return NULL;

}

test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Tree.h"

BTNode* CreateTree()
{
	BTNode* node1 = BuyNode(1);
	BTNode* node2 = BuyNode(2);
	BTNode* node3 = BuyNode(3);
	BTNode* node4 = BuyNode(4);
	BTNode* node5 = BuyNode(5);
	BTNode* node6 = BuyNode(6);


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

	return node1;
}


int main()
{
	BTNode* root = CreateTree();
	PrevOrder(root);
	//InOrder(root);
	//PostOrder(root);
	//int size = BinaryTreeSize(root);
	//int size = TreeHeight(root);
	//int size = BinaryTreeLeafSize(root);
	//int size = BinaryTreeLevelKSize(root,3);
	//printf("%d ", size);
	return 0;
}

总结

关于二叉树这块比较复杂, 递归的思路需要清晰, 每一次递归调用的作用是什么, 为什么要这样写, 写代码之前手动画一遍递归调用简图, 只要思路通了, 代码就顺了, 不要觉得是在浪费时间, 因为时间本就是来解决问题的.


完, 感谢关注~

  • 31
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

酷酷学!!!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值