数据结构初阶:详解二叉树(三):链式二叉树
🔥个人主页:胡萝卜3.0
🎬作者简介:C++研发方向学习者
⭐️人生格言:不试试怎么知道自己行不行

目录
七、实现链式结构二叉树
7.1 链式二叉树的结构
用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址。
其结构如下:
typedef char BTDataType;
typedef struct BinaryTreeNode
{
BTDataType data;//当前结点值域
struct BinaryTreeNode* left;//指向当前结点的左孩子
struct BinaryTreeNode* right;//指向当前结点的右孩子
}BTNode;
二叉树的创建方式比较复杂,为了更好的步入到二叉树内容中,我们先手动创建⼀棵链式二叉树。
//手动创建链式二叉树
BTNode* BuyNode(BTDataType num)
{
BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
if (newnode == NULL)
{
perror("malloc fail!");
exit(1);
}
newnode->data = num;
newnode->left = newnode->right = NULL;
return newnode;
}
BTNode* CreateTree()
{
BTNode* nodeA = BuyNode('A');
BTNode* nodeB = BuyNode('B');
BTNode* nodeC = BuyNode('C');
BTNode* nodeD = BuyNode('D');
BTNode* nodeE = BuyNode('E');
BTNode* nodeF = BuyNode('F');
nodeA->left = nodeB;
nodeA->right = nodeC;
nodeB->left = nodeD;
nodeB->right = nodeE;
nodeC->left = nodeF;
return nodeA;
}
回顾二叉树的概念,二叉树分为空树和非空二叉树,非空二叉树由根节点、根节点的左子树、根节点的右子树组成的。

根节点的左子树和右子树分别又是由子树结点、子树结点的左子树、子树结点的右子树组成的,因此二叉树定义是递归式的,后序链式二叉树的操作基本都是按照该概念实现的。
7.2 前中后序遍历
二叉树的操作离不开树的遍历,我们先来看看二叉树的遍历有哪些方式:

7.2.1 遍历规则
按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历
1)前序遍历(Preorder Traversal也称先序遍历):访问根节点的操作发生在遍历其左右子树之前 访问顺序:根节点、左子树、右子树
2)中序遍历(Inorder Traversal也称中根遍历):访问根节点的操作发生在遍历其左右子树之间 访问顺序:左子树、根节点、右子树
3)后序遍历(Postorder Traversal也称后根遍历):访问根节点的操作发生在遍历其左右子树之后,访问顺序:左子树、右子树、根节点
总结:前中后是指什么时候遍历根节点!!!
7.2.2 代码实现
//前序遍历--根左右
void PrevOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
printf("%c ", root->data);
PrevOrder(root->left);
PrevOrder(root->right);
}
//中序遍历--左根右
void InOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
InOrder(root->left);
printf("%c ", root->data);
InOrder(root->right);
}
//后续遍历--左右根
void PostOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
PostOrder(root->left);
PostOrder(root->right);
printf("%c ", root->data);
}
7.2.3 递归过程演示
接下来,我们一起来看一下这是怎么实现递归的
前序遍历递归过程:

红色为递归过程,绿色为回归过程

中序遍历递归过程:

后序遍历递归过程:

7.3 二叉树结点个数以及高度
7.3.1 二叉树的结点个数
如何求二叉树的结点个数呢?
二叉树的结点个数=根节点+左子树结点个数+右子树结点个数

我们看到求二叉树结点个数的返回类型是int类型,那么这里有个问题:递归的结束条件是什么?
当在递归过程中遇到空节点,我们是不是就不能继续再递归下去了?答案是:是的
递归的结束条件:遇到空节点就直接返回,直接返回0
ok,通过上面的简单思考,我们就知道该代码如何书写了。

代码:
//求二叉树的结点个数
//=根节点+左子树结点个数+右子树结点个数
int BinaryTreeSize(BTNode* root)
{
if (root == NULL)
{
return 0;
}
return 1 + BinaryTreeSize(root->left) + BinaryTreeSize(root->right);
}
递归过程演示(每个结点视为一个函数桟帧):

