目录
个人专栏:《数据结构世界》
数据结构世界暂时告别了线性大陆,来到了树形的天堂,首先迎来最经典的树——二叉树(Binary Tree)
数据结构世界中本只开辟了线性大陆,在其中不断迭代进化线性的力量,其余区域均为重重迷雾。但是,这一天,迷雾散开一角,露出深不见底的树形深渊,盘根杂枝,树影迷蒙。首先,二叉树入侵世界,它们拥有着与线性截然不同的力量,天生可以一心多用,同时还有一种强大的神通——空间递归。一时间,数据结构世界迎来了树形的恐惧,人人谈“树”色变

注意:树形结构中,子树之间不能有交集,否则就不是树形结构 (有交集,就变成图了,更加复杂的数据结构,后面会讲)

每一个树,都是由双亲节点和N个子树构成的,所以可以递归定义
概念:树+人类亲缘关系描述

这里高度有两种表示方法,,一种是直接看成1,另一种是把第一层看成0,这里推荐前者。
节点的度:一个节点含有的子树的个数称为该节点的度; 如上图:A的为6
叶节点或终端节点:度为0的节点称为叶节点; 如上图:B、C、H、I...等节点为叶节点
非终端节点或分支节点:度不为0的节点; 如上图:D、E、F、G...等节点为分支节点
双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点
孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点
兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:B、C是兄弟节点
树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为6
节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
树的高度或深度:树中节点的最大层次; 如上图:树的高度为4
堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:H、I互为兄弟节点
节点的祖先:从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先
子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙
森林:由m(m>0)棵互不相交的树的集合称为森林;
并查集中会用到森林
学好这些概念,后续才能进一步学习其他更复杂的树。比如:
AVL树
红黑树
B树系列(B树,B+树,B*树)

当我们定义树节点的结构体时,如果知道树的度,可以直接定义指针的个数,但如果不知道呢?

有一种方法,那就是用顺序表来存储孩子节点的地址,也就是指针数组

也可以用顺序表来存储孩子节点的下标,如下图

除此之外,还有人想出来一些奇特的定义方法。比如双亲表示法,树节点中只存储双亲结点的地址(因为每个节点只有一个双亲结点),所以也可以从下往上找。还有一种方法,树节点中既存储双亲结点,也存储孩子节点……
当然,最后我们还是详细了解一种最常用的王者表示法——左孩子右兄弟表示法

相当于一个负责宽度(同层找兄弟),一个负责深度(同分支找孩子)

这样,我们就可以表示整个树的结构

其实我们常用的Windows文件系统,就是一个森林。同级目录下的文件互为兄弟,上下级目录则为父子。还有后面准备学的Linux文件系统,就是一棵目录树。

那么,对树有了基本的概念以后,我们来看一下一种特殊的树——二叉树

注意:对于任意的二叉树都是由以下几种情况复合而成的:



要注意的是满二叉树是一种特殊的完全二叉树。
程序员看到肯定要去膜拜一下,多么标准的(满)二叉树啊!

tips:这里的性质不用强行记忆,随着后面刷题和对二叉树认知加深慢慢就融会贯通了。
1. 若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有 2^(i-1)个结点.
2. 若规定根节点的层数为1,则深度为h的二叉树的最大结点数是2^h - 1
3. 对任何一棵二叉树, 如果度为0其叶结点个数为 m, 度为2的分支结点个数为 n,则有 m=n+1
4. 若规定根节点的层数为1,具有n个结点的满二叉树的深度,h=log(n+1)(以2为底)
5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对于序号为i的结点有:

父子间下标关系:
1.父亲找孩子:leftchild = parent * 2 + 1
rightchild = parent * 2 + 2
2.孩子找父亲:parent = (child - 1) / 2

普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。



堆的性质:
好好理解这句话:堆的逻辑结构是二叉树,物理结构是数组 !


这里注意,不要对整个堆进行空间释放,因为堆这个结构体不是动态开辟的,只能对堆里的数组进行空间释放。




前面几个都非常简单,所以不多做介绍了(详情请看往期--线性时代)。现在来到了关键点——入堆。首先和往常一样,先判断是否需要扩容,再将入堆元素插入数组末尾。

