【数据结构】二叉树接口实现指南:递归方法的高效运用 (附经典算法OJ)

文章目录

1、前置说明

1、定义二叉树结点结构

2、创建新节点

3、手动创建二叉树

2、二叉树的遍历

1、前序,中序和后序遍历

1.1、二叉树前序遍历

1.2、二叉树中序遍历

1.3、二叉树后序遍历

2、二叉树层序遍历

3、二叉树的基础操作

1、二叉树节点总数

2、叶子节点个数

3、二叉树高度

4、二叉树第k层节点数

5、查找目标节点

6、通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树

7、销毁

4、经典算法OJ

1、单值二叉树

2、检查两颗树是否相同

3、另一棵树的子树


1、前置说明

前面的文章我们根据完全二叉树的结构特点,用顺序存储的方式实现了堆相关的接口以及堆排序。下面我们用链式结构来实现普通二叉树的基本操作,但在实现之前,我们需要先创建一棵二叉树,然后才能学习相关的操作,由于我们对二叉树的结构掌握还不够深入,我们就先手动地创建一棵二叉树,等我们对二叉树的结构有了进一步的理解,再反过来实现二叉树真正的创建。

二叉树是: 1、空树

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

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

1、定义二叉树结点结构

用链式结构实现二叉树,节点结构就类似于链表的节点。同时,我们还需要两个结构体类型的指针来连接孩子节点。

复制代码
//定义二叉树存储的数据类型
typedef int BTDateType;
//定义节点结构
typedef struct BinaryTree
{
	BTDateType val;
	struct BinaryTree* left;
	struct BinaryTree* right;
}BTNode;

2、创建新节点

复制代码
BTNode* BuyBTNode(BTDateType x)
{
	BTNode* pst = (BTNode*)malloc(sizeof(BTNode));
	if (pst == NULL)
	{
		perror("malloc");
		exit(1);
	}
	pst->val = x;
	pst->left = NULL;
	pst->right = NULL;
}

3、手动创建二叉树

复制代码
BTNode* BinaryTreeCreat()
{
	//创建二叉树的节点
	BTNode* s1 = BuyBTNode(1);
	BTNode* s2 = BuyBTNode(2);
	BTNode* s3 = BuyBTNode(3);
	BTNode* s4 = BuyBTNode(4);
	BTNode* s5 = BuyBTNode(5);
	BTNode* s6 = BuyBTNode(6);

    //连接节点成为二叉树
	s1->left = s2;
	s2->left = s3;
	s2->right = s7;
	s1->right = s4;
	s4->left = s5;
	s4->right = s6;
	return s1;
}

2、二叉树的遍历

1、前序,中序和后序遍历

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

按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历:

  1. 前序遍历(先序遍历)------访问根结点的操作发生在遍历其左右子树之前,根->左子树->右子树。

  2. 中序遍历------访问根结点的操作发生在遍历其左右子树之中(间),左子树->根->右子树。

  3. 后序遍历------访问根结点的操作发生在遍历其左右子树之后,左子树->右子树->根。

由于被访问的结点必是某子树的根,所以N(Node)、L(Left subtree)和R(Right subtree)又可解释为 根、根的左子树和根的右子树。NLR、LNR和LRN分别又称为先根遍历、中根遍历和后根遍历。

复制代码
// 二叉树前序遍历 
void prevOrder(BTNode* root);
// 二叉树中序遍历
void midOrder(BTNode* root);
// 二叉树后序遍历
void afterOrder(BTNode* root);

1.1、二叉树前序遍历

前序遍历递归图解:

复制代码
//前序遍历:根  左子树  右子树
void prevOrder(BTNode* root)
{
    //打印空节点
	if (root == NULL)
	{
		printf("N ");
		return;
	}
    //打印根节点的值
	printf("%d ", root->val);
    //递归遍历左子树
	prevOrder(root->left);
    //递归遍历右子树
	prevOrder(root->right);
}

