文章目录
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;elsereturn 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-1return 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;elsereturn 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-1return 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;}//排除空树,且只要不等于根结点的值,就返回falseif(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;}//一个为空一个不为空或递归到一个为空一个不为空时返回falseif(p==NULL||q==NULL){return false;}//值不相等,返回falseif(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);
}