7.3.2 二叉树的叶子结点个数
我们先来想一想,什么是叶子结点?叶子结点就是没有左右孩子的结点
那二叉树的叶子结点该如何求呢?
我们知道一棵非空二叉树有左子树和右子树,在左子树和右子树中都会存在叶子结点,😯,那二叉树的叶子结点个数不就是左子树叶子结点个数+右子树叶子结点个数,这样我们就可以求出一棵二叉树中存在多少颗叶子结点了。
二叉树叶子结点个数=左子树叶子结点个数+右子树叶子结点个数
那递归结束条件是什么呢?
递归结束条件:首先要判断该结点是否是空节点,如果是空节点,就直接返回0,如果不是空节点,我们要判断该结点是否有左右孩子,如果没有左右孩子,说明是叶子结点,直接返回1
ok,接下来让我们尝试写一下代码。

代码演示:
//求二叉树叶子节点个数
//叶子结点没有左右孩子
//==左子树叶子结点个数+右子树叶子结点个数
int BinaryTreeLeaveSize(BTNode* root)
{
if (root == NULL)
{
return 0;
}
if (root->left == NULL && root->right == NULL)
{
return 1;
}
return BinaryTreeLeaveSize(root->left) + BinaryTreeLeaveSize(root->right);
}
递归过程演示(每个结点视为一个函数桟帧)(红线递归,绿色回归)

7.3.3 二叉树第K层结点个数
问题1:如何知道我们是不是已经到达第K层?
在我们遍历第一层的时候,K=3,然后,我们每遍历一层都让K--,当K=1时,我们就知道已经到达第K层。
问题2:如果我们知道已经到达了第K层,那我们该如何求第K层的结点个数?
第K层结点个数=左子树第K层结点个数+右子树第K层结点个数。
当结点不为空,K!=1时,说明未到达指定层,我们需要继续对左子树和右子树进行查找。
ok,了解了上面的几个问题,我们就知道如何书写代码了

代码:
//求二叉树第K层结点个数
//==左子树第K层结点个数+右子树第K层结点个数
int BinaryTreeLevelKSize(BTNode* root,int k)
{
if (root == NULL)
{
return 0;
}
if (k == 1)
{
//说明已经到达第K层
return 1;
}
return BinaryTreeLevelKSize(root->left, k--) + BinaryTreeLevelKSize(root->right, k--);
}
递归过程演示(每个结点视为一个函数桟帧)(红线递归,绿色回归)

7.3.4 二叉树的高度
如何求一棵二叉树的高度呢?我们知道一个根节点为一层,根节点下面有左右子树,那是求左子树的高度还是求右子树的高度呢?
思路:我们需要求出左子树和右子树各的高度,然后得到其中的最大值,再加上根节点,就可以求出该二叉树的高度了。
二叉树的高度=1+max(左子树高度,右子树高度)
ok,通过上面的描述,我们就可以写出代码了。

我们如果写成一个三目表达式,是不是太长了?我们把它们保存一下——

代码:
//二叉树的深度
//==1+max(左子树深度,右子树深度)
int BinaryTreeDepth(BTNode* root)
{
if (root == NULL)
{
return 0;
}
int leftdepth = BinaryTreeDepth(root->left);
int rightdepth = BinaryTreeDepth(root->right);
return 1 + (leftdepth > rightdepth ? leftdepth : rightdepth);
}
递归过程演示(每个结点视为一个函数桟帧)(红线递归,绿色回归)

7.3.5 查找二叉树值为x的结点

