目录
二叉树初识
实现二叉树的功能
功能前操作
遍历功能
操作、计算、查询功能
源码
因为上次二叉树的文章的功能不全,所以这次更新一个完整版的二叉树文章
这篇文章有些功能是需要用到队列知识的,所以对队列不了解的可以先去看看这篇队列--详解
因为这次需要调用到栈的代码 所以本篇代码会较多 但我会在每个源码标上各个所属文件名称。
二叉树初识
我们先认识下二叉树
二叉树(Binary tree)是树形结构的一个重要类型。许多实际问题抽象出来的数据结构往往是二叉树形式,即使是一般的树也能简单地转换为二叉树,而且二叉树的存储结构及其算法都较为简单,因此二叉树显得特别重要。二叉树特点是每个节点最多只能有两棵子树,且有左右之分
树相关词汇与解释
节点的度:一个节点有多少个孩子,就叫节点的度
叶子节点:节点度为0的节点,就叫叶子节点(相当于该节点没有左、右孩子节点)
分支节点:节点度不为0的节点,叫分支节点
父节点:节点的父亲
子节点:节点的孩子
兄弟节点:具有相同父亲节点的叫兄弟节点
树的度:最大节点的度
节点层次:从根开始定义根为第一层,根的节点为第二层
堂兄弟节点:双亲在同一层
节点的祖先:根节点是所有节点的祖先
满足二叉树的条件:
- 本身是有序树
- 树中包含的各个节点的度不能超过 2,即只能是 0、1 或者 2
![](https://img-blog.csdnimg.cn/778e1f5561784f279b3749fec309ca99.png)
实现二叉树的功能
![](https://img-blog.csdnimg.cn/a946a6666ee84be2b73dd873c3f8382f.png)
功能前操作
需要用到的头文件
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
二叉树的节点为左、右节点,那么就创建两个指针,分别指向左、右节点,最后再添加一个存储该节点的数据的变量。再对类型名重命名,以方便以后更该类型
typedef char STDataType;
typedef struct Linode
{
struct Linode* left;
struct Linode* right;
STDataType data;
}TR;
1.1创建节点功能
先申请一个内存空间(即申请一个结点),并且对此空间进行检查
让此结点存放数据 并且先让它的left,right指针指向空
最后再返回该结点
TR* BuyTree(STDataType x)
{
TR* newnode = (TR*)malloc(sizeof(TR));
assert(newnode);
newnode->data = x;
newnode->left = NULL;
newnode->right = NULL;
return newnode;
}
1.2节点连接功能
创建一些节点后,对他们进行连接,再返回头节点。
TR* CreeTree()
{
TR*A1 = BuyTree('A');
TR*B1 = BuyTree('B');
TR*C1 = BuyTree('C');
TR*D1 = BuyTree('D');
TR*E1 = BuyTree('E');
A1->left = B1;
A1->right = C1;
B1->left = D1;
B1->right = E1;
return A1;
}
代码中连接的,可以理解为:
![Lonely丶墨轩](https://img-blog.csdnimg.cn/9676e066f8f7488db1e097d89efd451c.png)
实现好功能前操作,可以开始实现遍历功能了~
遍历功能
2.1前序遍历
前序遍历的走读思路:根---左节点---右节点
那么通过递归的方式即为:先打印根节点,再递归左节点,最后递归右节点
通过上图(构建的二叉树) 那么它的流程为:
(为了体现二叉树的结构,当递归遇到空节点时,先打印NULL再return)
1.A(即根节点)先打印A
2.递归左节点 再打印B
3.再递归左节点 打印的D
4.D递归左节点时为空,那么返回到B
5.B去递归右节点 打印E
6.E去递归左节点,左节点为空,返回到B
7.B左右节点递归完,返回到A
8.A再去递归右节点,打印C
则 前序遍历为 A---B---D---NULL---NULL---E---NULL---NULL---C---NULL---NULL
那么我们运行下,看打印结果
![](https://img-blog.csdnimg.cn/43f7eaf41783483b81f67d5110e17344.png)
void PrevOrder(TR* root)
{
if (root == NULL)
{
printf("NULL->");
return;
}
printf("%c->", root->data);
PrevOrder(root->left);
PrevOrder(root->right);
}
2.2中序遍历
中序遍历的走读思路:左节点---根---右节点
那么通过递归的方式即为:先递归左节点---打印根---再递归右节点
通过上图(构建的二叉树) 那么它的流程为:
1.先递归左节点 A递归到B 打印B
2.B再递归左节点 D
3.D再递归到左、右节点,均为空,返回B
4.B再递归右节点 E 打印E
5.E再递归到左、右节点、均为空 返回B
6.B左右节点均递归完后,返回A
7.A的左节点递归完后 打印A
8.A再递归右节点 C 再打印C
9.C再递归左、右节点,均为空
10.返回A
结束
则 中序遍历为 B---D---NULL---NULL---E---NULL---NULL---A---C---NULL---NULL
那么我们运行下 看打印结果
![](https://img-blog.csdnimg.cn/7441a253d0364723a20358d7599948af.png)
void InOrder(TR* root)
{
if (root == NULL)
{
printf("NULL->");
return;
}
PrevOrder(root->left);
printf("%c->", root->data);
PrevOrder(root->right);
}
2.3后续遍历
后序遍历的走读思路:左节点---右节点---根
那么通过递归的方式即为:先递归左节点---再递归右节点---打印根
通过上图(构建的二叉树) 那么它的流程为:
先递归左节点B 打印B
B再递归D 打印D
D再递归左、右节点,均为空,返回到B
B再递归到右节点E
E递归到左、右节点,均为空,返回到B
B的左右节点递归完后,返回A
A再递归到C 打印C
C再递归左、右节点均为空,再返回到A
再打印A
结束
则 后续遍历为 B---D---NULL---NULL---E---NULL---NULL---C---NULL---NULL---A
那么我们运行下 看打印结果
![](https://img-blog.csdnimg.cn/1bd6e65f454141de8121840e7b4bf8c4.png)
void CurOrder(TR* root)
{
if (root == NULL)
{
printf("NULL->");
return;
}
PrevOrder(root->left);
PrevOrder(root->right);
printf("%c->", root->data);
}
2.4层序遍历
层序遍历相交与另外三个遍历,是最好理解的,但是代码却比其他三个遍历难很多。
那么层序遍历是如果走读的呢?顾名思义就是一层层遍历。如图:
![Lonely丶墨轩](https://img-blog.csdnimg.cn/9676e066f8f7488db1e097d89efd451c.png)
先打印第一层 A 再打印第二层 B C 再打印第三层 D E
则 层序遍历为 A---B---C---D---E 因为层序遍历比较好体现结果,所以不用打印空
因为这里是需要用到队列的,而C语言没有队列的库 所以我们需要自己手动写一个(这也是C的缺点之一,栈、队列 C语言均没有(有需要队列的我待会在最下方会给出源码)
层序代码思路
首先初始化一个栈,
1.判断根节点是否为空,不为空时 把根节点放入队列 然后进循环(循环条件为队列为空时结束)
2.利用结构体指针 接收头结点(即存储这个头数据)
3.我们让刚才进队列的头数据,出队列
4.利用这个指针存储的数据 打印这个结点的数据
5.我们再判断它的左、右结点是否为空,不为空,就把不为空的结点放入队列
需要注意 因为队列原来的STDataType原来是int 而我们需要返回一个结构体指针类型,所以重定义类型名的优势就体现出来了 我们只需要在栈的重定义类型名修改为结构体指针类型即可
typedef struct Linode* STDaType;
6.循环 至循环结束
![](https://img-blog.csdnimg.cn/d2eab616730645778f1fec8ab21820fd.gif)
这个是简化动图,添加文字的动图做的很乱,以后学好了,可以添加更详细的动图!
操作、计算、查询功能
3.1计算结点个数
计算二叉树结点个数思路就是 递归到不为空的结点时+1 最后返回共加了几次1即为结点个数
首先判断结点是否为空,若为空返回0 若不为空则返回1。
这里有两种写法(思路一样)
一种是传一个值进去 没递归一次 判断不为空时,就自增++,但递归会有栈帧问题,所以需要换地址,会稍微麻烦点。
void TreeSize(TR* root,int* p)
{
/*return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;*/
if (root == NULL)
return 0;
++(*p);
TreeSize(root->left,p);
TreeSize(root->right,p);
}
另一种是用三目操作服,每次递归都+1,如果为空时返回0,此方法相较于上面的方法更简洁,但不利于理解。
int TreeSize(TR* root)
{
return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}
![](https://img-blog.csdnimg.cn/5f56f17a7c8d4b04a70a1bca6073e103.gif)
3.2计算叶子结点个数
首先要了解叶子结点 叶子结点即为 当它没有左结点、右结点时,就把它称为叶子结点
那么代码则可以判断 当这个结点左、右子树为空时就返回1
代码就迎刃而解
首先判断结点是否为空 若为空返回0
若它左、右结点均为空,返回1
依次递归他的左与右最后将他们相加(则为左子树共有几个叶子)+(右子树共右几个叶子)
int BTreeLeafSize(TR* root)
{
if (root == NULL)
return 0;
if (root->left == NULL && root->right == NULL)
return 1;
return BTreeLeafSize(root->left) + BTreeLeafSize(root->right);
}
![](https://img-blog.csdnimg.cn/3970a04e0cdc49d9a83a65af1fd1d732.gif)
3.3计算第K层结点个数
已根结点为1时 当走到第K层时 计算第K层的结点个数
本题思路需要多用到一个遍历,这个遍历记录着要计算的第K层 每递归一次 让K--,当K==1时,说明K已经走到了需要计算的那层,这时再++,最后把(左子树返回的总树)+(右子树返回的总数)就可以求出第K层的结点个数
先判断结点是否为空若为空返回0即可
这里只需判断一个条件 即 K==1时 就返回你
最后到递归左和递归右相加 即可求出
int BreeKlevelSize(TR* root, int k)
{
if (root == NULL)
return 0;
if (k == 1)
return 1;
return BreeKlevelSize(root->left, k - 1) + BreeKlevelSize(root->right, k - 1);
}
![](https://img-blog.csdnimg.cn/06e255b67bac4030874eaa05fc519a21.gif)
3.4计算计算左子和又子大的节点个数
这里只要计算左子树的结点总数与右子树的结点总数相比 大的那方再+1即为左子\右子大的结点总数
这里判断root是否为空 为空时就返回0
用left存放左子树
用right存放右子树
利用三目操作符相比 无论谁大 都+1(计算结点个数)
int BTreeDePth(TR* root)
{
if (root == NULL)
return 0;
int left = BTreeDePth(root->left);
int right = BTreeDePth(root->right);
return left > right ? left + 1 : right + 1;
}
![](https://img-blog.csdnimg.cn/21d1cc30a65e415b9a060d788400b440.gif)
3.5二叉树查找
查找 顾名思义就是查找某个值
思路为 依次递归,若查找到则返回,若查找不到,赋值空,若一直查找不到,最后则会返回空
TR* BTreeFind(TR* root, STDataType x)
{
if (root == NULL)
return 0;
if (root->data == x)
return root;
TR* rel1 = BTreeFind(root->left, x);
if (rel1)
return rel1;
TR* rel2 = BTreeFind(root->right, x);
if (rel2)
return rel2;
return NULL;//若到最后都查找不到 返回NULL
}
![](https://img-blog.csdnimg.cn/003e06d370fc4cd497f4d771e668e754.gif)
3.6判断二叉树是否是完全二叉树
先来看完全二叉树的定义
完全二叉树的特点:叶子结点只能出现在最下层和次下层,且最下层的叶子结点集中在树的左部。
一棵深度为k且有个结点的二叉树称为满二叉树
根据二叉树的性质 满二叉树每一层的结点个数都达到了最大值, 即满二叉树的第i层上有(2的i次方-1)个结点 (i≥1)
这里是需要用到队列的,它的代码思路根层序有点相像,并且思路较好理解
我们先来看非完全二叉树和完全二叉树在队列中结构
![](https://img-blog.csdnimg.cn/b7be22342932469d9e7380edbd928a13.png)
不难看出他们区别 非完全二叉树中间会有掺杂NULL 而 完全二叉树中间不会掺杂NULL
所以我们可以利用此特点
思路 当在队列中遇到NULL 就去判断NULL后面是否有值 如果有 就不是完全二叉树
若没有 就是完全二叉树
代码思路
1.创建队列。并初始化
2.当根结点不为空时,让他入队列。
3.用front接收结点,并让他出队列,判断这个front是否为空,当他为空,结束循环,让他进入下一个循环
4.当front不为空时,让他入结点的左,右结点
5.当循环结束
6.让他用front取结点 再出队列,当front不为空时,
(说明后面还有值 不是完全二叉树,返回false(假))
7.当front为空时,就继续循环,
8.直到循环结束时,还是为空时,说明他后面没有值,是完全二叉树 返回true
需要注意 因为队列原来的STDataType原来是int 而我们需要返回一个结构体指针类型,所以重定义类型名的优势就体现出来了 我们只需要在栈的重定义类型名修改为结构体指针类型即可
typedef struct Linode* STDaType;
![](https://img-blog.csdnimg.cn/f0651b7addfa458b8c5462e8f815bfa1.gif)
![](https://img-blog.csdnimg.cn/676f5b18dade451d91c668c2973a4247.gif)
这两个动图分别对应了完全二叉树和非完全二叉树的情况
bool BTcomDlete(TR* root)
{
Qnene p;
QueueInit(&p);
if (root)
{
QueuePush(&p, root);
}
while (!QueueEmpty(&p))
{
TR* front = QueueHeadTop(&p);
QueuePop(&p);
if (front == NULL)
{
break;
}
QueuePush(&p, front->left);
QueuePush(&p, front->right);
}
while (!QueueEmpty(&p))
{
TR* front = QueueHeadTop(&p);
QueuePop(&p);
if (front)
{
return false;
QueueDestory(&p);
}
}
return true;
QueueDestory(&p);
}
3.7销毁二叉树
销毁二叉树就是要销毁二叉树的每个结点
那么可以利用前面的后续遍历的原理 先走到最后的结点,先释放最后的结点,再返回,再释放,最后就可以全部释放
void BinaryTreeDestory(TR* root)
{
if (root == NULL)
{
return;
}
BinaryTreeDestory(root->left);
BinaryTreeDestory(root->right);
free(root);
}
![](https://img-blog.csdnimg.cn/9739ffeff57444e0a43dd571271b1d8e.gif)
源码
这个源码为队列是声明
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef struct Linode* STDaType;
typedef struct QListNode
{
struct QListNode* next;
STDaType data;
}Queue;
typedef struct Qnene
{
Queue* head;
Queue* tail;
}Qnene;
//初始化队列
void QueueInit(Qnene* Q);
//队尾入
void QueuePush(Qnene* Q, STDaType x);
//队头出
void QueuePop(Qnene* Q);
//取对头元素
STDaType QueueHeadTop(Qnene* Q);
//取队尾元素
STDaType QueueTailTop(Qnene* Q);
//判空
bool QueueEmpty(Qnene* Q);
//获取队列的元素个数
int QueueSize(Qnene* Q);
//释放
void QueueDestory(Qnene* Q);
这个源码为队列的实现
#include"队列.h"
//初始化队列
void QueueInit(Qnene* Q)
{
assert(Q);
Q->head = Q->tail = NULL;
}
//队尾入队列
void QueuePush(Qnene* Q, STDaType x)
{
assert(Q);
Queue* newnode = (Queue*)malloc(sizeof(Queue));
if (newnode == NULL)
{
printf("malloc fail\n");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
if (Q->tail == NULL)
{
Q->head = Q->tail = newnode;
}
else
{
Q->tail->next = newnode;
Q->tail = newnode;
}
}
//对头出
void QueuePop(Qnene* Q)
{
assert(Q);
if (Q->head->next == NULL)
{
free(Q->head);
Q->head = Q->tail = NULL;
}
else
{
Queue* tail = Q->head->next;
free(Q->head);
Q->head = tail;
}
}
//取队头元素
STDaType QueueHeadTop(Qnene* Q)
{
assert(Q);
return Q->head->data;
}
//取队尾元素
STDaType QueueTailTop(Qnene* Q)
{
assert(Q);
return Q->tail->data;
}
//判空
bool QueueEmpty(Qnene* Q)
{
assert(Q);
return Q->head == NULL;
}
//获取队列的元素个数
int QueueSize(Qnene* Q)
{
int size = 0;
Queue* cur = Q->head;
while (cur)
{
++size;
cur = cur->next;
}
return size;
}
//释放
void QueueDestory(Qnene* Q)
{
Queue* tail = Q->head;
while (tail)
{
Queue* next = tail->next;
free(tail);
tail = next;
}
Q->head = Q->tail = NULL;
}
这个源码为二叉树的声明
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef char STDataType;
typedef struct Linode
{
struct Linode* left;
struct Linode* right;
STDataType data;
}TR;
//层序遍历
void LeveOreder(TR* root);
TR* CreeTree();
//前序遍历
void PrevOrder(TR* root);
//中序遍历
void InOrder(TR* root);
//后续遍历
void CurOrder(TR* root);
//计算二叉树节点个数
int TreeSize(TR* root);
//二叉树叶子节点个数
int BTreeLeafSize(TR* root);
//计算二叉树第K层的节点个数
int BreeKlevelSize(TR* root, int k);
//计算左子和又子大的节点个数
int BTreeDePth(TR* root);
//二叉树查找
TR* BTreeFind(TR* root, STDataType x);
//判断二叉树是否是完全二叉树
bool BTcomDlete(TR* root);
//销毁二叉树
void BinaryTreeDestory(TR* root);
这个源码为二叉树的实现
#include"Tree.h"
#include"队列.h"
TR* BuyTree(STDataType x)
{
TR* newnode = (TR*)malloc(sizeof(TR));
assert(newnode);
newnode->data = x;
newnode->left = NULL;
newnode->right = NULL;
return newnode;
}
void PrevOrder(TR* root)//前序遍历
{
if (root == NULL)
{
printf("NULL->");
return;
}
printf("%c->", root->data);
PrevOrder(root->left);
PrevOrder(root->right);
}
void InOrder(TR* root)//中序遍历
{
if (root == NULL)
{
printf("NULL->");
return;
}
PrevOrder(root->left);
printf("%c->", root->data);
PrevOrder(root->right);
}
void CurOrder(TR* root)//后续遍历
{
if (root == NULL)
{
printf("NULL->");
return;
}
PrevOrder(root->left);
PrevOrder(root->right);
printf("%c->", root->data);
}
TR* CreeTree()
{
TR*A1 = BuyTree('A');
TR*B1 = BuyTree('B');
TR*C1 = BuyTree('C');
TR*D1 = BuyTree('D');
TR*E1 = BuyTree('E');
A1->left = B1;
A1->right = C1;
B1->left = D1;
B1->right = E1;
return A1;
}
void LeveOreder(TR* root)//层序遍历
{
Qnene q;
QueueInit(&q);//初始化
if (root)
{
QueuePush(&q, root);
}
while (!QueueEmpty(&q))
{
TR* front = QueueHeadTop(&q);
QueuePop(&q);
printf("%c->", front->data);
if (front->left)
{
QueuePush(&q, front->left);
}
if (front->right)
{
QueuePush(&q, front->right);
}
}
QueueDestory(&q);
}
//计算二叉树节点个数
int TreeSize(TR* root)
{
return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}
//计算二叉树叶子节点个数
int BTreeLeafSize(TR* root)
{
if (root == NULL)
return 0;
if (root->left == NULL && root->right == NULL)
return 1;
return BTreeLeafSize(root->left) + BTreeLeafSize(root->right);
}
//计算二叉树第K层的节点个数
int BreeKlevelSize(TR* root, int k)
{
if (root == NULL)
return 0;
if (k == 1)
return 1;
return BreeKlevelSize(root->left, k - 1) + BreeKlevelSize(root->right, k - 1);
}
//计算计算左子和又子大的节点个数
int BTreeDePth(TR* root)
{
if (root == NULL)
return 0;
int left = BTreeDePth(root->left);
int right = BTreeDePth(root->right);
return left > right ? left + 1 : right + 1;
}
//二叉树查找
TR* BTreeFind(TR* root, STDataType x)
{
if (root == NULL)
return 0;
if (root->data == x)
return root;
TR* rel1 = BTreeFind(root->left, x);
if (rel1)
return rel1;
TR* rel2 = BTreeFind(root->right, x);
if (rel2)
return rel2;
return NULL;
}
//判断二叉树是否是完全二叉树
bool BTcomDlete(TR* root)
{
Qnene p;
QueueInit(&p);
if (root)
{
QueuePush(&p, root);
}
while (!QueueEmpty(&p))
{
TR* front = QueueHeadTop(&p);
QueuePop(&p);
if (front == NULL)
{
break;
}
QueuePush(&p, front->left);
QueuePush(&p, front->right);
}
while (!QueueEmpty(&p))
{
TR* front = QueueHeadTop(&p);
QueuePop(&p);
if (front)
{
return false;
QueueDestory(&p);
}
}
return true;
QueueDestory(&p);
}
//销毁二叉树
void BinaryTreeDestory(TR* root)
{
if (root == NULL)
{
return;
}
BinaryTreeDestory(root->left);
BinaryTreeDestory(root->right);
free(root);
}
这个源码为测试二叉树功能
#include"Tree.h"
void Test()
{
TR* root = CreeTree();
printf("二叉树前序遍历:\n");
PrevOrder(root);//二叉树的前序遍历
printf("\n");
printf("二叉树中序遍历:\n");
InOrder(root);//二叉树的中序遍历
printf("\n");
printf("二叉树后序遍历:\n");
CurOrder(root);//二叉树的后续遍历
printf("\n");
printf("二叉树层序遍历:\n");
LeveOreder(root);
printf("\n");
printf("计算二叉树节点个数:\n");
int size = TreeSize(root);
printf("二叉树节点个数:%d\n", size);
printf("计算二叉树叶子节点个数:\n");
int size1 = BTreeLeafSize(root);
printf("二叉树叶子节点个数:%d\n", size1);
printf("求第K层的节点个数:\n");
int size2 = BreeKlevelSize(root, 2);
printf("第K层节点个数:%d\n", size2);
printf("计算左子和又子大的节点个数\n");
int size3=BTreeDePth(root);
printf("左子或右子大的节点个数为: %d\n", size3);
printf("查找x的节点\n");
TR* tree=BTreeFind(root, 'C');
if (tree != NULL)
{
tree->data = 'F';
LeveOreder(root);
printf("修改成功!");
}
printf("\n");
printf("判断二叉树是否是完全二叉树\n");
if (BTcomDlete(root))
{
printf("此二叉树是完全二叉树\n");
}
else
{
printf("此二叉树不是完全二叉树\n");
}
printf("二叉树的销毁\n");
BinaryTreeDestory(root);
//free(root);
root = NULL;
printf("销毁成功!");
}
int main()
{
Test();
return 0;
}
如果本篇对您有帮助,希望能获得您的赞!