做完这一切,我们才真正来到入堆的精华。将数组想象成一棵二叉树,那么刚刚插入的元素就在树的最底层。因为要保持任意一个元素都大于父节点(假设这里是小堆),所以我们就要根据父子间的下标关系进行调整,这种算法称为向上调整算法。
具体做法:

AdjustUp(php->a, php->size-1);
所以push最后加上向上调整,将数组和尾部元素下标传过去,调整完保持还是一个堆。
入堆讲完了,我们再来将另一个重点——出堆。大家觉得出堆应该从哪出,队尾?不,那没有任何意义。出堆应该从堆顶出,也就是二叉树的根节点。
但是,如果直接向前挪动覆盖,那父子关系就全乱了,堆也就不存在了,需要重新建堆,代价太大了。所以,如果我们要保持原有的堆的结构,那要怎么删除呢?
有一种做法可以达到目的,那就是先把首尾元素交换,再size--。这时就保持除了根节点,其余两棵子树都是堆,这时我们只要利用父子间下标关系进行调整即可,这种算法称为向下调整算法

具体做法:

堆的测试及运行结果

这样,我们先将数组元素入堆,再不断获取堆顶元素,再删除,就可以实现升序打印(小堆实现)
源代码
heap.h
- #pragma once
- #include
- #include
- #include
- #include
- #include
-
- typedef int HPDataType;
- typedef struct Heap
- {
- HPDataType* a;
- int size;
- int capacity;
- }HP;
-
- //初始化
- void HPInit(HP* php);
- //销毁
- void HPDestroy(HP* php);
- //入堆
- void HPPush(HP* php, HPDataType x);
- //删除堆顶元素
- void HPPop(HP* php);
- //判断堆是否为空
- bool HPEmpty(HP* php);
- //获取堆顶元素
- HPDataType HPTop(HP* php);
- //获取堆的元素个数
- int HPSize(HP* php);
-
- void Swap(HPDataType* p1, HPDataType* p2);
- void AdjustUp(HPDataType* a, int child);
- void AdjustDown(HPDataType* a, int n, int parent);
heap.c
- #define _CRT_SECURE_NO_WARNINGS 1
- #include"heap.h"
-
- void HPInit(HP* php)
- {
- assert(php);
-
- php->a = NULL;
- php->capacity = php->size = 0;
- }
-
- void HPDestroy(HP* php)
- {
- assert(php);
-
- free(php->a);
- php->a = NULL;
- php->capacity = php->size = 0;
- }
-
- void Swap(HPDataType* p1, HPDataType* p2)
- {
- HPDataType tmp = *p1;
- *p1 = *p2;
- *p2 = tmp;
- }
-
- void AdjustUp(HPDataType* a, int child)
- {
- int parent = (child - 1) / 2;
- while (child > 0)
- {
- if (a[child] < a[parent])//小堆
- {
- Swap(&a[child], &a[parent]);
-
- child = parent;
- parent = (child - 1) / 2;
- }
- else
- {
- break;
- }
- }
- }
-
- void HPPush(HP* php, HPDataType x)
- {
- assert(php);
- if (php->capacity == php->size)
- {
- int newCapacity = php->capacity == 0 ? 4 : 2 * php->capacity;
- HPDataType* tmp = (HPDataType*)realloc(php->a, newCapacity * sizeof(HPDataType));
- if (tmp == NULL)
- {
- perror("realloc fail");
- return;
- }
- php->a = tmp;
- php->capacity = newCapacity;
- }
- php->a[php->size++] = x;
-
- AdjustUp(php->a, php->size-1);
- }
-
- void AdjustDown(HPDataType* a, int n, int parent)
- {
- int child = parent * 2 + 1;
- while (child < n)
- {
- if (child + 1 < n && a[child + 1] < a[child])
- {
- child++;
- }
-
- if (a[child] < a[parent])
- {
- Swap(&a[child], &a[parent]);
-
- parent = child;
- child = parent * 2 + 1;
- }
- else
- {
- break;
- }
- }
- }
-
- void HPPop(HP* php)
- {
- assert(php);
- assert(!HPEmpty(php));
-
- Swap(&php->a[0], &php->a[php->size - 1]);
- php->size--;
-
- AdjustDown(php->a, php->size, 0);
- }
-
- bool HPEmpty(HP* php)
- {
- assert(php);
- return php->size == 0;
- }
-
- HPDataType HPTop(HP* php)
- {
- assert(php);
- return php->a[0];
- }
-
- int HPSize(HP* php)
- {
- assert(php);
- return php->size;
- }
test.c
- #define _CRT_SECURE_NO_WARNINGS 1
- #include"heap.h"
-
- void TestHeap1()
- {
- HP hp;
- HPInit(&hp);
- int arr[] = { 4,3,2,50,65,44,12,78,95,35 };
- int sz = sizeof(arr) / sizeof(arr[0]);
- int i = 0;
-
- for (i = 0; i < sz; i++)
- {
- HPPush(&hp, arr[i]);
- }
-
- while (!HPEmpty(&hp))
- {
- printf("%d\n", HPTop(&hp));
- HPPop(&hp);
- }
-
- HPDestroy(&hp);
- }
-
- int main()
- {
- TestHeap2();
- return 0;
- }
沿用刚刚的思路,那堆排序是不是就是这样呢?请看下面代码:

