一、 简单概念
一棵二叉树是结点的一个有限集合,该集合或者为空,或者是由一个根节点加上两棵别称为左子树和右子树的二叉树组成。
二叉树的特点:
1. 每个结点最多有两棵子树,即二叉树不存在度大于2的结点。
2. 二叉树的子树有左右之分,其子树的次序不能颠倒。
下图为几种树的节点的可能情况:
主要的概念有二叉树的性质、二叉树的存储结构、二叉树的遍历,具体概念内容,例如大部分学校常用的严蔚敏版本《数据结构(C语言版)》书中都有很详细的内容讲解(其他数据结构与算法书籍同)可在教材中做具体了解,但是该书中的算法实现都是伪代码形式的,所以我们需要将他进行实现。针对于二叉树基础的操作有二叉树的建立、二叉树的遍历、二叉树的销毁、求二叉树节点个数、二叉树第k层节点个数、二叉树叶子节点个数等。该文章主要针对这些操作采用递归的方法进行C语言实现。其二叉树的遍历再介绍一下,以对应代码进行实现,方便理解。
二、 二叉树遍历
2.1 简介
在二叉树的一些应用中,常常要求在树中查找具有某种特征的节点,或者对数中全部节点逐一进行某种处理。这就提出了遍历二叉树(traversing binary tree)的问题。假设以L、D、R分别表示遍历左子树、根节点、右子树,则有DLR、LDR、LRD、DRL、RDL、RLD这6种遍历方案。若规定先左后右,则只有前3种,分别成为前(先)序遍历,中序遍历、后序遍历。
1. NLR:前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。
2. LNR:中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。
3. LRN:后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。
2.2 示例遍历
举个例子遍历一下吧,如下二叉树
这个二叉树的前序: ABDEC
中序: DBEAC
后序: DEBCA
2.3 遍历二叉树递归算法定义
基于二叉树的递归定义,可得下述遍历二叉树得递归算法定义。
1. 前序遍历二叉树的操作定义:
若二叉树为空,则空操作,否则:
(1) 访问根节点;
(2) 前序遍历左子树;
(3) 前序遍历右子树。
2. 中序遍历二叉树的操作定义:
若二叉树为空,则空操作,否则:
(1) 中序遍历左子树;
(2) 访问根节点;
(3) 中序遍历右子树。
3 后序遍历二叉树的操作定义:
若二叉树为空,则空操作,否则:
(1) 后序遍历左子树;
(2) 后序遍历右子树;
(3) 访问根节点。
4. 还有一种遍历方法是层序遍历,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第2层上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程。
其实前、中、后序遍历的递归代码实现和算法描述一样一样的,语句都是一样一样的,具体见代码实现。
三、 基本操作C语言实现
上小节我们提到,要介绍的基本操作有:二叉树的创建、销毁、遍历、求二叉树的节点个数、二叉树叶子节点个数、二叉树第k层节点个数,二叉树查找值为x的节点。
3.1. 创建二叉树
创建二叉树的过程是这样的:首先,给出一个数组,将要创建的元素放在数组里;然后通过遍历(前 或 中 或 后遍历)的顺序访问创建二叉树每个节点;最后返回根节点的地址即创建完成。
以下面数组为例: char a[ ] = "ABD##E#H##CF##G##";
规则是这样的,按照前序遍历的顺序在访问过程中当节点的下一个遇到‘#’是说明该节点为叶子节点。
那么,开始创建二叉树吧,先定义出一个自定义结构类型表示一个节点,如下:
typedef char BTData; //表示节点的数据类型
typedef struct BTNode {
char data;
struct BTNode* left;
struct BTNode* right;
}BTNode;
然后通过前序遍历的顺序访问并创建二叉树,代码如下:
// 创建树,按前序遍历的顺序
BTNode* BinaryTreeCreate(BTData* a, int* pi) {
if (a[*pi] != '#') {
BTNode* root = (BTNode*)malloc(sizeof(BTNode));
root->data = a[*pi];
(*pi)++;
root->left = BinaryTreeCreate(a, pi);
(*pi)++;
root->right = BinaryTreeCreate(a, pi);
return root;
}
else {
return NULL;
}
}
创建出的二叉树如下:
在这里,参数传入一个数组a,一个指针索引变量pi。重点在这个索引变量pi上,按正常的思路,数组遍历索引变量idx是个整数型,遍历的时候执行++即可,但是在递归过程中就不是这样了。如果写成整型pi,那么它会将二叉树相同层的节点放在同一个位置上,然后覆盖掉节点内容。
3.2 二叉树遍历
趁着上小节介绍了二叉树遍历递归算法的定义,我们先来介绍二叉树的前、中、后序遍历打印。
1. 前序遍历,代码如下:
// 前序遍历打印
void BinaryTreePrevOrder(BTNode* root) {
if (root) {
printf("%c ", root->data);
BinaryTreePrevOrder(root->left);
BinaryTreePrevOrder(root->right);
}
}
我们把代码和递归算法定义拿过来比较一下,:
中序和后续遍历同理,方法一样,只是改变了访问节点的顺序。
2. 中序遍历,代码如下
// 中序遍历打印
void BinaryTreeInOrder(BTNode* root) {
if (root) {
BinaryTreeInOrder(root->left);
printf("%c ", root->data);
BinaryTreeInOrder(root->right);
}
}
3. 后序遍历,代码如下:
// 后续遍历打印
void BinaryTreePostOrder(BTNode* root) {
if (root) {
BinaryTreePostOrder(root->left);
BinaryTreePostOrder(root->right);
printf("%c ", root->data);
}
}
4. 其他操作实现
用递归的方法实现二叉树的操作,代码比较简单,但理解起来不是很容易。需要在调试的过程中去感受递归调用的过程怎样给各个变量赋值及之间的相互调用关系。剩余其他的操作实现思想基本上和遍历的思想差不多,就不一个一个介绍了,我直接展示代码,需要着重注意的地方会在注释中标出。其他操作代码如下:
/* ***** 二叉树叶子节点个数 **** */
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);
}
/* **********节点个数********** */
int BinaryTreeSize(BTNode* root) {
if (root == NULL) {
return 0;
}
return BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1; // 只要节点存在,就 +1
/* **第K层的节点个数 ************/
int BinaryTreeLevelKSize(BTNode* root, int k)
{
//空树: 0
if (root == NULL)
return 0;
// 只有根节点
if (k == 1)
return 1;
return BinaryTreeLevelKSize(root->left, k - 1) // 参数传进 k-1 可理解为:
//求第k层节点个数就是求它的孩子节点的k-1层节点个数,这样方便写成递归
+ BinaryTreeLevelKSize(root->right, k - 1);
}
/* **************二叉树查找值为x的节点*************** */
BTNode* BinaryTreeFind(BTNode* root, BTData x)
{
BTNode* ret;
//空树找不到
if (root == NULL)
return NULL;
if (root->data == x) // 找到了返回 root
return root;
//查找左右子树
ret = BinaryTreeFind(root->left, x);
if (ret) //不为空就继续查找
return ret;
return BinaryTreeFind(root->right, x);
}
/*******层序遍历打印**********/
// 先要遍历每一层的节点,这部分代码和输出第k层节点个数差不多。 然后遍历所有层 _BinaryTreeLevelOrder为遍历每一层的函数
void BinaryTreeLevelOrder(BTNode* root) {
if (root) {
for (int i = 1; i < getSize(root); i++) {
_BinaryTreeLevelOrder(root, i);
}
}
}
// 输出第i层
void _BinaryTreeLevelOrder(BTNode* root, int i) {
if (root == NULL || i == 0) {
return;
}
if (i == 1) {
printf("%c ", root->data);
return;
}
_BinaryTreeLevelOrder(root->left, i - 1);
_BinaryTreeLevelOrder(root->right, i - 1);
}
/******** 销毁二叉树***********/
void BinaryTreeDestory(BTNode** root) {
BTNode* cur = *root;
if (cur) {
BinaryTreeDestory(&cur->left);
BinaryTreeDestory(&cur->right);
free(cur);
*root = NULL;
}
}
以上即为所介绍的二叉树基本的几个操作,用递归实现之。
四、 结果测试
将所有函数整合到一个.c文件里,并简单写一个测试函数进行测试,.c文件代码整合如下:
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
typedef char BTData; //表示节点的数据类型
typedef struct BTNode {
char data;
struct BTNode* left;
struct BTNode* right;
}BTNode;
// 创建树,按前序遍历的顺序
BTNode* BinaryTreeCreate(BTData* a, int* pi) {
if (a[*pi] != '#') {
BTNode* root = (BTNode*)malloc(sizeof(BTNode));
root->data = a[*pi];
(*pi)++;
root->left = BinaryTreeCreate(a, pi);
(*pi)++;
root->right = BinaryTreeCreate(a, pi);
return root;
}
else {
return NULL;
}
}
// 销毁二叉树
void BinaryTreeDestory(BTNode** root) {
BTNode* cur = *root;
if (cur) {
BinaryTreeDestory(&cur->left);
BinaryTreeDestory(&cur->right);
free(cur);
*root = NULL;
}
}
// 前序遍历打印
void BinaryTreePrevOrder(BTNode* root) {
if (root) {
printf("%c ", root->data);
BinaryTreePrevOrder(root->left);
BinaryTreePrevOrder(root->right);
}
}
// 中序遍历打印
void BinaryTreeInOrder(BTNode* root) {
if (root) {
BinaryTreeInOrder(root->left);
printf("%c ", root->data);
BinaryTreeInOrder(root->right);
}
}
// 后续遍历打印
void BinaryTreePostOrder(BTNode* root) {
if (root) {
BinaryTreePostOrder(root->left);
BinaryTreePostOrder(root->right);
printf("%c ", root->data);
}
}
// 输出第i层
void _BinaryTreeLevelOrder(BTNode* root, int i) {
if (root == NULL || i == 0) {
return;
}
if (i == 1) {
printf("%c ", root->data);
return;
}
_BinaryTreeLevelOrder(root->left, i - 1);
_BinaryTreeLevelOrder(root->right, i - 1);
}
// 层序遍历打印
// 先要遍历每一层的节点,这部分代码和输出第k层节点个数差不多。 然后遍历所有层 _BinaryTreeLevelOrder为遍历每一层的函数
void BinaryTreeLevelOrder(BTNode* root) {
if (root) {
for (int i = 1; i < getSize(root); i++) {
_BinaryTreeLevelOrder(root, i);
}
}
}
// 二叉树叶子节点个数
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);
}
// 叶子节个数
int BinaryTreeSize(BTNode* root) {
if (root == NULL) {
return 0;
}
return BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1; // 只要节点存在,就 +1
}
// 第K层的节点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{
//空树: 0
if (root == NULL)
return 0;
// 只有根节点
if (k == 1)
return 1;
return BinaryTreeLevelKSize(root->left, k - 1) // 参数传进 k-1 可理解为:
//求第k层节点个数就是求它的孩子节点的k-1层节点个数,这样方便写成递归
+ BinaryTreeLevelKSize(root->right, k - 1);
}
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTData x)
{
BTNode* ret;
//空树找不到
if (root == NULL)
return NULL;
if (root->data == x) // 找到了返回 root
return root;
//查找左右子树
ret = BinaryTreeFind(root->left, x);
if (ret) //不为空就继续查找
return ret;
return BinaryTreeFind(root->right, x);
}
void test() {
char a[] = "ABD##E#H##CF##G##";
int pi = 0;
BTNode* root = BinaryTreeCreate(a, &pi);
printf("前序:");
BinaryTreePrevOrder(root);
printf("\n");
printf("中序:");
BinaryTreeInOrder(root);
printf("\n");
printf("后序:");
BinaryTreePostOrder(root);
printf("\n");
printf("层序:");
BinaryTreeLevelOrder(root);
printf("\n");
int j = BinaryTreeSize(root);
printf("二叉树的节点个数为 %d\n", j);
int n = BinaryTreeLeafSize(root);
printf("二叉树的叶子节点个数为 %d\n", n);
}
int main() {
test();
system("pause");
return 0;
}
其运行结果如下:
前序: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
二叉树的节点个数为 8
二叉树的叶子节点个数为 4
请按任意键继续. . .
没有测试完,其他几个函数若要进行测试,可根据需求进行测试哦!
本文所述内容和代码如有错误,请同志们多多指教,多多交流。