通过上图中的函数声明,我们就知道该函数的返回类型是一个结点的地址。
ok,那我们该如何在一棵二叉树中查找值为x的结点呢?
思路:首先判断该结点是否为空,如果为空,直接返回NULL(递归结束条件),如果结点不为空,我们就需要判断该结点中的值是否等于x,如果等于,直接返回该结点,如果不等于,先去左子树中查找,如果左子树中找到,直接返回该结点,如果左子树中没有找到,再去右子树中查找
根据思路,我们就可以写出代码了。

代码
//二叉树查找值为x的结点
//先在左子树中查找,如果没有找到再去右子树中找
BTNode* BinaryFindNode(BTNode* root,BTDataType num)
{
if (root == NULL)
{
return NULL;
}
//判断
if (root->data == num)
{
return root;
}
BTNode* left=BinaryFindNode(root->left,num);
if (left)
{
return left;
}
BTNode* right=BinaryFindNode(root->right, num);
if (right)
{
return right;
}
return NULL;
}
递归过程演示(每个结点视为一个函数桟帧)(红线递归,绿色回归)

7.3.6 二叉树的销毁
我们知道一棵二叉树是由一个一个结点组成,每个结点都要向操作系统申请空间,当我们不再使用这些结点时,我们就需要将这些空间返回给操作系统。
ok,那我们该怎么将这些空间返还给操作系统呢?是像前面的链表一样,每遍历到一个结点,就直接释放吗?
当然不是,我们知道一个结点中有三个域:值域,左右指针域。左右指针中分别指向左孩子结点和右孩子结点,如果直接将这个结点给释放掉,那我们就无法知道他的左孩子和右孩子是谁了,所以我们需要先释放该结点的左右孩子,然后再释放该节点,😯,这和前面的后序遍历不是一样的嘛,先释放左右孩子,再释放自己。
代码:
//二叉树的销毁
//后续遍历--左右根
void BinaryTreeDestory(BTNode** root)
{
if (root == NULL)
{
return;
}
BinaryTreeDestory(&((*root)->left));
BinaryTreeDestory(&((*root)->right));
free(*root);
*root = NULL;
}
递归过程演示(每个结点视为一个函数桟帧)(红线递归,绿色回归)

7.3.7 层序遍历
二叉树的遍历除了前序遍历、中序遍历以及后序遍历,其实还有一个层序遍历。
设二叉树的根结点所在层数为1,层序遍历就是从所在二叉树的根结点出发,首先访问第一层的树根结点,然后从左到右访问第2层上的结点,接着是第三层的结点,以此类推,自伤而下,自左至右,逐层访问树的结点的过程就是层序遍历
实现层序遍历需要额外借助数据结构:队列

那这是就会有同学想问了,你上面说层序遍历需要借助数据结构--队列,具体是怎么操作的呢?
思路:首先我们要创建一个队列,这个队列中会存放二叉树的结点,然后让根节点入队列,使队列不为空,然后循环判断队列是否为空,如果队列不为空,取队头,出队头,打印队头中的数据,使队头结点中不为空的左右孩子入队列,直到队列为空。
有了上面的思路,我们就会很轻松的写出代码。

代码:
//层序遍历
//借助数据结构--队列
//根节点入队列,循环判断队列是否为空,若队列不为空,取队头,出队头,
// 不为空的左右孩子入队列
void LevelOrder(BTNode* root)
{
Queue q;
QueueInit(&q);
QueuePush(&q, root);
while (!QueueEmpty(&q))
{
BTNode* top = QueueFront(&q);
printf("%c ", top->data);
QueuePop(&q);
if (top->left)
{
QueuePush(&q, top->left);
}
if (top->right)
{
QueuePush(&q, top->right);
}
}
QueueDestory(&q);
}
像前序、中序、后序遍历我们称之为深度优先遍历;
像层序遍历这种遍历我们称之为广度优先遍历。
7.3.8 判断是否是完全二叉树
我们先来回顾一下什么是完全二叉树,完全二叉树就是:除了最后一层,其余每层的结点个数达到最大,最后一层结点个数不一定达到最大;结点从左往右依次排列
更多细节请看【数据结构与算法】数据结构初阶:详解二叉树(一)-CSDN博客
那我们该怎么判断一棵二叉树是不是完全二叉树呢?
这里也要借助数据结构--队列,我们先来观察下面图中所展示的内容:

