二叉树前中后序遍历+结点+高度+刷题【数据结构初阶/二叉树/C语言实现】

普通链式二叉树没有意义,因而不学增删改查。

学习的目的是
1、为更复杂二叉树搜索->AVL树、红黑树,做铺垫
2、很多二叉树的题,是出在这一块

准备&链式二叉树的结构

image.png|275
自己手动构建一棵二叉树

#include<stdio.h>
#include<stdlib.h>
typedef struct BinaryTreeNode//定义一个节点的结构
{
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
	int val;
}BTNode;

BTNode* BuyNode(int x)//创建并初始化一个节点
{
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	if (node == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	node->val = x;
	node->left = NULL;
	node->right = NULL;

	return node;
}

int main()
{
	// 手动构建
	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 0;
}

二叉树的操作

二叉树的销毁

//二级指针传递
//一级指针只需要在调用完函数之后,在将root置空就好。
void BinaryTreeDestroy(BTNode** root) {
    if (*root == NULL)
        return;

    // 后序遍历进行销毁,这样子才能往下找到叶子结点,并往上删除
    BinaryTreeDestroy(&((*root)->_left));
    BinaryTreeDestroy(&((*root)->_right));

    free(*root);
    *root = NULL; // 将根节点指针置为空,避免出现悬挂指针
}

遍历(递归实现)

递归十分重要,且后续的题目是用二叉树来完成的

前序遍历

前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。即根-左子树-右子树

代码实现

void PrevOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("# ");//以“#”代替空子树
		return;
	}

	printf("%d ", root->val);
	PrevOrder(root->left);
	PrevOrder(root->right);
}

下图为模拟递归过程
image.png
打印结果为1 2 3 # # # 4 5 # # 6 # #

中序遍历

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

void InOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("# ");//以“#”代替空子树
		return;
	}

	InOrder(root->left);
	printf("%d ", root->val);
	InOrder(root->right)   ;
}

image.png

打印结果为# 3 # 2 # 1 # 5 # 4 # 6 #

后序遍历

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

void PostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("# ");//以“#”代替空子树
		return;
	}

	PostOrder(root->left);
	PostOrder(root->right);
	printf("%d ", root->val);
}

image.png
打印结果为# # 3 # 2 # # 5 # # 6 4 1

层序遍历

把一个结点push进一个队列中,并不会使结点的联系断开
因为我们push的是一个struct,在这个struct里面有int val,strcut* left和right

typedef struct BinaryTreeNode* QDataType;

void BinaryTreeLevelOrder(BTNode* root)
{
	//创建并初始化队列
	Queue q;
	QueueInit(&q);
	//把根结点放入
	if (root)
	{
		QueuePush(&q, root);
	}
	//每次只取队头元素(当前的根结点),
	//然后将当前根结点的非空孩子结点存入
	//终止条件:队空
	while (!QueueEmpty(&q))
	{
		//取队头元素后pop
		BTNode* front = QueueFront(&q);
		printf("%d ", front->data);
		QueuePop(&q);

		//如果有左孩子,则将左孩子的结点push进队列
		if (front->left)
		{
			QueuePush(&q, front->left);
		}
		if (front->right)
		{
			QueuePush(&q, front->right);
		}
	}
	QueueDestory(&q);
}

关于递归(复习)

总结递归
1. 确定递归函数和参数返回值
2. 终止条件
3. 单层递归的逻辑

递归的结束
并不是结束程序本身,而是回到调用当前函数的位置。结合函数栈帧理解: 调用一次函数,创建该函数的栈帧,而返回即销毁栈帧。上述的代码中结点的地址是作为参数,以局部变量的形式存在于函数栈帧中。

分治算法
对于一棵二叉树,我们可以看成由许多个小二叉树组成。递归到最后就是一片叶子(可以看作一个根外加两个空结点)。
这种分而治之的思想,就是分治算法的本质:即将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。

广度优先遍历与深度优先遍历