输出结果:

复制代码
1 2 3 N N N 4 5 N N 6 N N

为了能够更加直观的理解递归调用的过程,我们画图来感受一下,后面用递归实现接口的过程中,我们也可以用类似的方法来画图,帮助我们理解!!!

1.2、二叉树中序遍历

复制代码
//中序遍历:左子树  根  右子树
void midOreder(BTNode* root)
{   
    //打印空节点
	if (root == NULL)
	{
		printf("N ");
		return;
	}
    //递归遍历左子树
	midOreder(root->left);
    //打印根节点的值
	printf("%d ", root->val);
    //递归遍历右子树
	midOreder(root->right);
}

输出结果

复制代码
N 3 N 2 N 1 N 5 N 4 N 6 N

1.3、二叉树后序遍历

复制代码
//后序遍历:左子树  右子树  根
void afterOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
    //递归遍历左子树
	afterOrder(root->left);
    //递归遍历右子树
	afterOrder(root->right);
    //打印根节点的值
	printf("%d ", root->val);
}

输出结果

复制代码
N N 3 N 2 N N 5 N N 6 4 1

2、二叉树层序遍历

**层序遍历:**除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根结点所在 层数为1,层序遍历就是从所在二叉树的根结点出发,首先访问第一层的树根结点,然后从左到右访问第2层 上的结点,接着是第三层的结点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。

3、二叉树的基础操作

1、二叉树节点总数

思路1:遍历二叉树,创建一个变量来记录节点个数,但是,每次函数调用都是在栈区开辟空间,数据在函数调用结束后就会销毁,所以要创建全局变量,或者用static修饰的全局变量(但是在调用之前需要初始化为0)。当遇到某个节点的左孩子或者右孩子为空时,递归结束,开始返回。

复制代码
int size = 0;//全局变量
//static int size = 0;

int TreeSize(BTNode* root)
{
    //递归结束,遇到空树,返回
	if (root == NULL)
		return 0;
	else
		++size;
    //遍历二叉树
	TreeSize(root->left);
	TreeSize(root->right);

	return size;
}

还有一种简单的写法,用三目操作符来判断,但是不容易理解

复制代码
int TreeSize(BTNode* root)
{
	return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}

递归调用图解:

思路2:将size作为实参,但是为了让形参改变的同时改变实参的值,我们需要传实参的地址。