先将待排序数组元素全部入堆,再不断取堆顶元素放入数组中。好像很有道理,对吧?
但是这样排序有很多缺点:
所以,有没有一种更简单的方法实现堆排序呢? 有!
具体做法:

堆排序的大体思路是这样的,但是,其实堆的创建除了向上调整算法,也可以用向下调整算法,而且速度更快,可以达到优化的效果。
但是,这里用向下调整算法,和堆删除时不同,因为用向下调整算法的前提,是左右子树都为堆。这里一般肯定都不符合,因为要排序的数组肯定是杂乱无章的,所以怎么办呢?
实现思路:

时间复杂度分析与对比
简单的运用错位相减法化简即可



所以我们就通过精确的计算来得到向下调整算法比向上调整算法更快,时间复杂度更优——O(N)。因此,以后我们在写堆排序时,就只用写向下调整算法!(三个愿望一次满足)
其实我们还可以发现,创建好堆后,第二部分排序用向下调整算法时,时间复杂度与创建堆时向上调整算法相同,为O(N*logN)
所以,堆排序整体时间复杂度为O(N+N*logN),忽略小量来说就是O(N*logN)
这样,我们就实现了真正简单实用的堆排序。运行结果如下:

值得注意的是
代码如下:
- void HeapSort(int* a, int n)
- {
- //降序--建小堆
- //for (int i = 1; i < n; i++)
- //{
- // AdjustUp(a, i);//向上调整--建小堆
- //}
- for (int i = (n - 1 - 1) / 2; i >= 0; i--)
- {
- AdjustDown(a, n, i);//向下调整--建小堆
- }
-
- int end = n - 1;//尾部元素下标
- while (end > 0)
- {
- Swap(&a[0], &a[end]);//首尾交换,选出最小的
- AdjustDown(a, end, 0);//隔绝尾部元素,向下调整,选出次小的
- end--;
- }
- }
-
- void HeapSort(int* arr, int sz)
- {
- HP hp;
- HPInit(&hp);
-
- int i = 0;
- for (i = 0; i < sz; i++)
- {
- HPPush(&hp, arr[i]);
- }
-
- i = 0;
- while (!HPEmpty(&hp))
- {
- arr[i++] = HPTop(&hp);
- HPPop(&hp);
- }
-
- HPDestroy(&hp);
- }
-
- void TestHeap2()
- {
- int arr[] = { 4,3,2,50,65,44,12,78,95,35 };
- int sz = sizeof(arr) / sizeof(arr[0]);
- HeapSort(arr, sz);
- }
那让我们来实战一下,先造出N个数据

创造好数据后,再来实现topk的筛选



那这些数都是随机的,怎么检验自己的程序对不对呢?很简单,只要手动更改,让数值大于100w即可。

运行结果