第一棵二叉树不是完全二叉树,执行上面的步骤,遇到空节点跳出循环时,发现队列不为空,并且队列中存在不为空的结点;第二棵二叉树是完全二叉树,执行上面的步骤,遇到空节点跳出循环时,发现队列不为空,并且队列中都是为空的结点。
ok,通过比较我们是不是就知道如何判断是否是完全二叉树了。
思路:首先让根结点入队列,使队列不为空,循环判断队列是否为空,不为空,取队头,出队头,如果取到的队头为空结点,直接跳出循环,如果队头结点不是空结点,使队头结点的左右孩子都入队列。跳出循环后,再次循环判断队列是否为空,如果在取队头的过程中,取到非空结点,就说明此二叉树不是完全二叉树

完整代码:
//判断是否是完全二叉树
//完全二叉树除了最后一层,其他每层的节点个数达到最大
//结点从左往右依次排列
//借助数据结构--队列
//根节点入队列,循环判断队列是否为空,若队列不为空,取队头,出队头,
// 左右孩子都入队列
bool BinaryTreeComplete(BTNode* root)
{
Queue q;
QueueInit(&q);
QueuePush(&q, root);
while (!QueueEmpty(&q))
{
BTNode* top = QueueFront(&q);
if (top == NULL)
{
break;
}
QueuePop(&q);
QueuePush(&q, top->left);
QueuePush(&q, top->right);
}
//跳出循环,说明遇到空节点,此时队列不为空
//查看队列中是否都是空节点,若都是空节点,则为完全二叉树
while (!QueueEmpty(&q))
{
BTNode* top = QueueFront(&q);
if (top != NULL)
{
//说明不是完全二叉树
QueueDestory(&q);
return false;
}
QueuePop(&q);
}
QueueDestory(&q);
return true;
}
八、实现链式结构二叉树完整代码
tree.h
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
typedef char BTDataType;
typedef struct BinaryTreeNode
{
BTDataType data;//当前结点值域
struct BinaryTreeNode* left;//指向当前结点的左孩子
struct BinaryTreeNode* right;//指向当前结点的右孩子
}BTNode;
//前序遍历
void PrevOrder(BTNode* root);
//中序遍历
void InOrder(BTNode* root);
//后续遍历
void PostOrder(BTNode* root);
//求二叉树的结点个数
int BinaryTreeSize(BTNode* root);
//求二叉树叶子节点个数
int BinaryTreeLeaveSize(BTNode* root);
//求二叉树第K层结点个数
int BinaryTreeLevelKSize(BTNode* root, int k);
//二叉树的深度
int BinaryTreeDepth(BTNode* root);
//二叉树查找值为x的结点
BTNode* BinaryFindNode(BTNode* root,BTDataType num);
//二叉树的销毁
void BinaryTreeDestory(BTNode** root);
//层序遍历
void LevelOrder(BTNode* root);
//判断是否是完全二叉树
bool BinaryTreeComplete(BTNode* root);
tree.c
#include"tree.h"
#include"Queue.h"
//前序遍历--根左右
void PrevOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
printf("%c ", root->data);
PrevOrder(root->left);
PrevOrder(root->right);
}
//中序遍历--左根右
void InOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
InOrder(root->left);
printf("%c ", root->data);
InOrder(root->right);
}
//后续遍历--左右根
void PostOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
PostOrder(root->left);
PostOrder(root->right);
printf("%c ", root->data);
}
//求二叉树的结点个数
//=根节点+左子树结点个数+右子树结点个数
int BinaryTreeSize(BTNode* root)
{
if (root == NULL)
{
return 0;
}
return 1 + BinaryTreeSize(root->left) + BinaryTreeSize(root->right);
}
//求二叉树叶子节点个数
//叶子结点没有左右孩子
//==左子树叶子结点个数+右子树叶子结点个数
int BinaryTreeLeaveSize(BTNode* root)
{
if (root == NULL)
{
return 0;
}
if (root->left == NULL && root->right == NULL)
{
return 1;
}
return BinaryTreeLeaveSize(root->left) + BinaryTreeLeaveSize(root->right);
}
//求二叉树第K层结点个数
//==左子树第K层结点个数+右子树第K层结点个数
int BinaryTreeLevelKSize(BTNode* root,int k)
{
if (root == NULL)
{
return 0;
}
if (k == 1)
{
//说明已经到达第K层
return 1;
}
return BinaryTreeLevelKSize(root->left, k--) + BinaryTreeLevelKSize(root->right, k--);
}
//二叉树的深度
//==1+max(左子树深度,右子树深度)
int BinaryTreeDepth(BTNode* root)
{
if (root == NULL)
{
return 0;
}
int leftdepth = BinaryTreeDepth(root->left);
int rightdepth = BinaryTreeDepth(root->right);
return 1 + (leftdepth > rightdepth ? leftdepth : rightdepth);
}
//二叉树查找值为x的结点
//先在左子树中查找,如果没有找到再去右子树中找
BTNode* BinaryFindNode(BTNode* root,BTDataType num)
{
if (root == NULL)
{
return NULL;
}
//判断
if (root->data == num)
{
return root;
}
BTNode* left=BinaryFindNode(root->left,num);
if (left)
{
return left;
}
BTNode* right=BinaryFindNode(root->right, num);
if (right)
{
return right;
}
return NULL;
}
//二叉树的销毁
//后续遍历--左右根
void BinaryTreeDestory(BTNode** root)
{
if (root == NULL)
{
return;
}
BinaryTreeDestory(&((*root)->left));
BinaryTreeDestory(&((*root)->right));
free(*root);
*root = NULL;
}
//层序遍历
//借助数据结构--队列
//根节点入队列,循环判断队列是否为空,若队列不为空,取队头,出队头,
// 不为空的左右孩子入队列
void LevelOrder(BTNode* root)
{
Queue q;
QueueInit(&q);
QueuePush(&q, root);
while (!QueueEmpty(&q))
{
BTNode* top = QueueFront(&q);
printf("%c ", top->data);
QueuePop(&q);
if (top->left)
{
QueuePush(&q, top->left);
}
if (top->right)
{
QueuePush(&q, top->right);
}
}
QueueDestory(&q);
}
//判断是否是完全二叉树
//完全二叉树除了最后一层,其他每层的节点个数达到最大
//结点从左往右依次排列
//借助数据结构--队列
//根节点入队列,循环判断队列是否为空,若队列不为空,取队头,出队头,
// 左右孩子都入队列
bool BinaryTreeComplete(BTNode* root)
{
Queue q;
QueueInit(&q);
QueuePush(&q, root);
while (!QueueEmpty(&q))
{
BTNode* top = QueueFront(&q);
if (top == NULL)
{
break;
}
QueuePop(&q);
QueuePush(&q, top->left);
QueuePush(&q, top->right);
}
//跳出循环,说明遇到空节点,此时队列不为空
//查看队列中是否都是空节点,若都是空节点,则为完全二叉树
while (!QueueEmpty(&q))
{
BTNode* top = QueueFront(&q);
if (top != NULL)
{
//说明不是完全二叉树
QueueDestory(&q);
return false;
}
QueuePop(&q);
}
QueueDestory(&q);
return true;
}
Queue.h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef struct BinaryTreeNode* QDataType;
//队列的结构
typedef struct Queuelist
{
QDataType data;//存储的数据
struct Queuelist* next;
}Queuelist;
typedef struct Queue
{
Queuelist* phead;//指向队头节点的指针
Queuelist* ptail;//指向队尾节点的指针
int size;
}Queue;
//初始化队列
void QueueInit(Queue* pq);
//入队列
void QueuePush(Queue* pq, QDataType num);
//出队列
void QueuePop(Queue* pq);
//取队头数据
QDataType QueueFront(Queue* pq);
//取队尾数据
QDataType QueueBack(Queue* pq);
//队列有效元素个数
int QueueSize(Queue* pq);
//销毁
void QueueDestory(Queue* pq);
//队列判空
bool QueueEmpty(Queue* pq);
Queue.c
#include"Queue.h"
//队列判空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->phead == NULL;
}
//初始化队列
void QueueInit(Queue* pq)
{
assert(pq);
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
//入队列
void QueuePush(Queue* pq, QDataType num)
{
assert(pq);
//为num创建空间
Queuelist* newnode = (Queuelist*)malloc(sizeof(Queuelist));
if (newnode == NULL)
{
perror("malloc fail!");
exit(1);
}
newnode->data = num;
newnode->next = NULL;
if (pq->phead == NULL)
{
pq->phead = pq->ptail = newnode;
pq->size++;
}
else
{
pq->ptail->next = newnode;
pq->ptail = pq->ptail->next;
pq->size ++;
}
}
//出队列
void QueuePop(Queue* pq)
{
//如果队列只有一个节点
if (pq->phead == pq->ptail)
{
free(pq->phead);
pq->phead = pq->ptail = NULL;
pq->size--;
}
else
{
Queuelist* next = pq->phead->next;
free(pq->phead);
pq->phead = next;
pq->size--;
}
}
//取队头数据
QDataType QueueFront(Queue* pq)
{
assert(!QueueEmpty(pq));
return pq->phead->data;
}
//取队尾数据
QDataType QueueBack(Queue* pq)
{
assert(!QueueEmpty(pq));
return pq->ptail->data;
}
//队列有效元素个数
int QueueSize(Queue* pq)
{
return pq->size;
}
//销毁
void QueueDestory(Queue* pq)
{
assert(pq);
Queuelist* pcur = pq->phead;
while (pcur)
{
Queuelist* next = pcur->next;
free(pcur);
pcur = next;
}
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
test.c
#include"tree.h"
//手动创建链式二叉树
BTNode* BuyNode(BTDataType num)
{
BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
if (newnode == NULL)
{
perror("malloc fail!");
exit(1);
}
newnode->data = num;
newnode->left = newnode->right = NULL;
return newnode;
}
BTNode* CreateTree()
{
BTNode* nodeA = BuyNode('A');
BTNode* nodeB = BuyNode('B');
BTNode* nodeC = BuyNode('C');
BTNode* nodeD = BuyNode('D');
BTNode* nodeE = BuyNode('E');
BTNode* nodeF = BuyNode('F');
nodeA->left = nodeB;
nodeA->right = nodeC;
nodeB->left = nodeD;
nodeB->right = nodeE;
nodeC->left = nodeF;
return nodeA;
}
void test1()
{
BTNode* root=CreateTree();
int size = 0;
//PrevOrder(root);
//InOrder(root);
//PostOrder(root);
//size = BinaryTreeSize(root);
//size = BinaryTreeLeaveSize(root);
//size = BinaryTreeLevelKSize(root,2);
size = BinaryTreeDepth(root);
printf("%d\n", size);
/*if (BinaryFindNode(root, 'E'))
{
printf("找到了!");
}
else
{
printf("没找到!");
}*/
//LevelOrder(root);
/*if (BinaryTreeComplete(root))
{
printf("是完全二叉树");
}
else
{
printf("不是完全二叉树");
}*/
}
int main()
{
test1();
return 0;
}
到这里,链式结构我们全部就实现完了。
结尾
至此,【数据结构】初级阶段的二叉树的主线内容我们已经全部介绍完了!
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)