世事浮沉,有太多的责任需要我们担当。生活中总有些挫折和磨难,让我们觉得快要扛不住了。
但当我们咬牙坚持过,那段难熬的时光后,发现并没有想象中的那么难。
就像岛上书店中的一句话,每个人的生命中都有最艰难的那一年,等你迈过去了,
人生就会变得高远辽阔,生活总是泥沙俱下,每个看似百毒不侵的成年人,都有
过无数次绝望和崩溃的时刻,要走的路已经选定了,该怎么走,走多久,
都是命中注定的,走多久都是命中注定。在一切变好之前,我们总要经历一段
不开心的日子。这段日子也许很长,也许只是一觉醒来,人只有不断地经历跌倒
才能顽强与坚毅的性格。
———————— 一禅心灵庙语
- 首先我们需要对项目进行合理的分类,那个类,那个文件实现什么样的功能: 比如:一个类是用于定义什么类型,方法的 ,另一个类是用于什么头main 的,再另外一个类是用于对于什么功能上的实现的 ,合理的分类,可以提高我们项目的可读性,让我们自身不会因为自己随着代码量的增加,对应功能上的增加而导致我们越敲越乱,越写越蒙
- 对于量上我们可以,每实现了两三个 功能就运行测试看看,是否存在错误,如果存在出现了错误,我们可以在一定的量(范围内),快速的找出哪里出现了问题,从而对它进行调试 ,解决问题,而不是,把项目整体写完了或者是已经实现了好几十个功能,才开始运行测试项目,结果一运行,bug,警告 冒出一大坨,我们先不说,让不让人,抓头发,就是找出问题,恐怕都需要不时间上的浪费吧
- 对于代码上的注释,没事多写明白一点,或许有一天,你会感想曾经,那个写了注释的自己或同事
- 对于bug 不要害怕,一点一点的调试看看,找出问题的所在,慢慢来,要有耐心,要明白一点现在bug 写的多,以后bug 跟你说拜拜,现在bug ,不解决,bug 天天对你说 明天见
在实现生活中,存在很多的一对多 的情况需要处理,所以我们需要研究这种一对多的数据结构 —— “树” ,
树: 是一种非线性的数据结构,它是由 n (N >= 0 ) 个有限的节点组成的一个具有层次关系的集合,把它叫做树,是因为它看起来像一个倒挂的树,也就是说它是根朝上,而叶朝下的。
树的一些特点
树的结构相对线性表而言是比较复杂的,要存储表示起来也比较麻烦,在实际中树的有许多种表示的方式:如:双亲表示法,孩子表示法,孩子兄弟表示法等等 ,这里我们只简单介绍一种 孩子兄弟表示法
所谓的孩子兄弟表示法 ,其实就是创建一个结构体,存放一颗树中的 左孩子的地址,和其兄弟的地址的指针,从而建立树与子树的联系关系。
// 孩子兄弟表示法
typedef char BTDataType;
typedef struct Node
{
struct Node* firstChild; // 指向左孩子的节点的指针
struct Node* pNextBrother; // 指向其兄弟节点的指针
BTDataType data; // 树节点存放的数据
}Node;
树的实际运用主要是在 文件系统中的目录 ,因为使用树结构可以,明显的表示出每个目录中的文件的路径位置是绝对的 ,不存在重复的路径
二叉树 :是 (n > = 0 ) 个节点的有限集合,该集合可以 0 ,为空集合(空二叉树),或者由一个根节点和两棵互不相交 的,分别称为根的左子树 和右子树 的二叉树组合成的
二叉树的特点
特殊的二叉树
满二叉树 : 在一棵二叉树中,如果所有分支中的节点都存在左子树和右子树,并且所有的叶子都在同一层上,这样的二叉树称为 满二叉树 。注意 : 单是每个节点都存在左子树和右子树,并不能认定是 满二叉树,还必须要所有的叶子节点都是在同一层上,这就做到了整棵树的平衡,才为 满二叉树。
完全二叉树 :是效率很高的数据结构,完全二叉树是由满二叉树,引出来的,对于深度 为 K 的,有 n 个节点的二叉树,当且仅当其每个节点都与深度 为 K 的满二叉树中的编号,从 1 至 n 的节点—— 对应时,称为完全二叉树。说白了就是,完全二叉树是 所有的叶子节点在最后一层,并且所有的叶子节点是连续紧挨着的,不可以分开。满二叉树是完全二叉树中的特殊情况,就是完全二叉树中的所有节点都满了
注意: 满二叉树一定是完全二叉树,而完全二叉树不一定是满二叉树
二叉树的链式存储结构 用链表来表示一棵二叉树,即用链来指示数据之间的逻辑关系。通常的方法是链表中每个节点由三个域组成,数据域和左右指针域,左右指针分别用来给出,该节点的左孩子和右孩子所在的链节点的存储地址。链式结构又分为 二叉链 和 三叉链 ,当前我们学习中一般都是二叉链表,而一般高级数据结构如,红黑树,会用到三叉树
typedef char BTDataType;
// 二叉链
typedef struct BinaryTwoNode
{
struct BinaryTwoNode* pLeft; // 指向当前左孩子的节点的指针
struct BinaryTwoNode* pRight; // 指向当前右孩子的节点的指针
BTDataType data; // 存放的二叉树中的节点的数据
}BTwoNode;
typedef char BTDataType;
// 三叉链
typedef struct BinaryTreeNode
{
struct BinaryTreeNode* pParent; // 指向当前双亲节点的指针
struct BinaryTreeNode* pLeft; // 指向当前左孩子的指针
struct BinaryTreeNode* pRight; // 指向当前右孩子的指针
BTDataType data; // 当前二叉树节点存放的数据
}BTreeNode;
这里我们使用链表实现二叉树的
通过链表实现二叉树,一个数据域,两个指针域
typedef char BTDataType;
// 二叉树的结构体
typedef struct BinaryTreeNode
{
struct BinaryTreeNode* left; // 当前节点的左子树
struct BinaryTreeNode* right; // 当前节点的右子树
BTDataType data; // 当前节点的存放的数据
}BTNode;
传存放的数据,通过 malloc 函数动态开辟内存(堆区上),生成二叉树节点,
注意 : 一定要判断动态开辟的空间是否,真正开辟成功了,如果失败了,又使用该空间,的话,会报错的,所以一定要再使用前,进行判断,确保开辟成功。再使用,失败了,给予代码提示,容易定位错误,提高代码的健壮性。
// 生成二叉树节点
BTNode* BiTreeCreate(BTDataType x)
{
// malloc 堆区上开辟空间
BTNode* new = (BTNode*)malloc(sizeof(BTNode));
// 判断动态开辟空间是否,成功
if (NULL == new)
{
perror("malloc error"); // 堆区内存开辟空间,失败,错误提示
exit(-1); // 同时,非正常退出,程序
}
else // 开辟空间成功
{
new->data = x; // 数据存入到,当前节点的数据域中
new->left = NULL;
new->right = NULL;
}
return new; // 返回堆区上开辟的空间的地址指针
}
我们根据如下的二叉树,为每个二叉树节点之间建立相关的联系
int main()
{
// 生成二叉树节点
BTNode* A = BiTreeCreate('A');
BTNode* B = BiTreeCreate('B');
BTNode* C = BiTreeCreate('C');
BTNode* D = BiTreeCreate('D');
BTNode* E = BiTreeCreate('E');
// 构建二叉树联系
A->left = B;
A->right = C;
B->left = D;
B->right = E;
return 0;
}
前序遍历又被称为是先根遍历,规则是若二叉树为空,则空操作返回,否则先访问根节点——>再前序遍历当前节点的左子树——> 最后再前序遍历访问当前节点的右子树。
总结一点就是,把每个节点都当成是一个树 包含( 根节点 左子树,右子树) [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
,这样我们就可以使用,分治算法,分而治之,大问题分成类似的子问题,子问题,再分成子问题,直到子问题不可再分割,了。这种方式的处理,运用递归,如下图中的二叉树:
根据如上二叉树其 前序遍历结果 :A B D NULL NULL E NULL NULL C NULL NULL
// 前序遍历
void PrevOrder(BTNode* root)
{
if (NULL == root) // 递归结束条件
{
printf("NULL ");
return; // 返回调用该函数的位置
}
else
{
printf("%c ", root->data); // 访问当前的根节点
PrevOrder(root->left); // 递归,访问当前的节点的左子树
PrevOrder(root->right); // 递归,访问当前的节点的右子树
}
}
运行结果
前序遍历递归结构展开图
中序遍历又称为中根遍历,规则是若树为空,则空操作返回,否则 不为空,中序遍历当前根节点的左子树——> 然后再访问中序遍历当前的根节点——> 最后中序遍历当前节点的右子树。同样把每个节点看作是 (根,左,右) [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
,来递归访问,左子树——> 根——> 右子树
上述二叉树的中序遍历结果 : NULL D NULL B NULL E NULL A NULL C NULL
// 中序遍历
void InOrder(BTNode* root)
{
if (NULL == root) // 递归结束条件
{
printf("NULL ");
return; // 返回,调用该函数的位置
}
else
{
InOrder(root->left); // 递归,当前节点的左子树
printf("%c ", root->data); // 打印当前节点的数据
InOrder(root->right); // 递归,当前节点的右子树
}
}
运行结果
中序遍历递归结构展开图
后序遍历又称为后根遍历,规则是若树为空,则空操作返回,否则不为空,先后序遍历当前节点的左子树——> 然后后序遍历当前节点的右子树——> 最后后序遍历当前节点的根节点;同样把每个节点看作是 (根,左,右)的[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
形式理解,递归
上述二叉树的后序遍历结果: NULL NULL D NULL NULL E B NULL NULL C A
// 后序遍历
void PostOrder(BTNode* root)
{
if (NULL == root) // 递归结束条件
{
printf("NULL ");
return;
}
else
{
PostOrder(root->left); // 递归左子树
PostOrder(root->right); // 递归右子树
printf("%c ", root->data); // 打印当前的节点存放的数据
}
}
运行结果
后序遍历递归的展开图
层序遍历 :除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的 根节点所在层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然 后从左到右访问第2层上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问 树的结点的过程就是层序遍历。就是从根节点开始一层一层的从左向右的的访问下去;
这里我们使用队列的方式,进行层序遍历:把根节点入队列,同时定义出队该根节点,拷贝复制一下,循环把该节点的左子树和右子树的也就是下一层节点的数据入队列,
所以需要创建一个队列用于存放
核心思想就是 一层节点的出队带入下一层节点入队列
// 层序遍历
/* 核心思想就是,节点不为空入队,再出队,
* 一层中带下一层的数据节点
*/
void LeveIOrder(BTNode* root)
{
Queue q; // 定义队列
QueueInit(&q); // 初始化队列
if (root)
{
QueuePush(&q, root); // 根节点入队
}
while (!QueueEmpty(&q)) // 当队列为空时,表示二叉树,层序遍历完了
{
BTNode* front = QueueFront(&q); // 取出队列中存放的 根节点
QueuePop(&q); // 出队 ,是一起的,不然无法取到后面的数据
printf("%c ", front->data);
if (front->left)
{
QueuePush(&q, front->left); // 当前节点的左子树不为空,入队列
}
else
{
printf("NULL ");
}
if (front->right)
{
QueuePush(&q, front->right); // 当前节点的,右子树不为空,入队列
}
else
{
printf("NULL ");
}
}
printf("\n");
QueueDestory(&q); // 清空队列
}
注意:我们在清空二叉树中的节点的时候,不要 先把根节点中的数据给清除了,如果删除了,就无法访问后面的数据了。所以我们,先后序遍历一样,把根放到最后,释放空间,这里我们同样使用递归的方式把每个节点看成是(根,左子树,右子树),释放空间
// 清空二叉树中的节点
/*
* 注意:在二叉树中的不要把根节点,删除了,不然,无法找到左右子树
* 所以我们需要从最后面,删除,后序遍历类似
*/
void DestroryTree(BTNode* root)
{
if (NULL == root)
{
return;
}
DestroryTree(root->left); // 清空当前节点的左子树
DestroryTree(root->right); // 清空当前节点的右子树
free(root); // 释放当前节点的空间
}
计算二叉树中所有的节点的个数,我们这里有三种方式:
第一种方式,定义全局变量 递归计算:
定义全局变量的原因是:
如果没有定义全局变量的话,像下面这种 int size = 0 没有被定义全局变量,而是定义成了局部变量,出现的问题就是在,递归的过程中,对被不断初始化为 0 ,导致无法计数到
// 计算二叉树中所有节点的个数
// 方式一: 定义全局变量,递归计数
int size = 0;
void TreeSizeOne(BTNode* root)
{
int size = 0; // 注意这里,每次递归的时候都会被,初始化为 0 ,是没有办法计数到的
if (NULL == root) // 节点为空不计数
{
return;
}
else
{
++size; //递归计数
}
TreeSizeOne(root->left); // 左子树中的所有节点个数
TreeSizeOne(root->right); // 右子树中的所有节点个数
}
运行结果
修改一下我们定义一个全局变量,进行计数,代码如下:
int size = 0;
void TreeSizeOne(BTNode* root)
{
if (NULL == root) // 节点为空不计数
{
return;
}
else
{
++size; //递归计数
}
TreeSizeOne(root->left); // 左子树中的所有节点个数
TreeSizeOne(root->right); // 右子树中的所有节点个数
}
运行结果:
运行结果上看是没有问题的,但是,存在一个问题就是,因为我们定义的是全局变量,当我们计算另外一个二叉树的节点时,会把我们前面的计算的结果累加到一起,其结果就不正确了。
第二种方式: 传参数,递归计算
注意 :需要传参数的地址,因为形参的改变并不会影响到实参
具体代码实现如下:
// 计算二叉树中所有节点的个数的
// 方式2:传参数的地址,改变实参 递归计数
void TreeSizeTwo(BTNode* root,int* size)
{
if (NULL == root)
{
return;
}
else
{
++(*size); // 解引用改变实参
}
TreeSizeTwo(root->left,size); // 递归,计算左子树的节点个数
TreeSizeTwo(root->right,size); // 递归,计算右子树的节点个数
}
第三种方式:运用递归返回计数
把每个节点看作是(根,左子树,右子树),进行递归计算其左右子树的节点,计算好后一一返回,计算总合
int TreeSize(BTNode* root)
{
if (NULL == root) // 空节点不用计数,返回 0
{
return 0;
}
else
{
return TreeSize(root->left) + TreeSize(root->right) + 1; // +1 指的是当前不为空的,本身节点
// 递归计算左子树中的节点 右子树的节点
}
}
计算二叉树中所有节点的递归展开图
同样计算二叉树中所有叶子节点的个数,我们也可以使用递归分治算法 ,分而治之,将大问题,分成类似的小问题,使用同样的方法,解决。这里我们把每个节点的看作是(根,左子树,右子树) [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
进行递归分治之,把每个节点中的左子树和右子树计算出来后,一一递归回溯返回,最后汇总计算总和
// 计算二叉树中所有叶子节点的个数
int TreeLeafSize(BTNode* root)
{
if (root == NULL) // 节点为空,返回 0
{
return 0;
}
if (NULL == root->left && NULL == root->right) // 左右子树都为 NULL, 为叶子节点,返回 1 计数
{
return 1;
}
return TreeLeafSize(root->left) + TreeLeafSize(root->right);
// 计算 左子树 + 计算 右子树 的叶子节点
}
运行结果
计算二叉树中所有叶子节点的递归展开图
这里使用分布式文件设计模式实现的
该文件存放,头文件的引入,二叉树的结构体定义,有关二叉树函数的声明,宏定义
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include
#include
typedef char BTDateType; // 定义存放二叉树中的数据类型
typedef struct BinaryTreeNode { // 定义二叉树结构体
struct BinaryTreeNode* left; // 左子树的
struct BinaryTreeNode* right; // 右子树
BTDateType data; // 存放的数据
}BTNode;
// extern int size1 ; 存在累计问题
extern void test(); // 用于测试二叉树
extern void BinaryTreeInit(BTNode* root); // 初始化二叉树
extern BTNode* BinaryTreeGenerate(BTDateType x); // 产生树节点
extern void PrerOrder(BTNode* root); // 前序遍历
extern void InOrder(BTNode* root); // 中序遍历
extern void PostOrder(BTNode* root); // 后序遍历
// extern void TreeSize1(BTNode* root); // 计算二叉树的节点个数,方式一:全局变量
// extern void TreeSize2(BTNode* root, int* size); // 计算二叉树的节点的个数,方式二:传参数,注意时传的是地址
// 因为形参的改变不会影响到实参的,传地址
extern int TreeSize(BTNode* root); // 计算二叉树中第三中方式,递归计算
extern int TreeLeafSize(BTNode* root); // 计算二叉树中叶子节点的个数
extern void DestroryTree(BTNode* root); // 清空二叉树中的节点
该文件存放,有关二叉树功能的函数的实现
#include"BinaryTree.h"
// 初始化二叉树的
void BinaryTreeInit(BTNode* root)
{
root->left = NULL;
root->right = NULL;
}
// 生成二叉树节点
BTNode* BinaryTreeGenerate(BTDateType x)
{
// 内存堆区开辟空间
BTNode* new = (BTNode*)malloc(sizeof(BTNode));
// 判断该空间是否创建成功
if (NULL == new)
{
perror("malloc error"); // 打印提示错误
exit(-1);
}
new->left = NULL; // 左子树节点
new->right = NULL; // 右子树节点
new->data = x; // 存放的数值
return new;
}
// 前序遍历
// 将每个节点看作是 根左右,来递归
void PrerOrder(BTNode* root)
{
if (NULL == root) // 递归结束条件
{
printf("NULL ");
return; // 返回到调用它的位置
}
printf("%c ", root->data); // 取根
PrerOrder(root->left); // 左子树递归,
PrerOrder(root->right); // 右子树递归
}
// 中序遍历
// 把每个节点看作是 根左右,进行递归
void InOrder(BTNode* root)
{
if (NULL == root) // 递归结束条件
{
printf("NULL ");
return; // 递归,返回到调用该函数的位置
}
InOrder(root->left); // 左子树,递归
printf("%c ", root->data); // 取根
InOrder(root->right); // 右子树,递归
}
// 后序遍历
// 将每个节点看作是 根左右
void PostOrder(BTNode* root)
{
if (NULL == root) // 递归结束条件
{
printf("NULL ");
return; // 递归,返回调用它的位置处
}
else
{
PostOrder(root->left); // 左子树递归
PostOrder(root->right); // 右子树,递归
printf("%c ", root->data); // 取 根
}
}
// 计算二叉树中所有节点的个数,方式三
/* 递归,计算,把每个节点看作是 根左右,计算每个节点的数量*/
int TreeSize(BTNode* root)
{
if (NULL == root) // 递归结束条件
{
return 0; // 当左右树节点为 NULL,返回 0,返回调用它的位置
}
else
{
return TreeSize(root->left) + TreeSize(root->right) + 1;
// 把其节点的左右子树的节点个数加起来
}
}
// 计算二叉树中叶子节点的个数 注意把每个节点看成是 根左右的树
int TreeLeafSize(BTNode* root)
{
if (NULL == root) // 递归结束条件
{
return 0; // 空节点 0,返回调用它的位置
}
if (root->left == NULL && root->right == NULL)
{
return 1; // 左右子树都为空,返回 1, 返回到调用它的位置
}
return TreeLeafSize(root->left) + TreeLeafSize(root->right);
// 子树的叶子节点 + 右子树的叶子节点
}
// 清空二叉树中的节点
/*
* 注意:在二叉树中的不要把根节点,删除了,不然,无法找到左右子树
* 所以我们需要从最后面,删除,后序遍历类似
*/
void DestroryTree(BTNode* root)
{
if (NULL == root)
{
return;
}
DestroryTree(root->left); // 清空当前节点的左子树
DestroryTree(root->right); // 清空当前节点的右子树
free(root); // 释放当前节点的空间
}
// 计算二叉树的节点的个数 ,把每个节点看作是 根左右
/* 方式二,传参数的地址的方式,改变形参影响实参
void TreeSize2(BTNode* root, int* size)
{
if (NULL == root) // 递归结束条件
{
return; // 返回,调用你的位置处;
}
else
{
(*size)++; // 解引用,形参改变实参
}
TreeSize2(root->left, size); // 递归,左子树
TreeSize2(root->right, size); // 递归,右子树
}
*/
/*
void TreeSize2(BTNode* root)
{
int size = 0; // 该计算的数值,会在递归中,被不断置为 0,导致无法计数
if (NULL == root) // 所以使用传参数的地址的方式
{
return;
}
else
{
size++;
}
TreeSize2(root->left);
TreeSize2(root->right);
}
*/
// 计算二叉树的节点个数
/*方式一,定义全局变量,累计计算
* 存在累计问题
void TreeSize1(BTNode* root)
{
if (NULL == root) // 递归结束条件
{
return ;
}
else
{
size1++; // 节点不为空加加
}
TreeSize1(root->left); // 递归,计算其左子树
TreeSize1(root->right); // 递归,计算其右子树
return ;
}
*/
该文件存放 main函数 ,有关二叉树中的测试
#include"BinaryTree.h"
// int size1 = 0; // 计算二叉树节点的方式一,全局变量,存在累计问题
int main()
{
test();
return 0;
}
void test()
{
BTNode* A = NULL;
BTNode* B = NULL;
BTNode* C = NULL;
BTNode* D = NULL;
BTNode* E = NULL;
A = BinaryTreeGenerate('A'); // 创建生成二叉树节点
B = BinaryTreeGenerate('B');
C = BinaryTreeGenerate('C');
D = BinaryTreeGenerate('D');
E = BinaryTreeGenerate('E');
A->left = B; // A的左子树
A->right = C; // A的右子树
B->left = D; // B的左子树
B->right = E; // B的右子树
printf("前序遍历: ");
PrerOrder(A); // 前序遍历
printf("\n");
printf("中序遍历: ");
InOrder(A); // 中序遍历
printf("\n");
printf("后序遍历: ");
PostOrder(A); // 后序遍历
/*
* 使用方式一:定义全局变量,计算二叉树节点存在累积问题
TreeSize1(A);
printf("\n");
printf("%d\n", size1); // 5
TreeSize1(B);
printf("%d\n", size1); // 8 这里size 累计到了上次的size的数值了
*/
/*使用方式二:传实参的地址,形参的改变影响实参
int size = 0;
printf("\n");
TreeSize2(A, &size);
printf("%d\n", size);
int num = 0;
TreeSize2(B, &num);
printf("%d\n", num);
*/
printf("\n以A为根节点的二叉树中的所有节点的个数:%d",TreeSize(A)); // 计算二叉树中所有节点的个数
printf("\n以B为根节点的二叉树中的所有节点的个数:%d\n",TreeSize(B)); // 计算二叉树中所有节点的个数
printf("以A为根节点的二叉树中的所有叶子节点个数:%d", TreeLeafSize(A)); // 计算二叉树中所有叶子节点的个数
printf("\n");
printf("以B为根节点的二叉树中的所有叶子节点个数:%d", TreeLeafSize(B)); // 计算二叉树中所有叶子节点的个数
printf("\n");
printf("以C为根节点的二叉树中的所有叶子节点个数:%d", TreeLeafSize(C)); // 计算二叉树中所有叶子节点的个数
DestroryTree(A); // 清空二叉树中的节点
}
运行结果
限于自身水平,其中存在的错误,希望大家给予指教,韩信点兵——多多益善,谢谢大家,后会有期,江湖再见