复制代码
//将size作为参数
void TreeSize(BTNode* root, int* psize)
{
	if (root == NULL)
		return 0;
	else
		++(*psize);

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

2、叶子节点个数

思路:遍历二叉树,将二叉树不断分为左子树与右子树,找左孩子与右孩子都不存在的节点,即叶子节点,然后将所有左子树与右子树的叶子节点加起来。

复制代码
//计算叶子节点个数
int TreeLeafCount(BTNode* root)
{
    //遇到空节点,递归结束条件
	if (root == NULL)
	{
		return 0;
	}
    //当左子树与右子树同时为空时为叶子节点
	if ((root->left == NULL) && (root->right == NULL))
	{
		return 1;
	}
    //叶子节点数等于左子树+右子树
	return TreeLeafCount(root->left) + TreeLeafCount(root->right);
}

3、二叉树高度

思路:递归遍历二叉树的左子树与右子树的同时,记下左子树与右子树的高度,然后比较左子树与右子树,二叉树的高度就是较高的一个加上根节点的高度。

复制代码
//计算二叉树高度
int TreeHeight(BTNode* root)
{
    //递归结束条件
	if (root == NULL)
	{
		return 0;
	}

	//记下返回值,防止造成时间效率低下
    //左子树高度
	int leftheight = TreeHeight(root->left);
    //右子树高度
	int rightheight = TreeHeight(root->right);
	//高度为较大的高度+根节点的高度
	if (leftheight > rightheight)
		return leftheight + 1;
	else
		return rightheight + 1;
	//三目操作符
	//return leftheight > rightheight ? leftheight + 1 : rightheight + 1;
}

4、二叉树第k层节点数

思路:计算二叉树第k层节点数,递归遍历二叉树的左子树与右子树,计算第k-1层的节点的不为空的左孩子和右孩子的个数,返回左子树与右子树k-1层的节点的不为空的左孩子和右孩子的个数的和。

复制代码
//计算第k层有多少个节点
int TreeNodecount_k(BTNode* root, int k)
{
    //递归结束条件
	if (root == NULL)
		return 0;
    //处理特殊情况
	if (k == 1)
		return 1;

	//注意不能使用k--或者--k,这样递归一次,后一次调用相当于k-1-1
	return TreeNodecount_k(root->left, k - 1) + TreeNodecount_k(root->right, k - 1);
}

5、查找目标节点

递归遍历左子树与右子树

复制代码
//查找目标节点
BTNode* TreeFind(BTNode* root, BTDateType x)
{
	if (root == NULL)
		return NULL;
	if (root->val == x)
	{
		return root;
	}
	//记下返回值
	BTNode* ret1 = TreeFind(root->left, x);
	//找到了,就返回
	if (ret1)
		return ret1;
	BTNode* ret2 = TreeFind(root->right, x);
	if (ret2)
		return ret2;
	return NULL;
}

6、通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树

复制代码
//定义二叉树存储的数据类型
typedef char BTDateType;

ABC##DE#G##F### 其中"#"表示的是空格,空格字符代表空树。

复制代码
//创建一颗二叉树
BTNode* CreatTree(char* a, int* pi)
{
    //递归结束条件,同时处理空树
    if (a[*pi] == '#')
    {
        (*pi)++;
        return NULL;
    }
    //不是'#'就为节点开辟空间
    BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
    
    newnode->val = a[(*pi)++];
    //递归遍历数组
    newnode->left = CreatTree(a, pi);
    newnode->right = CreatTree(a, pi);
    return newnode;
}

int main()
{
    char arr[] = { "ABD##E#H##CF##G##" };
    int i = 0;
    BTNode* root = CreatTree(arr, &i);
    return 0;
}

7、销毁

思路:递归遍历二叉树,用后序遍历,防止将根释放而找不到左子树与右子树。

复制代码
//销毁二叉树
//后序遍历:左子树->右子树->根,防止将根释放而找不到左子树与右子树
void TreeDestry(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
    //递归遍历左子树
	TreeDestry(root->left);
    //递归遍历右子树
	TreeDestry(root->right);
	free(root);
}

4、完整源码

复制代码
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<math.h>
#include"Queue.h"

//定义二叉树的节点
typedef int BTDateType;
typedef struct BinaryTree
{
	BTDateType val;
	struct BinaryTree* left;
	struct BinaryTree* right;
}BTNode;

BTNode* BuyBTNode(BTDateType x)
{
	BTNode* pst = (BTNode*)malloc(sizeof(BTNode));
	if (pst == NULL)
	{
		perror("malloc");
		exit(1);
	}
	pst->val = x;
	pst->left = NULL;
	pst->right = NULL;
}
BTNode* BinaryTreeCreat()
{
	//创建一棵树
	BTNode* s1 = BuyBTNode(1);
	BTNode* s2 = BuyBTNode(2);
	BTNode* s3 = BuyBTNode(3);
	BTNode* s4 = BuyBTNode(4);
	BTNode* s5 = BuyBTNode(5);
	BTNode* s6 = BuyBTNode(6);
	BTNode* s7 = BuyBTNode(7);

	s1->left = s2;
	s2->left = s3;
	s2->right = s7;
	s1->right = s4;
	s4->left = s5;
	s4->right = s6;
	//s5->right = s7;
	return s1;
}
//前序遍历:根  左子树  右子树
void prevOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	printf("%d ", root->val);
	prevOrder(root->left);
	prevOrder(root->right);
}