广度优先遍历:(Breadth First Search——BFS)是一种先访问同一层节点,再访问下一层节点的遍历方式。层序遍历
深度优先遍历:(Depth First Search——DFS)是一种先访问根节点,然后递归地访问每个子节点的遍历方式。前序遍历

结点及其个数

二叉树查找值为x的节点

BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
    if (root == NULL)
        return NULL; // 如果根节点为空,直接返回 NULL

    if (root->_data == x)
        return root; // 如果根节点的值等于 x,返回根节点

    // 递归地在左子树中查找值为 x 的节点
    BTNode* leftResult = BinaryTreeFind(root->_left, x);
    if (leftResult != NULL)
        return leftResult; // 如果左子树中找到了,直接返回结果

    // 递归地在右子树中查找值为 x 的节点
    BTNode* rightResult = BinaryTreeFind(root->_right, x);
    return rightResult; // 返回右子树中查找的结果,可能是 NULL 或者找到的节点
}

二叉树总结点个数

这里的计数器应该是全局变量。如果是局部变量,随着栈帧的创建与销毁,每个计数器在各自的栈帧是独立的,起不到计数的作用。

int TreeSize(BTNode* root)
{
	return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
	//左右孩子结点都走完了,到父母结点+1返回上去
}

image.png

二叉树叶子结点个数

递到叶结点root->left == NULL && root->right == NULL,
且非叶子结点的空结点也要返回0

int TreeLeafSize(BTNode* root)
{
	//只是空,防止传入空指针
	if (root == NULL)
	{
		return 0;
	}
	//是叶子
	if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}

	return TreeLeafSize(root->left) + TreeSize(root->right);
}

image.png

第k层的结点个数

递到k层才返回1,遇到空结点返回0
当前树的第k层 = 第k-1层的左子树结点个数 + 第k-1层的右子树结点个数

int TreeKLevel(BTNode* root, int k)
{
	if (root == NULL)
	{
		return 0;
	}

	if (k == 1)
	{
		return 1;
	}
	
	return TreeKLevel(root->left,k-1) + TreeKLevel(root->right,k-1);
}

image.png

二叉树的高度

其实也就是计算二叉树的深度,在后面

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

完全二叉树在队列中只要遇见了第一个NULL,那么队列后面的内容就全是NULL,而非完全二叉树在队列中遇见了第一个NULL后,后面还会遇见非空元素
// 判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	QueuePush(&q, root);
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);

		if (front == NULL)//当遇见第一个NULL时就直接跳出循环来到下面的语
		{
			break;
		}
		else
		{
			QueuePush(&q, front->left);
			QueuePush(&q, front->right);
		}
	}

	// 遇到空了以后,检查队列中剩下的节点
	// 1、剩下全是空,则是完全二叉树
	// 2、剩下存在非空,则不是完全二叉树
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);

		if (front)//如果不为空就返回false
		{
			QueueDestroy(&q);
			return false;
		}
	}

	QueueDestroy(&q);
	return true;//全部节点都走完了没有发现非空,说明队列里全是空就返回true.
}

二叉树OJ基础练习题

单值二叉树

image.png|300

bool isUnivalTree(struct TreeNode* root) {
	if (root == NULL)
		return true;

	if (root->left && root->left->val != root->val)
		return false;

	if (root->right && root->right->val != root->val)
		return false;

	return isUnivalTree(root->left) && isUnivalTree(root->right);
}

二叉树的最大深度

image.png|350

int maxDepth(struct TreeNode* root) {
	if (root == NULL)
		return 0;
	int leftDepth = maxDepth(root->left);
	int rightDepth = maxDepth(root->right);
	return leftDepth > rightDepth ? leftDepth + 1 : rightDepth + 1;
}

image.png
区别
image.png

相同的树

image.png|400

bool isSameTree(struct TreeNode* p, struct TreeNode* q) {

	//判断根节点是否都为空,或者是叶子结点
	if (p == NULL && q == NULL)
		return true;

	//如果是只有一个为空,那就是不同的树
	if (p == NULL || q == NULL)
		return false;

	//对值进行判断
	if (p->val != q->val)
		return false;

	return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}

