一. 前置说明
在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。由于现在大家对二叉树结构掌握还不够深入,为了降低大家学习成本,此处手动快速创建一棵简单的二叉树,快速进入二叉树操作学习,等二叉树结构了解的差不多时,我们反过头再来研究二叉树真正的创建方式
typedef int BTDataType;
typedef struct BinaryTreeNode
{
BTDataType _data;
struct BinaryTreeNode* _left;
struct BinaryTreeNode* _right;
}BTNode;
BTNode* CreatBinaryTree()
{
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 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。(根 左子树 右子树)
- 中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。(左子树 根 右子树)
- 后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。(左子树 右子树 根)
由于被访问的结点必是某子树的根,所以N(Node)、L(Left subtree)和R(Right subtree)又可解释为根、根的左子树和根的右子树。NLR、LNR和LRN分别又称为先根遍历、中根遍历和后根遍历
// 二叉树前序遍历
void PreOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
printf("%d ", root->val);
PreOrder(root->left);
PreOrder(root->right);
}
// 二叉树中序遍历
void InOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
PreOrder(root->left);
printf("%d ", root->val);
PreOrder(root->right);
}
// 二叉树后序遍历
void PostOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
PreOrder(root->left);
PreOrder(root->right);
printf("%d ", root->val);
}
前序遍历递归图解:
- 前序遍历结果:1 2 3 4 5 6
- 中序遍历结果:3 2 1 5 4 6
- 后序遍历结果:3 1 5 6 4 1
2. 层序遍历
层序遍历:除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根节点所在层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第2层上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历
- 若要进行层序遍历,则要用到队列的思想
- 先将根节点 (root) 导入队列中,再获取队列中的头部节点(root)
- 然后将该节点中的数据打印
- 再判断该节点的左子树是否为空,若不为空,则将左子树导入队列
- 再判断该节点的右子树是否为空,若不为空,则将右子树导入队列
- 最后将根节点(root)导出队列,重复以上操作,直到队列为空
// 层序遍历
void LevelOrder(BTNode* root)
{
Que ps;
QueInit(&ps);
if (root)
{
QuePush(&ps, root);
}
while (!QueEmpty(&ps))
{
BTNode* Front = QueFront(&ps);
printf("%d ", Front->val);
if (Front->left)
{
QuePush(&ps, Front->left);
}
if (Front->right)
{
QuePush(&ps, Front->right);
}
QuePop(&ps);
}
QueDestroy(&ps);
}
练习:请写出下面的前序/中序/后序/层序遍历
- 前序遍历结果:A B D E H C F G
- 中序遍历结果:D B E H A F C G
- 后序遍历结果:D H E B F G C A
- 层序遍历结果:A B C D E F G H
3. 节点个数以及高度等
为了方便代码的测试,先手动构建节点
typedef int BTDataType;
typedef struct BinaryTreeList
{
struct BinaryTreeList* left;
struct BinaryTreeList* right;
BTDataType val;
}BTNode;
//构建树节点
BTNode* BuyNode(int x)
{
BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(-1);
}
newnode->val = x;
newnode->left = NULL;
newnode->right = NULL;
return newnode;
}
int main()
{
// 手动构建
BTNode* node1 = BuyNode(1);
BTNode* node2 = BuyNode(2);
BTNode* node3 = BuyNode(3);
BTNode* node4 = BuyNode(4);
BTNode* node5 = BuyNode(5);
node1->left = node2;
node1->right = node5;
node2->left = node3;
node2->left = node4;
return 0;
}
二叉树节点的个数:
- 这里采用后序遍历,先判断左子树是否为空树,再判断右子树是否为空树
- 因为空树并不算一个节点,所以这里遇到空树就返回 0
- 当左右子树都判断完,最后加上根节点(+1)
// 二叉树节点个数
int BinaryTreeSize(BTNode* root)
{
return root == NULL ? 0 : BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}
二叉树叶子节点个数:
- 首先,叶子节点是指不存在孩子的节点
- 若一棵树没有任何节点,则为空树,空树的叶子节点个数为 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);
}
二叉树第k层节点个数:
- 在二叉树中,若要直接定位到树的第K层,去计算节点个数,这是不容易控制的
- 所以,要利用相对位置的思想来解决问题,从第一层到第K层,要走K层,若从第二层到第K层,则要走K-1层
- 所以从根节点往左右子树进行遍历,每要向下一层,都要对K-1,直到K=1时,已经来到了第K层,则返回 1
// 二叉树第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);
}
4. 二叉树基础oj练习
单值二叉树:
- 若二叉树每个节点都具有相同的值,那么该二叉树就是单值二叉树
- 对于空树,它没有节点,它也是单值二叉树
- 从根节点开始向左右子树进行遍历,若左子树存在,且值相等,则返回 true
- 若右子树存在,且值相等,则返回 true
- 左右子树同时为真,则该二叉树才为单值二叉树
// 单值二叉树
bool isUnivalTree(struct TreeNode* root){
if(root == NULL)
{
return true;
}
if(root -> left && root -> left -> val != root -> val )
{
return false;
}
else if(root -> right && root ->right -> val != root -> val)
{
return false;
}
return isUnivalTree(root->left) && isUnivalTree(root->right);
}
检查两颗树是否相同:
- 从两棵树的树根开始判断,若两棵树同时为空树,则它们相等
- 若一个为空,一个不为空,则它们不相等
- 若两棵树都存在,但根的值不相等,则它们不相等
- 判断完根后,递归判断根的左右子树,若左右子树都相等,则这两棵树是相同的
// 检查两颗树是否相同
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);
}
对称二叉树:
- 判断对称二叉树的思想是与单值二叉树相同的
- 首先,从根节点(A)的左右子树(B,C)进行判断,若左右子树都为空,则该树是对称二叉树
- 若一个为空,一个不为空,则该树一定不对称,此时判断来判断左右子树的值是否相等
- 若相等,则进行递归,继续判断左子树(B)的左子树(D)与右子树©的右子树(G)是否相等
- 若相等,再判断左子树(B)的右子树(E)与右子树©的左子树(F)是否相同,若相等,则返回 true
- 继续重复以上递归调用,直到判断出该树是否是对称二叉树
// 对称二叉树
bool Check(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 Check(p->left,q->right)&&Check(p->right,q->left);
}
bool isSymmetric(struct TreeNode* root){
return Check(root->left,root->right);
}
二叉树的前序遍历:
- 给定一棵二叉树,进行前序遍历,将遍历的结果保存到数组中,返回该数组和树的节点数量
- 首先,新建函数TreeSize来计算树节点的个数,然后开辟一个足够存放该二叉树所有节点的数组
- 再新建一个前序遍历的函数,进行遍历并将节点的数据依次放到数组中,遍历完成后返回数组和树节点的个数
// 二叉树的前序遍历
int TreeSize(struct TreeNode* root)
{
return root==NULL ? 0 : TreeSize(root->left)+TreeSize(root->right)+1;
}
void preorder(struct TreeNode* root,int* a,int* i)
{
if(root==NULL)
{
return;
}
a[(*i)++]=root->val;
preorder(root->left,a,i);
preorder(root->right,a,i);
}
int* preorderTraversal(struct TreeNode* root, int* returnSize){
int n=TreeSize(root);
int* a=(int*)malloc(sizeof(int)*n);
if(a==NULL)
{
perror("malloc fail");
exit(1);
}
int i=0;
preorder(root,a,&i);
* returnSize=n;
return a;
}
二叉树中序遍历:
// 二叉树中序遍历
int TreeSize(struct TreeNode* root)
{
return root==NULL ? 0 : TreeSize(root->left)+TreeSize(root->right)+1;
}
void inorder(struct TreeNode* root,int* a,int* i)
{
if(root==NULL)
{
return;
}
inorder(root->left,a,i);
a[(*i)++]=root->val;
inorder(root->right,a,i);
}
int* inorderTraversal(struct TreeNode* root, int* returnSize){
int n=TreeSize(root);
int* a=(int*)malloc(sizeof(int)*n);
if(a==NULL)
{
perror("malloc fail");
exit(1);
}
int i=0;
inorder(root,a,&i);
* returnSize=n;
return a;
}
二叉树的后序遍历:
// 二叉树的后序遍历
int TreeSize(struct TreeNode* root)
{
return root==NULL ? 0 : TreeSize(root->left)+TreeSize(root->right)+1;
}
void postorder(struct TreeNode* root,int* a,int* i)
{
if(root==NULL)
{
return;
}
postorder(root->left,a,i);
postorder(root->right,a,i);
a[(*i)++]=root->val;
}
int* postorderTraversal(struct TreeNode* root, int* returnSize){
int n=TreeSize(root);
int* a=(int*)malloc(sizeof(int)*n);
if(a==NULL)
{
perror("malloc fail");
exit(1);
}
int i=0;
postorder(root,a,&i);
* returnSize=n;
return a;
}
另一颗树的子树:
- 首先,判断两棵树的根(root,subRoot)是否相等
- 若相等,则调用isSameTree函数来判断,这两棵树是否相等,
- 若不相等,则进行递归,将根的左右子树(root->left,root->right)与另一棵树的根(subRoot)比较是否相等,若相等,则继续调用isSameTree函数
- 重复以上操作,直到判断出结果
// 另一颗树的子树
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)
{
if(isSameTree(root,subRoot))
{
return true;
}
}
return isSubtree(root->left,subRoot)||isSubtree(root->right,subRoot);
}
二叉树的最大深度:
- 按图讲解,首先,判断根节点为不为空,若不为空,则进行左子树(9)的递归,递归到空树时,返回 0
- 左子树递归完,递归右子树,若遇空树,也返回 0
- 此时比较若是 left > right,则 left + 1,若是 right > left,则 right + 1,左子树递归返回 1,开始递归右子树(20)
- 多次递归返回后,最后得出树的深度
// 二叉树的最大深度
int maxDepth(struct TreeNode* root){
if(root == NULL)
{
return 0;
}
int left = maxDepth(root -> left);
int right = maxDepth(root -> right);
return left>right ? left+1 : right+1;
}
翻转二叉树:
- 按图讲解,首先,判断根(4)是否为空,若为空,则不需要翻转
- 若不为空,创建一个struct TreeNode*类型的临时变量来保存根(4)的左子树(2)
- 接着将根(4)的右子树(7)覆盖到根(4)的左子树(2)
- 再将临时变量的值赋给左子树(2),然后进入左子树(2)的递归,去翻转左子树的所有孩子节点
- 左子树递归结束后,进行右子树的递归,去翻转右子树的所有孩子节点,最后,整棵树翻转成功
// 翻转二叉树
struct TreeNode* invertTree(struct TreeNode* root){
if(root==NULL)
{
return NULL;
}
struct TreeNode* tmp=NULL;
tmp=root->left;
root->left=root->right;
root->right=tmp;
invertTree(root->left);
invertTree(root->right);
return root;
}
5. 二叉树的创建和销毁
- 先创建字符数组 str,用来存储输入的字符串,‘#’ 表示空树
- 然后创建CreatTree函数来构建二叉树,按先序遍历的方式(根 左子树 右子树)将字符数组中的数据依次输入到二叉树中
- 再对该二叉树进行中序遍历(左子树 根 右子树)
// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
// 再对二叉树进行中序遍历
#include <stdio.h>
typedef struct BinaryTree
{
int val;
struct BinaryTree* left;
struct BinaryTree* right;
}BTNode;
BTNode* CreatTree(char* str,int* pi)
{
if(str[*pi]=='#')
{
(*pi)++;
return NULL;
}
BTNode* new=(BTNode*)malloc(sizeof(BTNode));
new->val=str[*pi];
(*pi)++;
new->left=CreatTree(str,pi);
new->right=CreatTree(str,pi);
return new;
}
void Inorder(BTNode* root)
{
if(root==NULL)
{
return;
}
Inorder(root->left);
printf("%c ",root->val);
Inorder(root->right);
}
int main() {
char str[100]={0};
scanf("%s",str);
int i=0;
BTNode* root = CreatTree(str,&i);
Inorder(root);
return 0;
}
二叉树的销毁:
- 首先,进行左子树的递归遍历,当该根节点的左右子树都为空树时,意味着该节点为叶子节点,并对其释放
- 处理完左子树,再来处理右子树,当左右子树都递归遍历完后,就对根(root)进行释放,并置空
// 二叉树销毁
void BinaryTreeDestory(BTNode** root)
{
if ((*root) == NULL)
{
return;
}
BinaryTreeDestory(&(*root)->left);
BinaryTreeDestory(&(*root)->right);
free(*root);
*root=NULL;
}
完全二叉树的定义:若二叉树的深度为 h,除第 h 层外,其它各层的结点数都达到最大个数,第 h 层所有的叶子结点都连续集中在最左边,这就是完全二叉树
- 首先,这里要用到层序遍历的思想实现,将根先导入队列中,再取队列的首位数据进行判断
- 若为空,则跳出循环,若不为空,就将该根的左右子树也导入队列,然后取出队列中的首位数据进行判断
- 重复以上操作,然后判断空节点的后面是否存在非空节点,若存在非空节点,则不是完全二叉树,若不存在,则是完全二叉树
#include <stdbool.h>
#include<assert.h>
typedef struct TreeNode* QDataType;
typedef struct QueueNode
{
struct Queue* next;
QDataType data;
}QNode;
typedef struct Queue
{
QNode* head;
QNode* tail;
int size;
}Que;
void QueInit(Que* ps)
{
assert(ps);
ps->head = ps->tail = 0;
ps->size = 0;
}
void QuePush(Que* ps, QDataType x)
{
assert(ps);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(1);
}
newnode->data = x;
newnode->next = NULL;
if (ps->head == NULL)
{
ps->head = ps->tail = newnode;
}
else
{
ps->tail->next = newnode;
ps->tail = newnode;
}
++ps->size;
}
void QuePop(Que* ps)
{
assert(ps);
assert(ps->size > 0);/// !QueEmpty(ps)
if (ps->head->next == NULL)
{
free(ps->head);
ps->head = ps->tail = NULL;
}
else
{
QNode* next = ps->head->next;
free(ps->head);
ps->head = next;
}
--ps->size;
}
bool QueEmpty(Que* ps)
{
assert(ps);
return ps->size == 0;// ps->head==NULL
}
QDataType QueFront(Que* ps)
{
assert(ps);
assert(ps->size > 0); // !QueEmpty(ps)
return ps->head->data;
}
bool isCompleteTree(struct TreeNode* root ) {
Que ps;
QueInit((&ps));
if (root)
{
QuePush(&ps, root);
}
while (!QueEmpty(&ps))
{
struct TreeNode* Front = QueFront(&ps);
if(Front==NULL)
{
break;
}
QuePush(&ps, Front->left);
QuePush(&ps, Front->right);
QuePop(&ps);
}
while (!QueEmpty(&ps))
{
struct TreeNode* Front = QueFront(&ps);
if(Front!=NULL)
{
return false;
}
QuePop(&ps);
}
return true;
}