代码如下:
- void CreateNData()
- {
- int n = 10000;
- srand((unsigned int)time(NULL));
- FILE* fin = fopen("data.txt", "w");
- if (fin == NULL)
- {
- perror("fopen fail");
- return;
- }
-
- int x = 0;
- for (int i = 0; i < n; i++)
- {
- x = rand() % 1000000;
- fprintf(fin, "%d\n", x);
- }
-
- fclose(fin);
- }
-
- void PrintTopk(int k)
- {
- FILE* fout = fopen("data.txt", "r");
- if (fout == NULL)
- {
- perror("fopen fail");
- return;
- }
-
- int* minheap = (int*)malloc(sizeof(int) * k);
- if (minheap == NULL)
- {
- perror("malloc fail");
- return;
- }
-
- for (int i = 0; i < k; i++)
- {
- fscanf(fout, "%d", &minheap[i]);
- }
-
- for (int i = (k-1-1)/2; i >= 0; i--)
- {
- AdjustDown(minheap, k, i);
- }
-
- int val = 0;
- while (fscanf(fout, "%d", &val) != EOF)
- {
- if (val > minheap[0])
- {
- minheap[0] = val;
- AdjustDown(minheap, k, 0);
- }
- }
-
- for (int j = 0; j < k; j++)
- {
- printf("%d\n", minheap[j]);
- }
-
- free(minheap);
- }
-
- int main()
- {
- CreateNData();
- PrintTopk(5);
- return 0;
- }
终于来到链式二叉树的部分,也是真正有难度的精华部分。关于链式二叉树的学习,我们不会像往期的数据结构一样,讲解它的增删查改,因为对于普通的链式二叉树并没有什么意义。只有到了后期的搜索二叉树,以及更高阶的AVL树,红黑树等等,对它们增删查改才有意义。
所以,现在学习的链式二叉树有两个目的:

从现在开始,我们将任何一部分都看作根,左子树,右子树



遍历时我们用递归实现:

运行结果:

同理,中序遍历只需把访问的顺序调换一下即可

这里我们可以手动算一算结果是多少,有助于加深我们对遍历方式的认知。
运行结果:

同理,后序遍历只需把访问的顺序调换一下即可

运行结果:

三条遍历的实现过程非常相似,只有细微的差别,但是背后的遍历方式与函数递归时栈帧的创建却需要我们多去画图思考与理解。
方法一:遍历计数
根据刚刚遍历的思想,计算节点个数,每次递归经过一个节点时,size++
但是,思考一个问题,如果我们要在函数内定义size,那每次递归都会创建新的size变量,不能满足想要的效果。有同学可能会说,加上static修饰,变成局部静态变量,如下图。

看起来好像没问题,再看看运行结果。

我们发现,因为每次计算完节点个数,size没有置为0,导致后续计算错误。而且,因为size是局部静态变量,我们没有办法通过外部去修改,所以这种方法不可行。
那应该怎么办呢?其实很简单,把size设置为全局变量即可。

这样我们就能外部将size置为0,实现目的。

方法二:分治
上述方法是不是显得有些繁琐,每次都要将size置为0,所以我们有第二种更简便的方法——分治。什么是分治呢?简单来说,就是运用管理思维,分而治之。
假设一个场景,校长要统计全校的人数,那是不是就安排院长去统计,而院长又安排辅导员去统计……依此类推,最终全校人数经过一层层统计和汇总,交付到校长手中。而不是校长挨个去统计全校师生的人数。这样的效率是不是就很高了?

同理,想象一下,自下而上,让每个节点统计自身下面的节点个数,这样最后汇总到根节点时,就完成了对节点个数的统计。

这里熟练以后,还可以用三木操作符化简一下。
运行结果:

那么如果我们要求叶节点的个数,按照刚刚分治的思路,只要稍微改动一下即可。

运行结果:

求二叉树的高度,同样运用分治的思维:

注意,要定义变量存储当前递归计算的左右子树高度,千万不要写成以下形式。看似没什么区别,实际上递归的次数呈几何倍增加,时间复杂度高,效率极其低下。
运行结果:

求二叉树第k层节点个数,分析递归问题时,我们都要弄清楚子问题和返回条件

运行结果:

这里查找值为x的节点,要返回的是节点的地址,这样就可以进行修改。
同样,我们先确定一下返回条件

但是这题分解子问题时,有点复杂,可能有人会写成这种形式

这样是不对的,因为后续子问题没有返回值。所以应该怎么做呢?
具体思路如下:

那层序遍历要怎么实现呢?其实,这里用队列实现非常方便。
利用队列先进先出的性质,每一层的每个结点要出队列时,则将左右孩子带进队列,这样就达到层序遍历的效果。
具体做法:
注意,这里队列嵌套了三层,每个节点存储的数据,是树的根节点的地址(指针)
结构体定义:

