💗个人主页💗
⭐个人专栏——数据结构学习⭐
💫点击关注🤩一起学习C语言💯💫
导读:
我们在前面学习了单链表,顺序表,栈和队列,小堆。
今天我们来学习链式二叉树
关注博主或是订阅专栏,掌握第一消息。
1. 链式二叉树的概念和结构
链式二叉树(Linked Binary Tree)是一种基于链表实现的二叉树结构。在链式二叉树中,每个节点由三个部分组成:数据、左子节点和右子节点。
1.1 链式二叉树的特点
链式二叉树的特点包括:
- 每个节点都有一个数据项,可以是任意类型的数据。
- 每个节点都有一个左子节点和一个右子节点。如果某个节点没有左子节点或右子节点,对应的子节点指针就为空。
- 子节点可以是空的,也可以是另一个链式二叉树的根节点。这就构成了二叉树的递归结构。
链式二叉树的优点是可以动态地插入和删除节点,不需要预先指定树的大小。
同时,链式二叉树的节点可以随意分配在内存中,不需要连续的存储空间。
缺点是相对于数组实现的二叉树,链式二叉树需要额外的指针来连接节点,因此会占用更多的内存空间。
1.2 链式二叉树的遍历
链式二叉树的遍历方式有三种:前序遍历、中序遍历和后序遍历。
前序遍历先访问根节点,然后按照左子树、右子树的顺序遍历;
中序遍历先访问左子树,然后访问根节点,最后访问右子树;
后序遍历先访问左子树,然后访问右子树,最后访问根节点。
2. 二叉树的代码实现
2.1 创建二叉树
我们先创建一个六个节点的二叉树
typedef int BTDataType;
//typedef char BTDataType;
typedef struct BinaryTreeNode
{
BTDataType data;
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
}TreeNode;
//开辟空间并赋值
TreeNode* BuyTreeNode(int x)
{
TreeNode* node = (TreeNode*)malloc(sizeof(TreeNode));
assert(node);
node->data = x;
node->left = NULL;
node->right = NULL;
return node;
}
TreeNode* CreateTree()
{
TreeNode* node1 = BuyTreeNode(7);
TreeNode* node2 = BuyTreeNode(9);
TreeNode* node3 = BuyTreeNode(30);
TreeNode* node4 = BuyTreeNode(25);
TreeNode* node5 = BuyTreeNode(66);
TreeNode* node6 = BuyTreeNode(88);
node1->left = node2;
node1->right = node4;
node2->left = node3;
node4->left = node5;
node4->right = node6;
return node1;
}
2.2 前序遍历
前序遍历:访问根结点的操作发生在遍历其左右子树之前。
**注意:**左节点全部访问完之后才访问右节点。
遇到NULL,则返回,否则继续调用,直到遇到NULL
//前序遍历
void PrevOrder(TreeNode* root)
{
if (root == NULL)
{
printf("NULL ");
return NULL;
}
//先访问根节点
printf("%d ", root->data);
//之后访问左节点和右节点
PrevOrder(root->left);
PrevOrder(root->right);
}
在这里我们把空值也打印了出来
2.3 中序遍历
中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。
**注意:**是把一个节点的所有左子树访问完之后再去打印值,最后再访问右子树。
//中序遍历
void InOrder(TreeNode* root)
{
if (root == NULL)
{
printf("NULL ");
return NULL;
}
//访问完所有左子树后再打印值
InOrder(root->left);
printf("%d ", root->data);
InOrder(root->right);
}
2.4 计算节点个数
计算链式二叉树的节点个数可以通过递归的方式实现。
- 若二叉树为空,则节点个数为0。
- 若二叉树不为空,则节点个数为根节点的个数加上左子树的节点个数和右子树的节点个数。
- 使用递归对左子树和右子树分别计算节点个数。
- 返回根节点的个数加上左子树和右子树的节点个数。
// 节点个数
int TreeSize(TreeNode* root)
{
//如果为空返回0
if (root == NULL)
{
return 0;
}
//不为空调用左右节点,每次调用都会加一
return TreeSize(root->left)
+ TreeSize(root->right) + 1;
}
2.5 计算叶子节点的个数
叶子节点是指在树中没有子节点的节点,可以通过遍历树的方式来计算叶子节点的个数。
以下是计算叶子节点个数的递归算法:
- 如果树为空,则叶子节点个数为0。
- 如果树只有一个节点,则叶子节点个数为1。
- 否则,叶子节点个数等于左子树的叶子节点个数加上右子树的叶子节点个数。
// 叶子节点的个数
int TreeLeafSize(TreeNode* root)
{
if (root == NULL)
{
return 0;
}
//如果该节点的左节点和右节点都为空
if (!(root->left) && !(root->right))
{
return 1;
}
return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}
2.6 计算树的高度
计算树的高度可以使用递归的方法来完成。一个树的高度可以定义为从根节点到最远叶子节点的边数。
// 树的高度
int TreeHight(TreeNode* root)
{
if (root == NULL)
{
return 0;
}
//返回较大的一个
return fmax(TreeHight(root->left), TreeHight(root->right)) + 1;
}
2.7 计算树第k层节点个数
树的第k层节点个数取决于树的结构,不同的树的第k层节点个数可能不同。一般情况下,树的第k层节点个数为k层的节点数减去k-1层的节点数。
int TreeLevelK(TreeNode* root, int k)
{
assert(k > 0);
if (root == NULL)
{
return 0;
}
if (k == 1)
{
return 1;
}
return TreeLevelK(root->left, k - 1) +
TreeLevelK(root->right, k - 1);
}
k=1时,说明到达了k层
2.8 二叉树查找值为x的节点
二叉树查找值为x的节点可以通过递归来实现,具体步骤如下:
- 如果当前节点为空,则返回空。
- 如果当前节点的值等于x,则返回当前节点。
- 如果该节点的值不等于x,递归在左子树中查找值为x的节点。
- 如果该左子树节点的值不等于x,递归在右子树中查找值为x的节点。
TreeNode* TreeFind(TreeNode* root, BTDataType x)
{
if (root == NULL)
{
return NULL;
}
if (root->data == x)
{
return root;
}
//另设记录函数调用返回的值,再判断是否为kong
TreeNode* ret1 = TreeFind(root->left, x);
if (ret1)
{
return ret1;
}
TreeNode* ret2 = TreeFind(root->right, x);
if (ret2)
{
return ret2;
}
return NULL;
}
2.9 通过前序遍历的数组构建二叉树
通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树,通过遍历我们要建立如下效果:
// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
TreeNode* TreeCreate(char* a, int* pi)
{
if (a[*pi] == '#')
{
(*pi)++;
return NULL;
}
TreeNode* root = (TreeNode*)malloc(sizeof(TreeNode));
if (root == NULL)
{
perror("malloc fail");
exit(-1);
}
root->data = a[(*pi)++];
root->left = TreeCreate(a, pi);
root->right = TreeCreate(a, pi);
return root;
}
int main()
{
char arr[] = "ABD##E#H##CF##G##";
int i = 0;
TreeNode* rootstr = TreeCreate(arr, &i);
return 0;
}
2.10 层序遍历
链式二叉树的层序遍历可以通过使用队列来实现。具体步骤如下:
首先,将根节点入队。
进入循环,直到队列为空。
- 从队列中取出一个节点,并将其值输出。
- 如果该节点有左子节点,则将左子节点入队。
- 如果该节点有右子节点,则将右子节点入队。
循环结束后,层序遍历完成。
//层序遍历
void LevelOrder(TreeNode* root)
{
QNode q;
QueueInit(&q);
if (root)
{
QueuePush(&q, root);
}
int levelSize = 1;
while (!QueueEmpty(&q))
{
while (levelSize--)
{
TreeNode* front = QueueFront(&q);
QueuePop(&q);
printf("%c ", front->data);
if (front->left)
{
QueuePush(&q, front->left);
}
if (front->right)
{
QueuePush(&q, front->right);
}
}
printf("\n");
levelSize = QueueSize(&q);
}
printf("\n");
QueueDestroy(&q);
}
2.11 判断一棵树是否是完全二叉树
完全二叉树是指除了最后一层外,其他层的节点都是满的,且最后一层的节点从左到右依次填满。因此,我们可以通过遍历树的节点来判断是否是完全二叉树。
- 使用层次遍历的方法遍历树的节点。
- 当遇到第一个为空的节点时,停止遍历。
- 继续遍历树的节点,如果还存在非空节点,那么该树不是完全二叉树。
- 如果遍历完所有节点,都没有发现非空节点,那么该树是完全二叉树。
//判断一棵树是否是完全二叉树
bool TreeComplete(TreeNode* root)
{
Queue q;
QueueInit(&q);
if (root)
{
QueuePush(&q, root);
}
while (!QueueEmpty(&q))
{
TreeNode* front = QueueFront(&q);
QueuePop(&q);
if (front == NULL)
{
break;
}
QueuePush(&q, front->left);
QueuePush(&q, front->right);
}
// 前面遇到空以后,后面还有非空就不是完全二叉树
while (!QueueEmpty(&q))
{
TreeNode* front = QueueFront(&q);
QueuePop(&q);
if (front)
{
QueueDestroy(&q);
return false;
}
}
QueueDestroy(&q);
return true;
}
2.12 二叉树销毁
需要把每一个节点都销毁,值得注意的是,要从最下层的节点开始销毁,而不是根节点
//二叉树销毁
void DestroyTree(TreeNode* root)
{
if (root == NULL)
{
return NULL;
}
DestroyTree(root->left);
DestroyTree(root->right);
free(root);
}
3. 代码整理
该代码的层序遍历导入了我们之前写的队列
Queue.h
#define _CRT_SECURE_NO_WARNINGS
#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>
#include <math.h>
// 链式结构:表示队列
typedef int QDataType;
typedef struct QueueNode
{
QDataType val;
struct QueueNode* next;
}QNode;
// 队列的结构
typedef struct Queue
{
QNode* phead;
QNode* ptail;
int size;
}Queue;
// 初始化队列
void QueueInit(Queue* pq);
// 销毁队列
void QueueDestroy(Queue* pq);
// 队尾入队列
void QueuePush(Queue* pq, QDataType x);
// 队头出队列
void QueuePop(Queue* pq);
// 获取队列头部元素
QDataType QueueFront(Queue* pq);
// 获取队列队尾元素
QDataType QueueBack(Queue* pq);
// 检测队列是否为空,如果为空返回true,如果非空返回false
bool QueueEmpty(Queue* pq);
// 获取队列中有效元素个数
int QueueSize(Queue* pq);
Queue.c
#include "Queue.h"
// 初始化队列
void QueueInit(Queue* pq)
{
pq->phead = NULL;
pq->ptail = NULL;
pq->size = 0;
}
// 销毁队列
void QueueDestroy(Queue* pq)
{
assert(pq);
QNode* cur = pq->phead->next;
while (cur)
{
free(pq->phead);
pq->phead = cur;
cur = cur->next;
}
cur = NULL;
pq->phead = NULL;
pq->ptail = NULL;
pq->size = 0;
}
// 队尾入队列
void QueuePush(Queue* pq, QDataType x)
{
//开辟新空间
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail");
return;
}
newnode->val = x;
newnode->next = NULL;
if (pq->ptail == NULL)
{
pq->phead = pq->ptail = newnode;
}
else
{
pq->ptail->next = newnode;
pq->ptail = newnode;
}
pq->size++;
}
// 队头出队列
void QueuePop(Queue* pq)
{
assert(pq);
assert(pq->phead);
QNode* tmp = pq->phead;
pq->phead = pq->phead->next;
free(tmp);
tmp = NULL;
if (pq->phead == NULL)
{
pq->ptail = NULL;
}
pq->size--;
}
// 获取队列头部元素
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(pq->phead);
return pq->phead->val;
}
// 获取队列队尾元素
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(pq->ptail);
return pq->ptail->val;
}
// 检测队列是否为空,如果为空返回true,如果非空返回false
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->size == 0;
}
// 获取队列中有效元素个数
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
study.c
#include "Queue.h"
typedef int BTDataType;
//typedef char BTDataType;
typedef struct BinaryTreeNode
{
BTDataType data;
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
}TreeNode;
TreeNode* BuyTreeNode(int x)
{
TreeNode* node = (TreeNode*)malloc(sizeof(TreeNode));
assert(node);
node->data = x;
node->left = NULL;
node->right = NULL;
return node;
}
TreeNode* CreateTree()
{
TreeNode* node1 = BuyTreeNode(7);
TreeNode* node2 = BuyTreeNode(9);
TreeNode* node3 = BuyTreeNode(30);
TreeNode* node4 = BuyTreeNode(25);
TreeNode* node5 = BuyTreeNode(66);
TreeNode* node6 = BuyTreeNode(88);
node1->left = node2;
node1->right = node4;
node2->left = node3;
node4->left = node5;
node4->right = node6;
return node1;
}
//前序遍历
void PrevOrder(TreeNode* root)
{
if (root == NULL)
{
printf("NULL ");
return NULL;
}
//先访问根节点
printf("%d ", root->data);
//之后访问左节点和右节点
PrevOrder(root->left);
PrevOrder(root->right);
}
//中序遍历
void InOrder(TreeNode* root)
{
if (root == NULL)
{
printf("NULL ");
return NULL;
}
//访问完所有左子树后再打印值
InOrder(root->left);
printf("%d ", root->data);
InOrder(root->right);
}
// 节点个数
int TreeSize(TreeNode* root)
{
//如果为空返回0
if (root == NULL)
{
return 0;
}
//不为空调用左右节点,每次调用都会加一
return TreeSize(root->left)
+ TreeSize(root->right) + 1;
}
// 叶子节点的个数
int TreeLeafSize(TreeNode* root)
{
if (root == NULL)
{
return 0;
}
//如果该节点的左节点和右节点都为空
if (!(root->left) && !(root->right))
{
return 1;
}
return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}
// 树的高度
int TreeHight(TreeNode* root)
{
if (root == NULL)
{
return 0;
}
//返回较大的一个
return fmax(TreeHight(root->left), TreeHight(root->right)) + 1;
}
//树第k层节点个数
int TreeLevelK(TreeNode* root, int k)
{
assert(k > 0);
if (root == NULL)
{
return 0;
}
if (k == 1)
{
return 1;
}
return TreeLevelK(root->left, k - 1) +
TreeLevelK(root->right, k - 1);
}
// 二叉树查找值为x的节点
TreeNode* TreeFind(TreeNode* root, BTDataType x)
{
if (root == NULL)
{
return NULL;
}
if (root->data == x)
{
return root;
}
//另设记录函数调用返回的值,再判断是否为kong
TreeNode* ret1 = TreeFind(root->left, x);
if (ret1)
{
return ret1;
}
TreeNode* ret2 = TreeFind(root->right, x);
if (ret2)
{
return ret2;
}
return NULL;
}
// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
TreeNode* TreeCreate(char* a, int* pi)
{
if (a[*pi] == '#')
{
(*pi)++;
return NULL;
}
TreeNode* root = (TreeNode*)malloc(sizeof(TreeNode));
if (root == NULL)
{
perror("malloc fail");
exit(-1);
}
root->data = a[(*pi)++];
root->left = TreeCreate(a, pi);
root->right = TreeCreate(a, pi);
return root;
}
//二叉树销毁
void DestroyTree(TreeNode* root)
{
if (root == NULL)
{
return NULL;
}
DestroyTree(root->left);
DestroyTree(root->right);
free(root);
}
//层序遍历
void LevelOrder(TreeNode* root)
{
QNode q;
//初始化队列
QueueInit(&q);
if (root)
{
//把根节点入队列
QueuePush(&q, root);
}
//从1开始
int levelSize = 1;
while (!QueueEmpty(&q))
{
while (levelSize--)
{
TreeNode* front = QueueFront(&q);
QueuePop(&q);
printf("%c ", front->data);
//入左子树和右子树
if (front->left)
{
QueuePush(&q, front->left);
}
if (front->right)
{
QueuePush(&q, front->right);
}
}
printf("\n");
//获取队列有效数据个数
levelSize = QueueSize(&q);
}
printf("\n");
QueueDestroy(&q);
}
//判断一棵树是否是完全二叉树
bool TreeComplete(TreeNode* root)
{
Queue q;
QueueInit(&q);
if (root)
{
QueuePush(&q, root);
}
//遍历入队列
while (!QueueEmpty(&q))
{
TreeNode* front = QueueFront(&q);
QueuePop(&q);
if (front == NULL)
{
break;
}
QueuePush(&q, front->left);
QueuePush(&q, front->right);
}
// 前面遇到空以后,后面还有非空就不是完全二叉树
while (!QueueEmpty(&q))
{
TreeNode* front = QueueFront(&q);
QueuePop(&q);
if (front)
{
QueueDestroy(&q);
return false;
}
}
QueueDestroy(&q);
return true;
}
void Test1()
{
TreeNode* root = CreateTree();
printf("前序遍历:\n");
PrevOrder(root);
printf("\n");
printf("中序遍历:\n");
InOrder(root);
printf("\n");
printf("节点个数:%d\n", TreeSize(root));
printf("叶子节点的个数:%d\n", TreeLeafSize(root));
printf("树的高度:%d\n", TreeHight(root));
printf("树第k层节点个数:%d\n", TreeLevelK(root, 3));
int x = 0;
printf("请输入想要查找的x值:》");
scanf("%d", &x);
printf("查找值为x的节点: %p\n", TreeFind(root, x));
bool ret = TreeComplete(root);
if (ret)
{
printf("yes\n");
}
else
{
printf("no\n");
}
}
void Test2()
{
char arr[] = "ABD##E#H##CF##G##";
int i = 0;
TreeNode* rootstr = TreeCreate(arr, &i);
LevelOrder(rootstr);
}
int main()
{
//Test1();
Test2();
return 0;
}