二叉树的前序遍历

重点是不能忽略了栈帧销毁导致计数器的失效

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

void preorder(struct TreeNode* root, int* a, int* pi)
{
	if (root == NULL)
		return;

	a[(*pi)++] = root->val;
	//要用指针是因为每次栈帧的创建销毁,都会是计数器失效
	preorder(root->left, a, pi);
	preorder(root->right, a, pi);
}
int* preorderTraversal(struct TreeNode* root, int* returnSize) {
	//int* returnSize为输出型参数,可以理解为返回给力扣后台的一个参数,这里是让我们返回一个树的大小
	// //因此我们需要计算出树的大小    
	// int n = TreeSize(root);

	// //为二叉树的结点开辟数组,以供放入
	// int* a = (int*)malloc(sizeof(int)*n);
	int n = TreeSize(root);
	int* a = (int*)malloc(sizeof(int) * n);

	int j = 0;
	preorder(root, a, &j);//遍历的同时,并把结点数值放入数组
	*returnSize = n;

	return a;
}

另一棵树的子树

image.png|275

bool isSameTree(struct TreeNode* p, struct TreeNode* q)
{
	if (p == NULL && q == NULL)
		return true;

	if (p == NULL || q == NULL)
		return false;

	if (p->val != q->val)
		return false;

	return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}

bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot) {
	//题目给出不存在空树,所以如果root递到空,就说明这一分支没有相同的子树
	if (root == NULL)
		return false;

	if (root->val == subRoot->val)
	{
		//以相同的结点开始,往下判断子树是否是相同。
		if (isSameTree(root, subRoot))
			return true;
	}

	//没找到相同结点(为起始判断),那就继续往下递
	//只需要其中一个分支有相同就好,所以用`||`
	return isSubtree(root->left, subRoot) || isSubtree(root->right, subRoot);

	//此处别写成
	//return isSubtree(root->left,subRoot->left) || isSubtree(root->right,subRoot->right);
	//被比较的是一个完整的树。。。
}

对称二叉树

image.png|300
题目相同的树本质一样,但巧妙地将一棵树拆解成两棵

bool isSymmetricTree(struct TreeNode* p, struct TreeNode* q)
{
	if (p == NULL && q == NULL)
		return true;

	if (p == NULL || q == NULL)
		return false;

	if (p->val != q->val)
		return false;
	//左边等于右边即为对称
	return isSymmetricTree(p->left, q->right) && isSymmetricTree(p->right, q->left);
}
bool isSymmetric(struct TreeNode* root) {
	return isSymmetricTree(root->left, root->right);
	//拆成左右两棵树
}

翻转二叉树

image.png|375

struct TreeNode* invertTree(struct TreeNode* root) {
    if (root == NULL)
        return NULL;

    struct TreeNode* left = invertTree(root->left);
    struct TreeNode* right = invertTree(root->right);

    root->left = right;
    root->right = left;
    return root;
}
1. 如果当前节点为空,则直接返回空。
2. 递归地翻转左子树和右子树,得到左子树和右子树的翻转结果 `left` 和 `right`。
3. 将当前节点的左子树指针指向 `right`,右子树指针指向 `left`,完成当前节点的翻转。
4. 返回当前节点。

其他关于二叉树选择题(我的错题)

  1. N 0 = N 2 + 1 N_0 = N_2+1 N0=N2+1( N 0 N_0 N0为度数为0的结点, N 2 N_2 N2为度数为2的结点)

  2. 一棵非空的二叉树的先序遍历序列与后序遍历序列正好相反,则该二叉树一定满足,每个结点只有一个孩子,只有一个叶子结点
    image.png|275

  3. 如果一颗二叉树的前序遍历的结果是ABCD,则满足条件的不同的二叉树有( )种
    image.png


  1. image.png|350
    前序确定根,中序找到根确定根的左右子树image.png|186
  • 12
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值