//中序遍历:左子树  根  右子树
void midOreder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	midOreder(root->left);
	printf("%d ", root->val);
	midOreder(root->right);
}
//后序遍历:左子树  右子树  根
void afterOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	afterOrder(root->left);
	afterOrder(root->right);
	printf("%d ", root->val);
}

//计算节点总数
//每次函数调用都是在栈区开辟空间,数据在函数调用结束后就会销毁,所以要创建全局变量,或者用static修饰的全局变量(但是在调用之前需要初始化为0)

//错误示范:static修饰的变量在函数作用域内,又static修饰的变量不会销毁,所以在main函数中反复调用会导致结果不断增加
//int TreeNodeCount(BTNode* root)
//{
//	static int size = 0;
//	if (root == NULL)
//	{
//		return 0;
//	}
//	size++;
//	TreeNodeCount(root->left);
//	TreeNodeCount(root->right);
//	return size;
//}

int size = 0;//全局变量
//static int size = 0;

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

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

	return size;
}

////将size作为参数
//void TreeSize(BTNode* root, int* psize)
//{
//	if (root == NULL)
//		return 0;
//	else
//		++(*psize);
//
//	TreeSize(root->left, psize);
//	TreeSize(root->right, psize);
//}

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

//计算叶子节点个数
int TreeLeafCount(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	if ((root->left == NULL) && (root->right == NULL))
	{
		return 1;
	}
	return TreeLeafCount(root->left) + TreeLeafCount(root->right);
}


//计算二叉树高度
int TreeHeight(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}

	//记下返回值,防止造成时间效率低下
	int leftheight = TreeHeight(root->left);
	int rightheight = TreeHeight(root->right);
	//高度为较大的高度+根节点的高度
	if (leftheight > rightheight)
		return leftheight + 1;
	else
		return rightheight + 1;
	//三目操作符
	//return leftheight > rightheight ? leftheight + 1 : rightheight + 1;
}

////使用fmax()函数求较大的值
//int TreeHeight(BTNode* root)
//{
//	if (root == NULL)
//		return 0;
//
//	return fmax(TreeHeight(root->left), TreeHeight(root->right)) + 1;
//}

//计算第k层有多少个节点
int TreeNodecount_k(BTNode* root, int k)
{
	if (root == NULL)
		return 0;
	if (k == 1)
		return 1;

	//注意不能使用k--或者--k,这样递归一次,后一次调用相当于k-1-1
	return TreeNodecount_k(root->left, k - 1) + TreeNodecount_k(root->right, k - 1);
}

//查找目标节点
BTNode* TreeFind(BTNode* root, BTDateType x)
{
	if (root == NULL)
		return NULL;
	if (root->val == x)
	{
		return root;
	}
	//记下返回值
	BTNode* ret1 = TreeFind(root->left, x);
	//找到了,就返回
	if (ret1)
		return ret1;
	BTNode* ret2 = TreeFind(root->right, x);
	if (ret2)
		return ret2;
	return NULL;
}

//销毁二叉树
//后序遍历:左子树->右子树->根,防止将根释放而找不到左子树与右子树
void TreeDestry(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	TreeDestry(root->left);
	TreeDestry(root->right);
	free(root);
}

//测试
int main()
{
	BTNode* root = BinaryTreeCreat();
	//前序遍历
	prevOrder(root);
	printf("\n");

	//中序遍历
	midOreder(root);
	printf("\n");
	//后序遍历
	afterOrder(root);
	printf("\n");

	//计算二叉树节点总个数

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

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

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

	//printf("%d ", TreeSize(root));
	//printf("%d ", TreeSize(root));

	//求叶子节点的个数
	//printf("%d\n", TreeLeafCount(root));

	////求二叉树的高度
	//printf("%d\n", TreeHeight(root));

	////求二叉树第k层有多少个节点
	//printf("%d\n",TreeNodecount_k(root, 3));

	////查找目标节点
	//BTNode* ret = TreeFind(root, 5);
	//printf("%d\n", ret->val);

	return 0;
}