函数定义:


运行结果:

经过前面的磨练,相信二叉树的销毁对于各位只是小意思了~
比较方便的方式,是采用后序遍历,先销毁左子树,再销毁右子树,最后销毁根节点。
如果采用其余遍历,先销毁了根节点,那还要保存其地址,这样才能继续往下找。
返回条件:如果已经走到空树,那就不用销毁,直接return即可

使用时要注意,实现半自动,在外部手动置空(和free函数的逻辑一样)

经过前面对递归的认识不断加深,我们现在来研究正在创建二叉树的方法。
我们通过一道题目来理解二叉树的创建。
描述
编一个程序,读入用户输入的一串先序遍历字符串,根据此字符串建立一个二叉树(以指针方式存储)。 例如如下的先序遍历字符串: ABC##DE#G##F### 其中“#”表示的是空格,空格字符代表空树。建立起此二叉树以后,再对二叉树进行中序遍历,输出遍历结果。
输入描述:
输入包括1行字符串,长度不超过100。
输出描述:
可能有多组测试数据,对于每组数据, 输出将输入字符串建立二叉树后中序遍历的序列,每个字符后面都有一个空格。 每个输出结果占一行

大体思路:
这里重点讲讲创建二叉树的函数实现
代码如下图,其实大家会发现,好像也没有想象中那么难,就是前序遍历的小小改版。(因为大家对递归的掌握更加深刻了)

完整代码如下:
- #include
- #include
-
- typedef int BTDataType;
- typedef struct BinaryTreeNode
- {
- BTDataType data;
- struct BinaryTreeNode* left;
- struct BinaryTreeNode* right;
- }BTNode;
-
- BTNode* BuyNode(BTDataType x)
- {
- BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
- if (newnode == NULL)
- {
- perror("malloc fail");
- return NULL;
- }
-
- newnode->data = x;
- newnode->left = NULL;
- newnode->right = NULL;
-
- return newnode;
- }
-
- BTNode* BTreeCreat(char* a, int* pi)
- {
- if (a[*pi] == '#')
- {
- (*pi)++;
- return NULL;
- }
-
- BTNode* root = BuyNode(a[(*pi)++]);
- root->left = BTreeCreat(a, pi);
- root->right = BTreeCreat(a, pi);
-
- return root;
- }
-
- void InOrder(BTNode* root)
- {
- if (root == NULL)
- {
- return;
- }
-
- InOrder(root->left);
- printf("%c ",root->data);
- InOrder(root->right);
- }
-
- int main()
- {
- char a[100];
- scanf("%s",a);
- int i = 0;
- BTNode* root = BTreeCreat(a, &i);
- InOrder(root);
- return 0;
- }
判断一棵二叉树是否为完全二叉树,先回忆一下完全二叉树的定义:前n-1层都为满,最后一层可以不满,但从左到右必须连续。
思路:根据这个特性,我们可以采取层序遍历,如果遇到空以后,还有非空,则不为完全二叉树;如果遇到空后,后面全是空,则为连续,为完全二叉树

具体方法:


运行结果:

