前言:这篇文章主要是补充一些二叉树相关的函数,因为二叉树是用递归来展开的结构所以大多数的函数都是使用递归实现的所以整体的代码量还是比较少的但是很容易因为某个奇奇怪怪的细节问题而导致效率低下或者死递归
1.整体结构和队列代码
我们还是利用一个结构体来定义二叉树的结点,在结构体定义两个指针变量成员来分别指向二叉树的左孩子和右孩子:
cpp
typedef char BTDataType;
typedef struct BinaryTreeNode
{
BTDataType data;
struct BinaryTreeNode* left;//指向左孩子
struct BinaryTreeNode* right;//指向右孩子
}BTNode;
下面是我们接下来要实现的功能:
cpp
// 通过前序遍历的数组构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi);
// 二叉树销毁
void BinaryTreeDestory(BTNode** root);
// 二叉树节点个数
int BinaryTreeSize(BTNode* root);
// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root);
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k);
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);
// 二叉树前序遍历
void BinaryTreePrevOrder(BTNode* root);
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root);
// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root);
// 层序遍历
void BinaryTreeLevelOrder(BTNode* root);
// 判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root);
可以自行通过写测试函数或者调试来测试,我已经先测过了应该是没问题的。
因为我们在层序(宽度)优先遍历用到了队列,这里我用的队列是我之前写的队列因为C语言不想C++的STL一样为我们提供了现成的队列所以我就用了我之前写的队列,有关这个我写过了队列介绍在我们之前的文章【数据结构】队列及其C语言模拟实现中有介绍我这里就简单的贴个代码:
Queue.h:
cpp
typedef struct BinaryTreeNode* DataType;
typedef struct QueueNode
{
struct QueueNode* next;
DataType val;
}QNode;
typedef struct Queue
{
QNode* phead;
QNode* ptail;
int size;
}Queue;
void QueueInit(Queue* pst);
void QueueDestroy(Queue* pst);
void QueuePush(Queue* pst, DataType x);
void QueuePop(Queue* pst);
DataType QueueFront(Queue* pst);
DataType QueueBack(Queue* pst);
bool QueueEmpty(Queue* pst);
int QueueSize(Queue* pst);
Queue.c:
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include "Queue.h"
void QueueInit(Queue* pst)
{
assert(pst);
pst->phead = pst->ptail = NULL;
pst->size = 0;
}
void QueueDestroy(Queue* pst)
{
assert(pst);
QNode* cur = pst->phead;
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
pst->phead = pst->ptail = NULL;
pst->size = 0;
}
void QueuePush(Queue* pst, DataType x)
{
assert(pst);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail !");
return;
}
newnode->val = x;
newnode->next = NULL;
if (pst->ptail == NULL)
{
pst->ptail = pst->phead = newnode;
}
else
{
pst->ptail->next = newnode;
pst->ptail = newnode;
}
pst->size++;
}
void QueuePop(Queue* pst)
{
assert(pst);
assert(pst->phead);
if (pst->phead == pst->ptail)
{
free(pst->phead);
pst->phead = pst->ptail = NULL;
}
else
{
QNode* next = pst->phead->next;
free(pst->phead);
pst->phead = next;
}
pst->size--;
}
DataType QueueFront(Queue* pst)
{
assert(pst);
assert(pst->phead);
return pst->phead->val;
}
DataType QueueBack(Queue* pst)
{
assert(pst);
assert(pst->ptail);
return pst->ptail->val;
}
bool QueueEmpty(Queue* pst)
{
assert(pst);
return pst->size == 0;
}
int QueueSize(Queue* pst)
{
assert(pst);
return pst->size;
}
2.函数实现:
2.1通过前序遍历的数组构建二叉树
cpp
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi)
{
if (*pi >= n || a[*pi] == '#')
{
(*pi)++;
return NULL;
}
BTNode* Node = (BTNode*)malloc(sizeof(BTNode));
if (Node == NULL)
{
perror("malloc fail!");
return NULL;
}
Node->data = a[(*pi)++];
Node->left = Node->right = NULL;//也可不加因为下一次递归会覆盖掉但是我习惯加上去
Node->left = BinaryTreeCreate(a, n, pi);
Node->right = BinaryTreeCreate(a, n, pi);
return Node;
}
其中pi用来记录数组遍历到的下标位置,当越界或者遇到#号就返回一个空指针NULL,经过递归最终会返回这颗树的根结点。
2.2二叉树销毁
cpp
void BinaryTreeDestory(BTNode** root)
{
if (*root == NULL)
return;
BinaryTreeDestory(&((*root)->left));
BinaryTreeDestory(&((*root)->right));
free(*root);
*root = NULL;
}
这里用什么顺序来遍历销毁是有细节的讲究的,如何使用前序或者中序遍历的话还需要把该结点的孩子结点记录下面有点麻烦所以这里就干脆使用后序遍历销毁的方式这样也比较的方便
2.3二叉树节点个数
cpp
int BinaryTreeSize(BTNode* root)
{
if (root == NULL)
return 0;
int leftsize = BinaryTreeSize(root->left);
int rightsize = BinaryTreeSize(root->right);
return rightsize + leftsize + 1;
}
当遍历到空结点时不记录返回0,然后依次根据规模从小到大返回左孩子和右孩子加上该子树根节点的数量向上返回,最后就是这颗二叉树的个数
2.4二叉树叶子节点个数
cpp
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);
}
当这个结点的左右指针都为空时它就是一个叶子结点,最后再把左子树和右子树的叶子结点数量依次向上返回就可以了
2.5二叉树第k层节点个数
cpp
int BinaryTreeLevelKSize(BTNode* root, int k)
{
if (root == NULL)
return 0;
if (k == 1)
return 1;
int leftsize = BinaryTreeLevelKSize(root->left, k - 1);
int rightsize = BinaryTreeLevelKSize(root->right, k - 1);
return leftsize + rightsize;
}
这里我们可以反向思维一下,让每向下递归一层就让k减去1,这样k等于1时就是目标层。这里的
cpp
if (root == NULL)
return 0;
是用两个作用,一是作为递归的出口,二是为k == 1 时过滤掉该结点为空的情况。
2.6二叉树查找值为x的节点
cpp
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
if (root == NULL)
return NULL;
if (root->data == x)
return root;
BTNode* leftNode = BinaryTreeFind(root->left, x);
if (leftNode)
return leftNode;
return BinaryTreeFind(root->right, x);
}
当左子树返回空时就表示在各种规模的左子树找不到目标节点,这时就直接返回右子树的返回值反正就算是右边也找不到的话返回的也是NULL
2.7各种顺序遍历二叉树
这里的代码非常简单我就直接贴代码了:
cpp
// 二叉树前序遍历
void BinaryTreePrevOrder(BTNode* root)
{
if (root == NULL)
return;
printf("%c ", root->data);
BinaryTreePrevOrder(root->left);
BinaryTreePrevOrder(root->right);
}
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root)
{
if (root == NULL)
return;
BinaryTreeInOrder(root->left);
printf("%c ", root->data);
BinaryTreeInOrder(root->right);
}
// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root)
{
if (root == NULL)
return;
BinaryTreePostOrder(root->left);
BinaryTreePostOrder(root->right);
printf("%c ", root->data);
}
2.8层序遍历二叉树
关于二叉树的层序遍历在我之前的文章【数据结构】树的基本概念及存储里的宽度优先遍历(DFS)就有过介绍核心思想,只不过在哪里我是用了C++的STL库而且比较的简陋,所以我这里就不再赘述了
cpp
void BinaryTreeLevelOrder(BTNode* root)
{
if (root == NULL)
return;
Queue mp;
QueueInit(&mp);
QueuePush(&mp, root);
while (!QueueEmpty(&mp))
{
BTNode* tem = QueueFront(&mp);
QueuePop(&mp);
printf("%c ", tem->data);
if (tem->left)
QueuePush(&mp, tem->left);
if (tem->right)
QueuePush(&mp, tem->right);
}
QueueDestroy(&mp);
}
这里相比那个主要是多处理了一下空树和防止空结点入队的情况,其他的不能说一摸一摸只能说是没有区别了
2.9判断二叉树是否是完全二叉树
cpp
bool BinaryTreeComplete(BTNode* root)
{
if (root == NULL)
return true;
Queue mp;
QueueInit(&mp);
QueuePush(&mp, root);
while (!QueueEmpty(&mp))
{
BTNode* tem = QueueFront(&mp);
QueuePop(&mp);
if (tem == NULL)
break;
QueuePush(&mp, tem->left);
QueuePush(&mp, tem->right);
}
while (!QueueEmpty(&mp))
{
BTNode* tem = QueueFront(&mp);
QueuePop(&mp);
if (tem != NULL)
{
QueueDestroy(&mp);
return false;
}
}
QueueDestroy(&mp);
return true;
}
首选我们要先搞明白该如何判断二叉树是否是完全二叉树,这里我用到了层序遍历的方法来实现这个函数,原理也非常简单当队里的结点出队时无论它的孩子是否为空我们都统统让它们进队,当第一次遇到空结点时退出循环,接着我们就从这个结点开始重新层序遍历当后面还遇到非空结点时就表示该二叉树不可能是一个完全二叉树,因为对一个完全二叉树来说第一次遇到空结点后后面都应该是空结点这个画个图也可以明白
完