文章目录
6、通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
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)是按照某种特定的规则,依次对二叉树中的结点进行相应的操作,并且每个结点只操作一次。访问结点所做的操作依赖于具体的应用问题。 遍历 是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。

按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历:
-
前序遍历(先序遍历)------访问根结点的操作发生在遍历其左右子树之前,根->左子树->右子树。
-
中序遍历------访问根结点的操作发生在遍历其左右子树之中(间),左子树->根->右子树。
-
后序遍历------访问根结点的操作发生在遍历其左右子树之后,左子树->右子树->根。
由于被访问的结点必是某子树的根,所以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、单值二叉树
题目描述:

示例:

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、检查两颗树是否相同
题目描述:

示例:

首先判断如果都是空树时返回真;一个为空一个不为空时返回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、另一棵树的子树
题目描述:

不断递归遍历一棵树的左子树和右子树,然后判断这棵树的左子树与右子树与另一棵树是否相等。可以借助前面实现的判断两棵树是否相等的函数。
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);
}