一、前置说明
在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。由于现在大家对二叉树结构掌握还不够深入,为了降低大家学习成本,此处手动快速创建一棵简单的二叉树,快速进入二叉树 操作学习,等二叉树结构了解的差不多时,我们反过头再来研究二叉树真正的创建方式。这里就直接单独创建一个 BinaryTree.c 的文件。
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. 非空:根结点,根结点的左子树、根结点的右子树组成的。
从概念中可以看出,二叉树定义是递归式的,因此后序基本操作中基本都是按照该概念实现的。
二、二叉树的遍历
2.1 前序、中序以及后序遍历
学习二叉树结构,最简单的方式就是遍历。所谓二叉树遍历(Traversal)是按照某种特定的规则,依次对二叉树中的结点进行相应的操作,并且每个结点只操作一次。访问结点所做的操作依赖于具体的应用问题。 遍历 是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。
按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历:
- 前序遍历(根左右):首先访问根节点,然后遍历左子树,最后遍历右子树。
- 中序遍历(左根右):首先遍历左子树,然后访问根节点,最后遍历右子树。
- 后序遍历(左右根):首先遍历左子树,然后遍历右子树,最后访问根节点。
由于被访问的结点必是某子树的根,所以N(Node)、L(Left subtree)和R(Right subtree)又可解释为根、根的左子树和根的右子树。NLR、LNR和LRN分别又称为先根遍历、中根遍历和后根遍历。
//代码实现包含的头文件
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
//定义树的结构体,包含节点的值,左子树和右子树的地址
typedef int BTDataType;
typedef struct BinaryTreeNode
{
BTDataType data;
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
}BTNode;
//节点空间申请
BTNode* BuyNode(int x)
{
BTNode* node = (BTNode*)malloc(sizeof(BTNode));
if (node == NULL)
{
perror("malloc fail!");
return;
}
node->data = x;
node->left = node->right = NULL;
}
//构建一棵树
BTNode* CreatBT()
{
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;
}
以上述树为例进行遍历。
前序遍历:
//前序遍历
void PreOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
printf("%d ", root->data);
PreOrder(root->left);
PreOrder(root->right);
}
中序遍历:
void MidOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
MidOrder(root->left);
printf("%d ", root->data);
MidOrder(root->right);
}
后序遍历:
//后序遍历
void TailOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
TailOrder(root->left);
TailOrder(root->right);
printf("%d ", root->data);
}
三种遍历结果展示:
2.2遍历执行过程:
前序遍历递归图解:
2.3 层序遍历
层序遍历:除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根结点所在层数为1,层序遍历就是从所在二叉树的根结点出发,首先访问第一层的树根结点,然后从左到右访问第2层 上的结点,接着是第三层的结点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历 。
三、树中常见操作
// 二叉树结点个数
int TreeSize(BTNode* root);
// 二叉树叶子结点个数
int TreeLeafSize(BTNode* root);
// 二叉树第k层结点个数
int TreeLevelKSize(BTNode* root, int k);
// 二叉树查找值为x的结点
BTNode* TreeFind(BTNode* root, BTDataType x);
3.1二叉树节点个数
错误示范1:
//求节点个数
int TreeSize(BTNode* root)
{
static int size = 0;
if (root == NULL)
return 0;
else
{
size++;
}
TreeSize(root->left);
TreeSize(root->right);
return size;
}
上述方法就是一种错误的示范,因为static修饰的变量后续调用不会重置,会继续使用上一次调用的结果来作为初始值。所以大家在求节点个数时一定要注意该方法。
方法1:
//直接将size定义成全局变量
//求节点个数
int size = 0;
int TreeSize(BTNode* root)
{
if (root == NULL)
return 0;
else
{
size++;
}
TreeSize(root->left);
TreeSize(root->right);
return size;
}
虽然法1可以实现代码,但是却很鸡肋,因此也不建议使用全局变量来定义从而弥补错误示范的坑。
法2:
因为前面的方法主要是问题就是不能通过参数之间改变我们想要的值,属于是传值操作,因此想要改变其size的值,就需要考虑指针,进行传值操作。
/求节点个数
//可以再定义一个指针来传递size的值,
int TreeSize(BTNode* root, int* psize)
{
if (root == NULL)
{
return 0;
}
else
{
(*psize)++;
}
TreeSize(root->left,psize);
TreeSize(root->right, psize);
return *psize;
}
但是该方法仍然十分麻烦,和定义全局变量大差不差多少,还有没有更加简洁的方法呢?答案是有的,接下来请看第三种方法。
法3:
int TreeSize(BTNode* root)
{
return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}
这时候是不是发现代码简单多了,一行代码就完成了,这方法我们理解其背后的执行过程就可以了,毕竟也是大佬们才写出来的。
这里就以一颗子树为例说明一下:
3.2 二叉树叶子节点个数
//求叶子节点个数
int TreeLeafSize(BTNode* root)
{
if (root == NULL)
{
return 0;
}
if (root->left == NULL && root->right == NULL)
{
return 1;
}
return TreeLeafSize(root->left)+
TreeLeafSize(root->right);
}
3.3 二叉树第k层节点个数
int TreeKLayerSize(BTNode* root,int k)
{
if (root == NULL)
return 0;
if (k == 1)
return 1;
return TreeKLayerSize(root->left, k - 1)
+ TreeKLayerSize(root->right, k - 1);
}
这里可以理解为将树反过来看或者是说k反过来,当k=3时,以2为根节点左子树就一个3,所以在最后的递归调用结束后就返回1;但以4为根节点的右子树,有5,6两个节点,所以递归调用结束后会返回2。
3.4 二叉树的高度
//求树的高度
int TreeHeight(BTNode* root)
{
if (root == NULL)
{
return 0;
}
if (root->left == 0 && root->right == 0)
{
return 1;
}
int left = TreeHeight(root->left);
int right = TreeHeight(root->right);
return left > right ? left + 1 : right + 1;
}
树的高度肯定是左右子树较大的那一个,但是这里一定要将左右子树的高度分别用一个变量给存起来,如left , right ,如果不存起来,当一棵树特别大时,会引起内存泄漏,导致代码无法执行。
3.5 二叉树中寻找值为x的值
//二叉树中寻找值为x的值
BTNode* TreeFind(BTNode* root, BTDataType x)
{
if (root == NULL)
{
return NULL;
}
if (root->data == x)
{
return root;
}
BTNode* root1 = TreeFind(root->left, x);
if (root1)
return root1;
BTNode* root2 = TreeFind(root->right, x);
if (root2)
return root2;
return NULL;
}
该函数一定要注意每个都有返回值,不然容易报错。
为了方便大家更好理解,我将代码及画板展现如下
作者vs代码如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int BTDataType;
typedef struct BinaryTreeNode
{
BTDataType data;
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
}BTNode;
BTNode* BuyNode(int x)
{
BTNode* node = (BTNode*)malloc(sizeof(BTNode));
if (node == NULL)
{
perror("malloc fail!");
return;
}
node->data = x;
node->left = node->right = NULL;
}
BTNode* CreatBT()
{
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;
}
//前序遍历
void PreOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
printf("%d ", root->data);
PreOrder(root->left);
PreOrder(root->right);
}
//中序遍历
void MidOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
MidOrder(root->left);
printf("%d ", root->data);
MidOrder(root->right);
}
//后序遍历
void TailOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
TailOrder(root->left);
TailOrder(root->right);
printf("%d ", root->data);
}
//该方法使用了static修饰,因为static只会初始化一次,使用后续调用就会出问题
//求节点个数
//int TreeSize(BTNode* root)
//{
// static int size = 0;
// if (root == NULL)
// return 0;
// else
// {
// size++;
// }
// TreeSize(root->left);
// TreeSize(root->right);
// return size;
//}
直接将size定义成全局变量
求节点个数
//int size = 0;
//int TreeSize(BTNode* root)
//{
// if (root == NULL)
// return 0;
// else
// {
// size++;
// }
//
// TreeSize(root->left);
// TreeSize(root->right);
// return size;
//}
//求节点个数
//可以再定义一个指针来传递size的值,
//int TreeSize(BTNode* root, int* psize)
//{
// if (root == NULL)
// {
// return 0;
// }
// else
// {
// (*psize)++;
// }
// TreeSize(root->left,psize);
// TreeSize(root->right, psize);
// return *psize;
//}
//int TreeSize(BTNode* root)
//{
// return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
//}
//求叶子节点个数
int TreeLeafSize(BTNode* root)
{
if (root == NULL)
{
return 0;
}
if (root->left == NULL && root->right == NULL)
{
return 1;
}
return TreeLeafSize(root->left)+
TreeLeafSize(root->right);
}
//求树的高度
int TreeHeight(BTNode* root)
{
if (root == NULL)
{
return 0;
}
if (root->left == 0 && root->right == 0)
{
return 1;
}
int left = TreeHeight(root->left);
int right = TreeHeight(root->right);
return left > right ? left + 1 : right + 1;
}
//求第k层节点个数
int TreeKLayerSize(BTNode* root,int k)
{
if (root == NULL)
return 0;
if (k == 1)
return 1;
return TreeKLayerSize(root->left, k - 1)
+ TreeKLayerSize(root->right, k - 1);
}
//二叉树中寻找值为x的值
BTNode* TreeFind(BTNode* root, BTDataType x)
{
if (root == NULL)
{
return NULL;
}
if (root->data == x)
{
return root;
}
BTNode* root1 = TreeFind(root->left, x);
if (root1)
return root1;
BTNode* root2 = TreeFind(root->right, x);
if (root2)
return root2;
return NULL;
}
int main()
{
BTNode* root = CreatBT();
//PreOrder(root);
//printf("\n");
//MidOrder(root);
//printf("\n");
//TailOrder(root);
//printf("\n");
//int size = 0;
//printf("TreeSize:%d\n", TreeSize(root,&size));
//printf("TreeSize:%d\n", TreeSize(root, &size));
//size = 0;
//printf("TreeSize:%d\n", TreeSize(root, &size));
//size = 0;
//printf("TreeSize:%d\n", TreeSize(root, &size));
//size = 0;
//int leafsize = 0;
//int size = 0;
//printf("TreeSize:%d\n", TreeSize(root, &size));
//printf("TreeSize:%d\n", TreeSize(root, &size));
//printf("TreeSize:%d\n", TreeSize(root, &size));
//int size = 0;
//printf("TreeLeafSize:%d\n", TreeLeafSize(root));
//printf("TreeSize:%d\n", TreeSize(root, &size));
//printf("TreeHight:%d\n", TreeHeight(root));
//printf("第三层节点个数:%d\n", TreeKLayerSize(root, 3));
//printf("TreeKLayerSize:%d\n", TreeKLayerSize(root, 3));
return 0;
}
画图内容如下:
以上就是二叉树的一些常见遍历及二叉树的操作内容,希望大家可以多提意见。