5、经典算法OJ

1、单值二叉树

965. 单值二叉树 - 力扣(LeetCode)

题目描述:

示例:

复制代码
bool _isUnivalTree(struct TreeNode* root,int x) 
{  
   //处理空树情况,同时为递归结束条件
   if(root==NULL)
   {
    return true;
   }

   //排除空树,且只要不等于根结点的值,就返回false
   if(root->left&&root->left->val!=x)
   {
    return false;
   }
   if(root->right&&root->right->val!=x)
   {
    return false;
   }

   //递归遍历左子树与右子树
   return _isUnivalTree(root->left, x)&&_isUnivalTree(root->right,x);
}

bool isUnivalTree(struct TreeNode* root) 
{
   return _isUnivalTree(root,root->val);
}

2、检查两颗树是否相同

100. 相同的树 - 力扣(LeetCode)

题目描述:

示例:

首先判断如果都是空树时返回真;一个为空一个不为空时返回false;值不相等,返回false。然后递归遍历左子树和右子树,左子树与左子树比较,右子树与右子树比较,当递归到都是空时返回true(与刚开始的判空对应),递归到一个为空一个不为空时返回false(与刚开始的判断一个为空一个不为空对应),最后左子树与左子树相等且右子树与右子树相等则返回true;反之,返回false。

复制代码
bool isSameTree(struct TreeNode* p, struct TreeNode* q) 
{
    //都是空树或递归到都是空时返回真
    if(p==NULL&&q==NULL)
    {
        return true;
    }
    //一个为空一个不为空或递归到一个为空一个不为空时返回false
    if(p==NULL||q==NULL)
    {
        return false;
    }
    //值不相等,返回false
    if(p->val!=q->val)
    {
        return false;
    }
    //递归遍历左子树和右子树,左子树与左子树比较,右子树与右子树比较
    return isSameTree(p->left,q->left)&&isSameTree(p->right,q->right);
}

3、另一棵树的子树

572. 另一棵树的子树 - 力扣(LeetCode)

题目描述:

不断递归遍历一棵树的左子树和右子树,然后判断这棵树的左子树与右子树与另一棵树是否相等。可以借助前面实现的判断两棵树是否相等的函数。

复制代码
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) 
{
    if(root==NULL)
    {
        return false;
    }
    if((root->val==subRoot->val) && (isSameTree(root,subRoot)))
    { 
        return true;
    }
    //将root拆分为左子树和右子树与subRoot比较
    return isSubtree(root->left,subRoot)||isSubtree(root->right,subRoot);
}
相关推荐
技术炼丹人29 分钟前
从RNN为什么长依赖遗忘到注意力机制的解决方案以及并行
人工智能·python·算法
闪电麦坤9542 分钟前
数据结构:反转链表(reverse the linked list)
数据结构·链表
NfN-sh1 小时前
计数组合学7.12( RSK算法的一些推论)
笔记·学习·算法
ikkkkkkkl1 小时前
LeetCode:15.三数之和&&18.四数之和
c++·算法·leetcode
屁股割了还要学1 小时前
【数据结构入门】链表
c语言·开发语言·数据结构·c++·学习·算法·链表
Mr数据杨2 小时前
数据与模型优化随机森林回归进行天气预测
算法·随机森林·回归
chen1112 小时前
有关人工智能(AI)的搜索算法(CS50)
算法
恣艺2 小时前
LeetCode 135:分糖果
算法·leetcode·职场和发展
TDengine (老段)2 小时前
TDengine 中 TDgp 中添加算法模型(异常检测)
java·大数据·数据库·算法·时序数据库·tdengine·涛思数据
2501_924748243 小时前
高密度客流识别精度↑32%!陌讯多模态融合算法在智慧交通的实战解析
大数据·人工智能·算法·目标检测·计算机视觉