🌇个人主页:平凡的小苏
📚学习格言:别人可以拷贝我的模式,但不能拷贝我不断往前的激情
🛸C语言专栏:https://blog.csdn.net/vhhhbb/category_12174730.html
🚀数据结构专栏:https://blog.csdn.net/vhhhbb/category_12211053.html
家人们更新不易,你们的👍点赞👍和⭐关注⭐真的对我真重要,各位路过的友友麻烦多多点赞关注,欢迎你们的私信提问,感谢你们的转发!
关注我,关注我,关注我,你们将会看到更多的优质内容!!
目录
1、树概念及结构
1.1、树的概念
- 有一个特殊的结点,称为根结点,根节点没有前驱结点
- 除根节点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、……、Tm,其中每一个集合Ti(1<= i <= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继
- 因此,树是递归定义的。
下面我们来看一个树的结构图:
注意:树形结构中,子树之间是不能有交集的,否则就不是树形结构,变成了一个图。
1.2 树的相关概念
2、二叉树概念及结构
2.1、概念
从上图可以看出:
2.2、 特殊的二叉树
2.3、二叉树的性质
2.5 、二叉树的存储结构
目前我们是需要讲堆结构的,这里只详细介绍二叉树的顺序存储
3、二叉树的顺序结构及实现
3.1、 二叉树的顺序结构
3.2、堆的概念及结构
堆是具有下列性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆(例如下面第一张图所示);或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆(例如下面第二张图所示)
3.3、堆的实现
3.3.1、堆的结构代码
typedef int HPDataType;
typedef struct Heap
{
HPDataType* a;//数组
int size;//堆结点个数
int capacity;//堆的容量
}Heap;
3.3.2、堆的初始化
// 堆的构建
void HeapCreate(Heap* hp)
{
assert(hp);
hp->a = (HPDataType*)malloc(sizeof(int) * 4);
hp->size = 0;
hp->capacity = 4;
}
3.3.3、堆的插入
//堆的向上调整
void AdjustUp(HPDataType* a, int child)
{
//这里构建的是大根堆
int parent = (child - 1) / 2;
while (child > 0)//如果孩子结点不大于0就跳出循环
{
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
child = parent;//孩子结点走到父节点
parent = (child - 1) / 2;//更新父节点
}
else
{
break;
}
}
}
void HeapPush(Heap* hp, HPDataType x)
{
assert(hp);
if (hp->size == hp->capacity)//判断堆的容量是否满足
{
HPDataType* tmp = (HPDataType*)realloc(hp->a, sizeof(int) * hp->capacity * 2);
if (tmp == NULL)
{
perror("realloc fail:");
exit(-1);
}
hp->a = tmp;
hp->capacity *= 2;
}
hp->a[hp->size] = x;
hp->size++;
AdjustUp(hp->a, hp->size - 1);//插入向上调整算法
}
算法思想:
我们每次向堆里面插入一个树,都需要调用向上调整算法,如果我们不这样操作,那么我们插入的数就不是一个大根堆,就无法实现堆的删除,取前k大的数等等操作
1.首先我们先让插入的数当做孩子结点,拿去和父亲结点比较,如果孩子结点大于父结点,那么我们就需要交换
2.更新孩子结点和父节点
3.孩子结点不大于0就跳出循环
3.3.4、堆的删除
//堆的向下调整
void AdjustDown(HPDataType* a, int n, int parent)
{
int child = 2 * parent + 1;
while (child < n)
{
if (child + 1 < n && a[child + 1] > a[child])
{
child += 1;
}
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = 2 * parent + 1;
}
else
{
break;
}
}
}
void HeapPop(Heap* hp)
{
assert(hp);
Swap(&hp->a[0], &hp->a[hp->size - 1]);将堆顶的数和最后一个叶子结点交换
hp->size--;//堆个数减1
AdjustDown(hp->a, hp->size, 0);//调用向下调整算法
}
算法思想:
1.交换堆顶和最后叶子结点
2.堆个数减12,并且调用向下调整算法
3.找到孩子两个孩子结点中最小的结点,将™交换
4.更新父节点和孩子结点
5.如果孩子结点大于结点个数就退出循环
3.3.5、取堆顶数据
HPDataType HeapTop(Heap* hp)
{
assert(hp);
return hp->a[0];
}
3.3.6、堆的个数
int HeapSize(Heap* hp)
{
assert(hp);
return hp->size;
}
3.3.7、堆的判空
bool HeapEmpty(Heap* hp)
{
assert(hp);
return hp->size == 0;
}
3.3.8、堆的销毁
void HeapDestory(Heap* hp)
{
assert(hp);
free(hp->a);
hp->capacity = hp->size = 0;
}
3.4、建堆的时间复杂度
3.4.1、向上建堆的时间复杂度
时间复杂度证明如下图所示:
3.4.2、向下调整建堆的时间复杂度证明
向下调整建堆的时间复杂度是O(N),是向上调整建堆的时间复杂度的优化
计算证明如下图所示:
3.5、堆的应用
3.5.1、堆排序
堆排序就是利用堆进行排序的方法。它的基本思想是,将待排序的序列构造成一个大顶堆。此时,整个序列的最大值就是堆顶的根节点。将它移走(其实就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余的n-1个序列重新构造一个堆,这就会得到n个元素的次大值。如此反复执行,便能得到一个有序序列。
注意:我们需要升序,就需要建大堆。降序就需要建小堆。
堆排序的图形演示:
从左至右,从上至下演示
3.5.2、堆排序代码
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
void Swap(int* p1, int* p2)
{
int x = *p1;
*p1 = *p2;
*p2 = x;
}
void PrintArray(int* a, int n)
{
for (int i = 0; i < n; i++)
{
printf("%d ", a[i]);
}
}
void AdjustDown(int* a, int n, int parent)
{
int child = 2 * parent + 1;
while (child <= n)
{
if (child + 1 <= n && a[child + 1] < a[child])
{
child += 1;
}
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = 2 * parent + 1;
}
else
{
break;
}
}
}
void HeapSort(int* a, int n)
{
for (int i = (n - 2) / 2; i >= 0; i--)
{
AdjustDown(a, n - 1, i);
}
int end = n - 1;
while (end > 0)
{
Swap(&a[0], &a[end]);
end--;
AdjustDown(a, end, 0);
}
PrintArray(a, n);
}
int main()
{
int a[10] = { 4,2,7,8,3,1,5,6,9,0 };
HeapSort(a, sizeof(a) / sizeof(a[0]));
return 0;
}
3.5.3、TOP-K问题
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<time.h>
void Swap(int * p1,int * p2)
{
int x = *p1;
*p1 = *p2;
*p2 = x;
}
//堆的向下调整
void AdjustDown(int * a, int n, int parent)
{
int child = 2 * parent + 1;
while (child < n)
{
if (child + 1 < n && a[child + 1] < a[child])
{
child += 1;
}
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = 2 * parent + 1;
}
else
{
break;
}
}
}
void PrintTopK(const char* fin, int k)
{
// 1. 建堆--用a中前k个元素建堆
int* topK = (int*)malloc(sizeof(int) * k);
if (topK == NULL)
{
perror("malloc fail:");
return;
}
FILE* fout = fopen(fin, "r");
if (fout == NULL)
{
perror("FILE fail");
return;
}
for (int i = 0; i < k; i++)
{
fscanf(fout,"%d",&topK[i]);
}
// 2. 将剩余n-k个元素依次与堆顶元素交换,不满则则替换
for (int i = (k - 2) / 2; i >= 0; i--)
{
AdjustDown(topK, k, i);
}
int val = 0;
int ret = fscanf(fout, "%d", &val);
while (ret != EOF)
{
if (val > topK[0])
{
topK[0] = val;
AdjustDown(topK, k, 0);
}
ret = fscanf(fout, "%d", &val);
}
for (int i = 0; i < k; i++)
{
printf("%d ", topK[i]);
}
printf("\n");
free(topK);
fclose(fout);
}
void CreateNDate()
{
// 造数据
int n = 10000;
srand(time(0));
const char* file = "data.txt";
FILE* fin = fopen(file, "w");
if (fin == NULL)
{
perror("fopen error");
return;
}
for (size_t i = 0; i < n; ++i)
{
int x = rand() % 10000;
fprintf(fin, "%d\n", x);
}
fclose(fin);
}
int main()
{
CreateNDate();
PrintTopK("data.txt", 10);
return 0;
}
注:这里得到的就是前10个最大的数
好了!小编的分享到这里就结束了,有什么不足的地方请大佬多多指教!!!