关于二叉树,其实还没有完全讲完,只是暂时告一段落。剩下的部分,后续会在C++中讲解,因为用C++做会方便不少。
源代码
queue.h
- #pragma once
- #include
- #include
- #include
- #include
-
- typedef int BTDataType;
- typedef struct BinaryTreeNode
- {
- BTDataType data;
- struct BinaryTreeNode* left;
- struct BinaryTreeNode* right;
- }BTNode;
-
- typedef BTNode* QDataType;
- typedef struct QueueNode
- {
- QDataType data;
- 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);
- //检测队列中有效元素个数
- int QueueSize(Queue* pq);
- //检测队列是否为空
- bool QueueEmpty(Queue* pq);
queue.c
- #define _CRT_SECURE_NO_WARNINGS 1
- #include"queue.h"
-
- void QueueInit(Queue* pq)
- {
- assert(pq);
-
- pq->phead = NULL;
- pq->ptail = NULL;
- pq->size = 0;
- }
-
- void QueueDestroy(Queue* pq)
- {
- assert(pq);
-
- QNode* cur = pq->phead;
- while (cur)
- {
- QNode* next = cur->next;
- free(cur);
- cur = next;
- }
-
- pq->phead = pq->ptail = NULL;
- pq->size = 0;
- }
-
- void QueuePush(Queue* pq, QDataType x)
- {
- assert(pq);
-
- QNode* newnode = (QNode*)malloc(sizeof(QNode));
- if (newnode == NULL)
- {
- perror("malloc fail");
- return;
- }
- newnode->data = x;
- newnode->next = NULL;
-
- if (pq->ptail == NULL)
- {
- assert(pq->phead == NULL);
- pq->phead = pq->ptail = newnode;
- }
- else
- {
- pq->ptail->next = newnode;
- pq->ptail = newnode;
- }
-
- pq->size++;
- }
-
- void QueuePop(Queue* pq)
- {
- assert(pq);
- assert(!QueueEmpty(pq));
-
- if (pq->phead->next == NULL)
- {
- free(pq->phead);
- pq->phead = pq->ptail = NULL;
- }
- else
- {
- QNode* next = pq->phead->next;
- free(pq->phead);
- pq->phead = next;
- }
-
- pq->size--;
- }
-
- QDataType QueueFront(Queue* pq)
- {
- assert(pq);
-
- return pq->phead->data;
- }
-
- QDataType QueueBack(Queue* pq)
- {
- assert(pq);
-
- return pq->ptail->data;
- }
-
- int QueueSize(Queue* pq)
- {
- assert(pq);
-
- return pq->size;
- }
-
- bool QueueEmpty(Queue* pq)
- {
- assert(pq);
-
- return pq->size == 0;
- }
test.c
- #define _CRT_SECURE_NO_WARNINGS 1
- #include"queue.h"
-
-
- BTNode* BuyNode(BTDataType x)
- {
- BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
- if (newnode == NULL)
- {
- perror("malloc fail");
- return NULL;
- }
-
- newnode->data = x;
- newnode->left = NULL;
- newnode->right = NULL;
-
- return newnode;
- }
-
- BTNode* CreatBinaryTree()
- {
- BTNode* node1 = BuyNode(1);
- BTNode* node2 = BuyNode(2);
- BTNode* node3 = BuyNode(3);
- BTNode* node4 = BuyNode(4);
- BTNode* node5 = BuyNode(5);
- BTNode* node6 = BuyNode(6);
-
- node1->left = node2;
- node1->right = node4;
- node2->left = node3;
- node4->left = node5;
- node4->right = node6;
- return node1;
- }
-
- void PreOrder(BTNode* root)
- {
- if (root == NULL)
- {
- printf("N ");
- return;
- }
-
- printf("%d ", root->data);
- PreOrder(root->left);
- PreOrder(root->right);
- }
-
- void InOrder(BTNode* root)
- {
- if (root == NULL)
- {
- printf("N ");
- return;
- }
-
- InOrder(root->left);
- printf("%d ", root->data);
- InOrder(root->right);
- }
-
- void PostOrder(BTNode* root)
- {
- if (root == NULL)
- {
- printf("N ");
- return;
- }
-
- PostOrder(root->left);
- PostOrder(root->right);
- printf("%d ", root->data);
- }
-
- int size1 = 0;
-
- void BTreeSize1(BTNode* root)
- {
- if (root == NULL)
- {
- return;
- }
- size1++;
-
- BTreeSize1(root->left);
- BTreeSize1(root->right);
- }
-
- //int BTreeSize(BTNode* root)
- //{
- // static int size = 0;
- // if (root == NULL)
- // {
- // return size;
- // }
- // size++;
- //
- // BTreeSize(root->left);
- // BTreeSize(root->right);
- // return size;
- //}
-
- int BTreeSize(BTNode* root)
- {
- return root == NULL ? 0 : BTreeSize(root->left)
- + BTreeSize(root->right) + 1;
-
- //if (root == NULL)
- //{
- // return 0;
- //}
-
- //return BTreeSize(root->left)
- // + BTreeSize(root->right) + 1;
- }
-
- int BTreeLeafSize(BTNode* root)
- {
- if (root == NULL)
- {
- return 0;
- }
-
- if (root->left == NULL
- && root->right == NULL)
- {
- return 1;
- }
-
- return BTreeLeafSize(root->left)
- + BTreeLeafSize(root->right);
- }
-
- int BTreeHeight(BTNode* root)
- {
- if (root == NULL)
- {
- return 0;
- }
-
- int leftHeight = BTreeHeight(root->left);
- int rightHeight = BTreeHeight(root->right);
-
- return leftHeight > rightHeight ?
- leftHeight + 1 : rightHeight + 1;
- }
-
- int BTreeLevelKSize(BTNode* root, int k)
- {
- assert(k > 0);
- if (root == NULL)
- {
- return 0;
- }
-
- if (k == 1)
- {
- return 1;
- }
-
- return BTreeLevelKSize(root->left, k - 1)
- + BTreeLevelKSize(root->right, k - 1);
- }
-
- BTNode* BTreeFind(BTNode* root, BTDataType x)
- {
- if (root == NULL)
- {
- return NULL;
- }
-
- if (root->data == x)
- {
- return root;
- }
-
- BTNode* leftRoot = BTreeFind(root->left, x);
- if (leftRoot)
- {
- return leftRoot;
- }
-
- BTNode* rightRoot = BTreeFind(root->right, x);
- if (rightRoot)
- {
- return rightRoot;
- }
-
- return NULL;
- }
-
- void LevelOrder(BTNode* root)
- {
- Queue q;
- QueueInit(&q);
- QueuePush(&q, root);
-
- while (!QueueEmpty(&q))
- {
- BTNode* front = QueueFront(&q);
- QueuePop(&q);
- printf("%d ", front->data);
-
- if (front->left)
- {
- QueuePush(&q, front->left);
- }
- if (front->right)
- {
- QueuePush(&q, front->right);
- }
- }
-
- QueueDestroy(&q);
- }
-
- void BTreeDestroy(BTNode* root)
- {
- if (root == NULL)
- {
- return;
- }
-
- BTreeDestroy(root->left);
- BTreeDestroy(root->right);
- free(root);
- }
-
- bool BTreeComplete(BTNode* root)
- {
- Queue q;
- QueueInit(&q);
- QueuePush(&q, root);
-
- while (!QueueEmpty(&q))
- {
- BTNode* front = QueueFront(&q);
- QueuePop(&q);
-
- if (front == NULL)
- {
- break;
- }
-
- QueuePush(&q, front->left);
- QueuePush(&q, front->right);
- }
-
- while (!QueueEmpty(&q))
- {
- BTNode* front = QueueFront(&q);
- QueuePop(&q);
-
- if (front)
- {
- QueueDestroy(&q);
- return false;
- }
- }
-
- QueueDestroy(&q);
- return true;
- }
-
- int main()
- {
- BTNode* root = CreatBinaryTree();
- PreOrder(root);
- InOrder(root);
- PostOrder(root);
-
- printf("%d\n", BTreeSize(root));
- printf("%d\n", BTreeSize(root));
- printf("%d\n", BTreeSize(root));
-
- BTreeSize(root);
- printf("%d\n", size1);
- size1 = 0;
- BTreeSize(root);
- printf("%d\n", size1);
- size1 = 0;
- BTreeSize(root);
- printf("%d\n", size1);
-
- printf("%d\n", BTreeLeafSize(root));
- printf("%d\n", BTreeLeafSize(root));
- printf("%d\n", BTreeLeafSize(root));
-
- printf("%d\n", BTreeHeight(root));
- printf("%d\n", BTreeHeight(root));
- printf("%d\n", BTreeHeight(root));
-
- printf("%d\n", BTreeLevelKSize(root, 1));
- printf("%d\n", BTreeLevelKSize(root, 2));
- printf("%d\n", BTreeLevelKSize(root, 3));
-
- BTNode* pos = BTreeFind(root, 3);
-
- LevelOrder(root);
-
- printf("%d\n", BTreeComplete(root));
-
- BTreeDestroy(root);
- root = NULL;
- return 0;
- }
仅仅了解二叉树的知识是不够的,让我们来刷刷题吧!
看到这里了还不给博主扣个:
⛳️ 点赞☀️收藏 ⭐️ 关注!
💛 💙 💜 ❤️ 💚💓 💗 💕 💞 💘 💖
拜托拜托这个真的很重要!
你们的点赞就是博主更新最大的动力!
有问题可以评论或者私信呢秒回哦。