前言
随着栈和队列的学习完成,接下来就到了二叉树环节。二叉树(Binary tree)是树形结构的一个重要类型。许多实际问题抽象出来的数据结构往往是二叉树形式,即使是一般的树也能简单地转换为二叉树,而且二叉树的存储结构及其算法都较为简单,因此二叉树显得特别重要。二叉树特点是每个节点最多只能有两棵子树,且有左右之分。
到在此之前,先介绍一下,什么是树的结构。
一. 树
1. 树的定义
在数据结构中,有一种被称为树的结构。和链式结构相似,需要用一个节点中的指针去查找下一块位置。与链表不同的是,指向其他节点的指针会大于一。也就是如图1-1所示:
图1-1 树
根据上图我们能够很清楚的了解数的定义:每个节点都会存储数据和指针,树有一个特点,就是虽然指针很多,但是能找到一个固定节点的指针只有一个。
为了方便我们更加清楚地描述树,接下来将会讲解相关概念。
(1)节点的度:一个节点含有的子树个数。(例如节点1的度为6,节点2的度为2)
(2)叶子节点或终端节点:度为0的节点被称为叶节点。(例如图1-1中6、7等)
(3)非叶子节点或非终端节点:度不为0的节点。(例如1、2等)
(4)双亲节点或父节点:有子节点的节点,非叶子节点都是某个节点的父节点。(例如1、2等)
(5)孩子节点或子节点:一个节点有父节点则为该父节点的子节点。(如图除了1节点之外都能够是子节点)
(6)兄弟节点:具有相同父节点的节点。(例如2、3、4、5都是兄弟节点)
(7)树的度:整个树中最大的度。(指1节点的度)
(8)节点的层次:开始为1层,向下增加。(例如1节点的层数为1,10节点的层数是3)
(9)树的高度或深度:最大节点的层次,也就是最大层。(这里一共有3层,所以树的高度为3)
(10)堂兄弟节点:父节点在同一层的节点,但是父节点不相同。(例如7和10是堂兄弟节点)
(11)祖先节点:顾名思义,特定节点向上的所有节点都是祖先节点。(例如10的祖先节点有1和5)
(12)子孙节点:顾名思义,向下的所有子孙。(例如1节点,剩下的节点都是子孙节点)
这之中需要重点掌握的是(2)(4)(5)。
2. 树的结构
有图1-1可知,树的子节点都是不固定的,那么我们没办法直接构建相同结构体来表示数。因此有人想出了另一种方法来接收描述树。用一个子节点来找到他的兄第节点,如图1-2所示:
图1-2 树2
红色部分为实际数据存储方式,黑色为树原本结构。如此解决了结构体多定义的问题,形成了一种只有子节点指针和右兄弟指针的方式。代码如下:
struct TreeNode
{
int val; //存储的数据
struct TreeNode* leftchild; // 左孩子指针
struct TreeNode* rightbrother; // 右兄弟指针
};
二. 二叉树
1. 二叉树定义
二叉树(binary tree)是指树中节点的度不大于2的有序树,它是一种最简单且最重要的树。二叉树的递归定义为:二叉树是一棵空树,或者是一棵由一个根节点和两棵互不相交的,分别称作根的左子树和右子树组成的非空树;左子树和右子树又同样都是二叉树。
图2-1 二叉树
如图2-1所示,二叉树的子节点数最大为2.
特殊的二叉树:
(1)满二叉树:每一层都是满的。(如图2-2所示)
(2)完全二叉树:除了最后一层之外都是满的,并且最后一层的元素是连续的。(如图2-3,满二叉树是特殊的完全二叉树)
图2-2 满二叉树 图2-3 完全二叉树
2. 二叉树的结构
二叉树的结构有两种,一种是物理结构使用顺序表存储数据,另一种是按照逻辑结构一样用左右指针储存数据。对应的分别为图2-4中的物理结构和逻辑结构。
图2-4 树的储存结构
三. 二叉树的实现
1. 顺序表形式的二叉树
和顺序表一样,我们需要增删查改等功能,只是和顺序表不同我呢吧需要排序。因为需要排序,所以我们需要准确定义父子节点的位置关系。以图2-4为例,F的左孩子为C,右孩子为E。F在顺序表中的位置是0,C和E的分别为1和2。他们的实际关系相当于:
左孩子 = 父节点 * 2 + 1。 右孩子 = 父节点 * 2 + 2
这个公式对于二叉树中任意节点都生效。
如果需要建立小堆(及父节点的数小于子节点),那么数据尾插之后需要向上遍历节点
图3-1 二叉树插入1
如图3-1的二叉树中,需要插入节点的值为8,那么首先我们需要把8尾插进入顺序表中,如图3-2所示。
图3-2 二叉树插入2
然后我们向上遍历节点,比如这里的8需要和16比较,小于16就需要交换他们的位置,交换完成之后和9比较,小于9,继续交换。直到遇到6,大于6就不在交换,停止遍历。
8刚插入的时候位置为 7,父节点的位置根据公式计算 (7 - 1)/ 2。为 3。之后(3 - 1)/ 2 = 1。就能正确找到16和9的位置。剩下的就是构建函数交换数据即可。结果为图3-3所示。
图3-3 二叉树插入3
介绍完插入之后就是删除,读取头节点了,从图3-3中不难看出删除头节点6之后,剩下的数据不好删除,那么我们就需要修改一下删除的方法。首先将首元素和末尾元素交换,然后删除最后的元素,最后将首元素位置的数据重新排序。重新排序的方法(小堆),找子节点中较小的那位,如果,数据比这个值大,就交换数据并继续向下遍历节点,反之就结束遍历。具体方法如图3-4 所示:
图3-4 二叉树删除
删除中找到较小节点的方法使用贪心的方式,假设左孩子更小,然后与右孩子比较,大于右孩子就将更小的孩子改为右孩子,如果没有右孩子,就与左孩子比较。都没有就结束遍历,删除完成。
2. 顺序表二叉树代码
代码部分的讲解可以从1. 顺序表形式的二叉树 中找到,或者与顺序表内容相似,故不在讲解。
2.1. 基础代码
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>
#define HPINIT 20
typedef int HPDataType;
typedef struct Heap
{
HPDataType* _a; // 指向存储数据的空间
int _size; // 数据个数
int _capacity; // 容量
}Heap;
// 堆的初始化
void HeapInit(Heap* php);
// 堆的销毁
void HeapDestory(Heap* php);
// 堆的插入
void HeapPush(Heap* php, HPDataType x);
// 堆的删除
void HeapPop(Heap* php);
// 取堆顶的数据
HPDataType HeapTop(Heap* php);
// 堆的数据个数
int HeapSize(Heap* php);
// 堆的判空
bool HeapEmpty(Heap* php);
// 父大于子判真(小堆)
bool PCCompare_s(HPDataType parent, HPDataType child);
// 交换数据
void Swap(HPDataType* p1, HPDataType* p2);
// 向上调整
void AdjustUp(HPDataType* _a, int child);
// 向下调整
void AdjustDown(HPDataType* _a, int size, int parent);
2.2. 堆的初始化
// 堆的初始化
void HeapInit(Heap* php)
{
assert(php);
// 申请初始空间 HPINIT == 4
php->_a = (HPDataType*)malloc(sizeof(HPDataType) * HPINIT);
if(php->_a == NULL)
{
perror("HeapInit malloc fail");
exit(1);
}
// 容量和个数初始化
php->_size = 0;
php->_capacity = HPINIT;
}
// 堆的销毁
void HeapDestory(Heap* php)
{
assert(php);
// 释放内存
free(php->_a);
// 内容全部置为0或者NULL
php->_a = NULL;
php->_size = php->_capacity = 0;
}
2.3. 父大于子判真(小堆)
// 父大于子判真(小堆)
bool PCCompare_s(HPDataType parent, HPDataType child)
{
return parent > child;
}
2.4. 交换数据
// 交换数据
void Swap(HPDataType* p1, HPDataType* p2)
{
HPDataType tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
2.5. 向上调整
// 向上调整
void AdjustUp(HPDataType* _a, int child)
{
assert(_a);
// 父节点的位置等于(child - 1) / 2;
int parent = (child - 1) / 2;
// 反复向上调整直到子节点到树顶
while(child > 0)
{
if(PCCompare_s(_a[parent], _a[child]))// 满足交换条件就调整父子节点数据位置
{
Swap(&_a[parent], &_a[child]);
child = parent;
parent = (child - 1) / 2;
}
else// 反之就跳出循环(无需调整)
{
break;
}
}
}
2.6. 向下调整
// 向下调整
void AdjustDown(HPDataType* _a, int size, int parent)
{
assert(_a);
// 假设左孩子更小, 公式:child = parent * 2 + 1;
int child = parent * 2 + 1;
// 孩子节点没越界就继续比较
while(child < size)
{
// 调整左右孩子,取更小的
if(child + 1 < size && PCCompare_s(_a[child], _a[child + 1]))
{
++child;
}
// 如果满足条件就交换父子位置并继续向下遍历
if(PCCompare_s(_a[parent], _a[child]))
{
Swap(&_a[parent], &_a[child]);
parent = child;
child = parent * 2 + 1;
}
else// 反之,跳出循环(无需继续调整)
{
break;
}
}
}
2.7. 堆的插入
// 堆的插入
void HeapPush(Heap* php, HPDataType x)
{
assert(php);
// 容量不足扩容
if(php->_capacity == php->_size)
{
// int tmp = php->_capacity * 2;
HPDataType* newnode = (HPDataType*)realloc(php->_a, sizeof(HPDataType) * php->_capacity * 2);
if(newnode == NULL)
{
perror("HeapPush malloc fail");
exit(1);
}
php->_a = newnode;
php->_capacity *= 2;
}
// 插入数据
php->_a[php->_size++] = x;
// 堆尾向上调整
AdjustUp(php->_a, php->_size - 1);
}
2.8. 堆的删除
// 堆的删除
void HeapPop(Heap* php)
{
assert(php);
assert(!HeapEmpty(php));
// 删除堆顶数据
Swap(&php->_a[0], &php->_a[php->_size - 1]);
--php->_size;
// 堆顶数据向下调整
AdjustDown(php->_a, php->_size, 0);
}
2.9. 取堆顶的数据
// 取堆顶的数据
HPDataType HeapTop(Heap* php)
{
assert(php);
assert(!HeapEmpty(php));
return php->_a[0];
}
2.10. 堆的数据个数
// 堆的数据个数
int HeapSize(Heap* php)
{
assert(php);
return php->_size;
}
2.11. 堆的判空
// 堆的判空
bool HeapEmpty(Heap* php)
{
assert(php);
return php->_size == 0;
}
3. 测试二叉树(堆)
和顺序表一样,测试插入,排序和删除。
void TestHeap()
{
Heap hp;
HeapInit(&hp);
HPDataType arr[] = {9,88,666,28,114,254,612,450,153,24};
int size_a = sizeof(arr) / sizeof(arr[0]);
for(int i = 0; i < size_a; ++i)
{
HeapPush(&hp, arr[i]);
}
printf("堆的大小为:%d\n", HeapSize(&hp));
while(!HeapEmpty(&hp))
{
printf("%d ", HeapTop(&hp));
HeapPop(&hp);
}
printf("\n");
HeapDestory(&hp);
}
int main()
{
TestHeap();
//TestHeapSort();
return 0;
}
4. 堆排序
了解了二叉树之后,我们想要直接堆一个数组进行排序呢?
我们可以利用之前的调整函数堆数组进行调整,有两种方式:
(1)从上向下遍历数组,每个节点都执行一次向上遍历。时间复杂度O(N^2)
(2)从有子节点的最后一位开始,从下向上遍历数组,每个节点执行一次向下遍历。时间复杂度O(N)
这里我们选择方法(2),那么得到小堆之后,数组不一定是有序的,这个时候我们仍然需要排序,但是我们只知道,堆中的头节点是最小的。所以就将头结点位置数据和没有交换过的尾数据相交换,并从头节点向下遍历一次堆。最后数组变成了递增的数组,
特点是递增建立大堆,递减建立小堆。
那么代码如下:
// 对数组进行堆排序
void HeapSort(int* a, int n)
{
assert(a);
//向下调整建堆(小堆)
int i = n / 2 - 1;
while(i >= 0)
{
AdjustDown(a, n, i);
--i;
}
// 得到降序数组
while(--n)
{
Swap(&a[0], &a[n]);
AdjustDown(a, n, 0);
}
}
测试代码:
void TestHeapSort()
{
int arr[] = {1,2,3,4,5,6,7,8,9,10};
int sz = sizeof(arr) / sizeof(arr[0]);
HeapSort(arr, sz);
for(int i = 0; i < sz; ++i)
{
printf("%d ", arr[i]);
}
printf("\n");
}
5. 传统二叉树实现
5.1. 二叉树的传统结构
传统二叉树,图2-4中和逻辑结构相同的物理结构。现阶段刚接触到二叉树,真正需要插入数据的话还要在搜索二叉树中才有意义。现阶段的代码实现中会缺少相关插入删除的函数,到C++之后接触到搜索二叉树、红黑树、B树的实收再讲。
因为逻辑结构和物理结构相同,所以元素的访问有所不同,那么我们会需要新的函数,例如:
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
#include <string.h>
typedef char BTDataType;
typedef struct BinaryTreeNode
{
BTDataType _data;
struct BinaryTreeNode* _left;
struct BinaryTreeNode* _right;
}BTNode;
// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
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);
这些函数现阶段我们都可以实现了,而且,有许多共通的地方。在开始学习上面的函数之前,我们首先需要了解一下二叉树的三种基础的遍历结构。
5.2. 二叉树节点的遍历
二叉树有3种基础的遍历方式,分为:前序遍历、中序遍历、后续遍历。
三种遍历的不同之处在于根节点操作的顺序不同,例如:
(1)前序遍历的顺序为:根节点、左子树、右子树。
(2)中序遍历的顺序为:左子树、根节点、右子树。
(3)后序遍历的顺序为:左子树、右子树、根节点。
具体的遍历方式,我们用图片的方式说明。
如图3-5所示,我们拥有一颗树。
图3-5 树
5.2.1. 前序遍历
前序遍历就是先访问根节点然后访问左子树然后是右子树,那么它的访问结果应该是:F C A # # D # # E H # # G # #.。其中‘#’表示空节点,如果去掉空节点前序遍历为: F C A D E H G。辨识度就比较低了,而且拼不出来一个唯一的树。
实现遍历的具体过程如图3-6所示
图3-6 前序遍历
5.2.2. 中序遍历
讲过了前序遍历之后,中序遍历会好理解很多。可以理解为左子树访问完回到根,然后访问右子树。如图3-7所示,中序遍历的结果为:# A # C # D # F # H # E # G #,去掉空节点结果为:A C D F H E G。
图3-7 中序遍历
6.2.3. 后序遍历
根据之前的经验,直接说后序遍历的结论吧。后续遍历: # # A # # D C # # H # # G E F。去掉空节点结果为: A D C H G E F。
图3-8 后序遍历
5.3. 遍历的规律
根据之前三种遍历方式,我们其实可以从结果中找到遍历的线索。比如从结果中看到子树,具体描述如图3-9所示:
图3-9 从遍历顺序中找树
5.4. 层序遍历
层序遍历就是按照每一层每一层总左往右按顺序访问,这个顺序其实和顺序表实现的二叉树顺序相同,假设现有一颗如图3-5所示的树。层序遍历的结果为: F C E A D H G,如果需要在代码中实现要怎么做呢?我们可以找一个队列存节点数据,先推送F进去,F取出来之后推送子节点C、E入队列,输出完一层的数据后重新确认下一层数据个数。
依次向下,直到队列中没有数据。
6. 传统二叉树实现代码
6.1. 创建二叉树
创建二叉树,我们需要先访问根节点,所以采用前序遍历的思路链接节点即可,代码如下:
// 申请节点
BTNode* BuyNode(BTDataType x)
{
BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
if(newnode == NULL)
{
perror("BuyNode malloc fail");
exit(1);
}
newnode->_data = x; // 将数据置入
newnode->_left = newnode->_right = NULL; //初始化指针
}
// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi)
{
if(a[*pi] == '#')
{
++*pi;
return NULL;
}
BTNode* node = BuyNode(a[*pi]);
++*pi;
node->_left = BinaryTreeCreate(a, n, pi);
node->_right = BinaryTreeCreate(a, n, pi);
return node;
}
6.2. 销毁二叉树
销毁二叉树,根节点我们需要留到最后删除,先删除左右子树的数据,代码如下:
// 二叉树销毁
void BinaryTreeDestory(BTNode** root)
{
assert(root);
if(*root == NULL)
{
return;
}
//后序遍历
BinaryTreeDestory(&((*root)->_left));
BinaryTreeDestory(&((*root)->_right));
free(*root); // 删除根节点空间并置空
*root == NULL;
}
6.3. 统计二叉树节点个数
拆分成子问题,将树的节点个数分为,左子树所有节点个数+右子树所有节点个数 + 根节点,代码如下:
// 二叉树节点个数
int BinaryTreeSize(BTNode* root)
{
if(root == NULL) // 节点为空返回0
{
return 0;
}
return BinaryTreeSize(root->_left) + BinaryTreeSize(root->_right) + 1; // 左子树加右子树节点加自己
}
6.4. 二叉树叶子节点的个数
思路和6.3. 相同,找左右子树根节点个数,总和即可。
// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root)
{
if(root == NULL) // 节点为空返回0
{
return 0;
}
if(root->_left == NULL && root->_right == NULL) // 左右都为空则是子节点,返回1
{
return 1;
}
return BinaryTreeLeafSize(root->_left) + BinaryTreeLeafSize(root->_right); //至少有一个不为空,总数为左子树子节点+右子树子节点
}
6.5. 二叉树第k层节点个数
大致思路不变,找一个数字来记录是否到了k层即可。
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{
if(root == NULL) // 节点为空返回0
{
return 0;
}
if(k == 1) // 是第k层节点,返回1
{
return 1;
}
return BinaryTreeLevelKSize(root->_left, k - 1) + BinaryTreeLevelKSize(root->_right, k - 1); // 层数减一,找左右子树中k层节点的总和
}
6.6. 查找二叉树中值为x的节点
通过前序遍历将所有节点都查找一遍,最后返回找到的节点即可。
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
if(root == NULL)// 空节点直接返回空
{
return NULL;
}
if(root->_data == x) // 找到值了,返回该节点
{
return root;
}
BTNode* leftnode = BinaryTreeFind(root->_left, x); // 找左树中是否有k
BTNode* rightnode = BinaryTreeFind(root->_right, x); // 找右树中是否有k
if(leftnode) // 左不为空返回左,反之返回右
{
return leftnode;
}
else
{
return rightnode;
}
}
6.7. 前序遍历
之前有说过什么事前序遍历,具体递归情况请读者自行分析,或者我下次单独出一期讲。中序和后序遍历也一样,直接给代码了。
// 二叉树前序遍历
void BinaryTreePrevOrder(BTNode* root)
{
if(root == NULL)
{
printf("#");
return;
}
printf("%c", root->_data);
BinaryTreePrevOrder(root->_left);
BinaryTreePrevOrder(root->_right);
}
6.8. 中序遍历
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root)
{
if(root == NULL)
{
printf("#");
return;
}
BinaryTreeInOrder(root->_left);
printf("%c", root->_data);
BinaryTreeInOrder(root->_right);
}
6.9. 后序遍历
// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root)
{
if(root == NULL)
{
printf("#");
return;
}
BinaryTreePostOrder(root->_left);
BinaryTreePostOrder(root->_right);
printf("%c", root->_data);
}
6.10. 层序遍历
// 层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{
assert(root);//判空
Queue q;//建立队列
QueueInit(&q);// 初始化队列
QueuePush(&q, root);// 首先推送第一层的根节点
int levelSize = QueueSize(&q); // 确认每一层元素个数
while(!QueueEmpty(&q)) //队列非空就继续
{
while(levelSize--) // 分别打印每一层
{
BTNode* front = QueueFront(&q); // 按照顺序取出每一行的元素
QueuePop(&q);
printf("%c ", front->_data); // 打印
if(front->_left) // 存入下一行存在的节点
{
QueuePush(&q, front->_left);
}
if(front->_right)
{
QueuePush(&q, front->_right);
}
}
levelSize = QueueSize(&q);// 为下一层做准备
}
}
6.11. 判断二叉树是否是完全二叉树
这里直接使用层序遍历的思路,但是空指针也存到队列之中,如果遇到节点为空就结束层序遍历,查看队列中剩余元素的内容是否都为空,如果都为空则该树是完全二叉树,反之就不是。代码如下:
// 判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root)
{
assert(root);
// 使用和队列相同的思路,但是存入空节点,如果空节点之后仍然存在着节点就不是完全二叉树
Queue q;//建立队列
QueueInit(&q);// 初始化队列
QueuePush(&q, root);// 首先推送第一层的根节点
int levelSize = QueueSize(&q); // 确认每一层元素个数
while(!QueueEmpty(&q)) //队列非空就继续
{
while(levelSize--) // 分别打印每一层
{
BTNode* front = QueueFront(&q); // 按照顺序取出每一行的元素
QueuePop(&q);
if(front == NULL) // 如果节点为空就跳出循环
{
goto end;
}
// 空节点也存储
QueuePush(&q, front->_left);
QueuePush(&q, front->_right);
}
levelSize = QueueSize(&q);// 为下一层做准备
}
end:
while(!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q); // 按照顺序取出每一行的元素
QueuePop(&q);
if(front) // 如果存在节点不为空,就不是完全二叉树
{
return false;
}
}
return true; // 剩余节点均为空,说明是完全二叉树
}
7. 测试传统二叉树
根据以下代码,按照顺序,确认每个函数是否有问题。
void TestBTtree()
{
BTNode* bt;
BTDataType ch[] = "ABD##E#H##CF##G##";
int size = strlen(ch);
int i = 0;
bt = BinaryTreeCreate(ch, size, &i);
printf("二叉树节点个数:%d\n", BinaryTreeSize(bt));
printf("二叉树叶子节点个数:%d\n", BinaryTreeLeafSize(bt));
printf("二叉树第%d层节点个数:%d\n", 3, BinaryTreeLevelKSize(bt, 3));
BTNode* search = BinaryTreeFind(bt, 'G');
if(search)
{
printf("找到了,节点为%p->%c\n", search, search->_data);
}
else
{
printf("二叉树中不存在该节点\n");
}
search = BinaryTreeFind(bt, 'N');
if(search)
{
printf("找到了,节点为%p->%c\n", search, search->_data);
}
else
{
printf("二叉树中不存在该节点\n");
}
BinaryTreePrevOrder(bt); // 前序遍历
printf("\n");
BinaryTreeInOrder(bt); // 中序遍历
printf("\n");
BinaryTreePostOrder(bt); // 后续遍历
printf("\n");
BinaryTreeLevelOrder(bt);//层序遍历
printf("\n");
if(BinaryTreeComplete(bt))
{
printf("该树是完全二叉树\n");
}
else
{
printf("该树不是完全二叉树\n");
}
BinaryTreeDestory(&bt);
}
int main()
{
TestBTtree();
return 0;
}
运行结果如下:
树的逻辑样貌如下图所示:
图3-10
如图3-10所示,检测上述代码运行结果后对比无误。
作者结语
二叉树整体内容还挺多的,有不是完全二叉树的,也有只有逻辑上是二叉树的。学习完本节之后其实也只能算是开端,离实际应用还相差很远。突然觉得当程序员是真的太难了,我需要鼓励朋友们。
有的地方没有细讲,有点偷懒,毕竟写博客没什么明面上的奖励,所以写起来没什么干劲,作为人的局限性一下就突出出来了,没有好处就不想干活。
好在写完了,勉强能看,细化是真不想细化了。