当前位置: 首页 > news >正文

二叉树-堆(补充)

二叉树-堆

  • 1.二叉树的基本特性
  • 2.堆
    • 2.1.堆的基本概念
    • 2.2.堆的实现
      • 2.2.1.基本结构
      • 2.2.2.堆的初始化
      • 2.2.3.堆的销毁
      • 2.2.4.堆的插入
      • 2.2.5.取出堆顶的数据
      • 2.2.6.堆的删除
      • 2.2.7.堆的判空
      • 2.2.8.堆的数据个数
      • 2.2.9.交换
      • 2.2.10.打印堆数据
      • 2.2.11.堆的创建
      • 2.2.12.堆排序
      • 2.2.13.完整代码
  • 3.Top-K问题

🌟🌟hello,各位读者大大们你们好呀🌟🌟
🚀🚀系列专栏:【数据结构的学习】
📝📝本篇内容:二叉树的基本特性;堆;堆的基本概念;堆的实现;堆的初始化;堆的销毁;堆的插入;取出堆顶的数据;堆的删除;堆的判空;堆的数据个数;交换;打印堆数据;堆的创建;堆排序;完整代码;Top-K问题
⬆⬆⬆⬆上一篇:二叉树(三)
💖💖作者简介:轩情吖,请多多指教(> •̀֊•́ ) ̖́-

1.二叉树的基本特性

在这里插入图片描述
上图展示的就是二叉树,我将它的规律也写在上面了
一般我们把二叉树的高度设置从1开始,从0开始的话,空树就是-1,就不太合适了
一棵N个结点的树有N-1条边
假设二叉树的第k层是满的,它的结点数为2^(k-1)个
我们的二叉树还分为满二叉树完全二叉树,下图展示了二者的对比图
在这里插入图片描述
满二叉树:一个二叉树,如果每一层的结点都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为k,且结点总数是2^k-1,则就是满二叉树。
完全二叉树:前N-1层是满的,最后一层可以不满,但是必须从左往右是连续的,满二叉树是一种特殊的完全二叉树。
我们先来分析一下满二叉树的特性:
假设满二叉树有k层,则它的最后一层的结点有2^(k-1)个
假设满二叉树有k层,一棵满二叉树一共有2^k-1个结点,计算方法如下:

在这里插入图片描述
其实还有一个小技巧:我们的二进制的每一位的值和二叉树的每一层的结点数相等的,假设我们的二进制为11111111,它是一个unsigned char类型的最大值,此时我们计算它的十进制就通过它的再高一位的值-1计算得出,即2^8-1=255。类比到二叉树,即下一层的结点数-1,设最后一层的结点个数为2 ^3,第4层,计算整棵二叉树的结点数为2 ^4-1。

设满二叉树的总结点数为N个,树的高度为log₂(N+1),通过2^k-1=N计算可得
完全二叉树的特性:
设完全二叉树有k层,完全二叉树总共结点最少就是最后一层只有一个,即2^(k-1)个;最多也就是满二叉树,即2 ^k-1个结点

最多不用讲怎么计算了,最少可以用之前讲的错位相减法来计算,也可以二叉树的规律来算:假设完全二叉树一共k层;那么根据前面讲的,除去最后一层一个结点,它就是一棵满二叉树,共k-1层,根据满二叉树的总共结点公式,总结点数为2^ (k-1)-1个;那么再加上去掉的一个结点,完全二叉树的总结点数即为2^(k-1)个,如下图
在这里插入图片描述
对任何一棵二叉树, 如果度为0其叶结点个数为n0, 度为2的分支结点个数为n2,则有n2=n0+1

2.堆

2.1.堆的基本概念

接下来讲的堆是二叉树的一种存储方式,从逻辑结构(想象的结构)上看我们的堆是一棵完全二叉树,从存储结构上看堆是数组
我们的堆还分为大根堆(大堆)和小根堆(小堆)
大堆:父结点大于等于孩子结点,并且子树也同样的,大堆的根结点在整个堆中是最大的元素
大堆:父结点小于等于孩子结点,并且子树也同样的,小堆的根结点在整个堆中是最小的元素
在这里插入图片描述
在这里插入图片描述
之所以我们我们的数组只能表示完全二叉树,是因为不是完全二叉树会有空间浪费,如下图
在这里插入图片描述
并且我们的堆是数组存储还有一个特性:
对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对于序号为i的结点有:
若i>0,i位置节点的双亲序号:(i-1)/2;i=0,i为根节点编号,无双亲节点
若2i+1<n,左孩子序号:2i+1,2i+1>=n否则无左孩子
若2i+2<n,右孩子序号:2i+2,2i+2>=n否则无右孩子
在这里插入图片描述

2.2.堆的实现

2.2.1.基本结构

//Heap.h
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
typedef int T;//堆数据类型typedef struct Heap
{T* arr;//堆的存储位置int size;//堆的数据个数int capacity;//堆的容量
}Heap;void HeapInit(Heap* heap);//堆的初始化
void HeapCreate1(Heap* heap, T* a, int n);//创建堆方法1
void HeapCreate2(Heap* heap, T* a, int n);//创建堆方法2
void HeapDestory(Heap* heap);//将堆销毁
void HeapPush(Heap* heap,T x);//堆的构建
void HeapPrint(Heap* heap);//将堆数据打印出来
T HeapTop(Heap* heap);//取出堆顶数据
void HeapPop(Heap* heap);//删除堆顶数据
int HeapSize(Heap* heap);//堆的数据个数
bool HeapEmpty(Heap* heap);//堆是否为空void swap(T* x, T* y);//交换
void AdjustUp(T* arr, int child);//向上调整
void AdjustDown(T* arr, int size, int parent);//向下调整void HeapSort(int* arr,int n);//堆排序

上述代码已经将存储堆的结构体已经写好,同时把我们的堆的各种函数调用已经声明好了

2.2.2.堆的初始化

void HeapInit(Heap* heap)//堆的初始化
{assert(heap);heap->arr = NULL;heap->capacity = heap->size = 0;
}

我们这边的写法是一开始不给数组任何的空间,后期直接使用realloc开辟

2.2.3.堆的销毁

void HeapDestory(Heap* heap)//将堆销毁
{assert(heap);free(heap->arr);//释放空间heap->size = heap->capacity = 0;
}

不要忘记释放开辟的空间!!!

2.2.4.堆的插入

由于我们数组的特性,头插需要移动元素什么的,效率极低,但是可以尾插,所以说堆一般性就都是在尾部插入

//我们默认建立大堆哈
void AdjustUp(T* arr,int child)//向上调整
{int parent = (child - 1) / 2;//找父结点//使用parent>=0有点不合理,倒数第二次运行循环时child已经是0了,应该结束//但是(child-1)/2导致parent依旧为0,再次进入循环,然后通过else中的breakwhile (child>0){//孩子结点大于父结点if (arr[child] > arr[parent]){//交换swap(&arr[child], &arr[parent]);//继续向上调整child = parent;parent = (child - 1) / 2;}else{//如果孩子结点小于父结点,就不用向上调整了break;}}
}void HeapPush(Heap* heap, T x)//堆的插入
{assert(heap);//空间不足开辟内存if (heap->size == heap->capacity){int newcapacity = heap->capacity == 0 ? 4 : heap->capacity * 2;//realloc的第一个元素是NULL的话,功能和malloc一样T* newarr = (T*)realloc(heap->arr, newcapacity*sizeof(T));if (newarr == NULL){perror("realloc fail");exit(-1);}//修改已经开辟好空间后的信息heap->arr = newarr;//realloc可能不在原位扩容,所以说这一步是必要的heap->capacity = newcapacity;}//正式插入heap->arr[heap->size] = x;heap->size++;//数据个数+1//向上调整,保证是一个堆AdjustUp(heap->arr, heap->size - 1);
}

我们默认建立的是大堆哈,如果想要建立小堆,只需要将向上调整中的比较孩子结点和父结点的>变成<即可
在这里插入图片描述
代码和图片也展示在了上面,可以通过图片来理解一下,之所以这样写是因为我们在没有进行插入前,我们的结构肯定是堆的,但是插入后,我们需要进行调整才能保证堆的结构,我们写的是大堆,
因此需要和父结点进行比较,如果比父结点大,那么继续交换。但是由于交换后,可能还是比祖父结点大,也就还需要不停地调整。向上调整是对插入结点的祖先进行调整

2.2.5.取出堆顶的数据

T HeapTop(Heap* heap)//取出堆顶数据
{	assert(heap);assert(heap->size > 0);return heap->arr[0];
}

我们的可以用于Top-k问题,举个例子,我们在10000个人里面,选出最有钱的人,我们的堆就发挥了大用处,因为它的堆顶的数据,即数组第一个元素是最大(最小)的,我们只需要取出后,再删除就可以选出第二大的…第n大的,因此我们还需要来实现一下删除才能彻底理解

2.2.6.堆的删除

void AdjustDown(T* arr, int size,int parent)
{//选出左孩子和右孩子中较大的那个//假设较大的是左孩子int child = parent * 2 + 1;//孩子结点存在才向下调整while (child<size){if (child + 1 < size && arr[child + 1] > arr[child]){//进行判断,如果右孩子大于左孩子,就+1,因为左孩子和右孩子之间就相差1child++;}//孩子结点大于父节点,就需要交换,保证大堆的特性if (arr[child] > arr[parent]){swap(&arr[child], &arr[parent]);//继续向下调整parent = child;child = parent * 2 + 1;}else{//如果孩子结点小于父结点,说明不需要调整了,跳出循环break;}}}void HeapPop(Heap* heap)//删除堆顶数据
{assert(heap);assert(heap->size>0);//先将堆顶的元素和最后一个元素进行交换swap(&heap->arr[0], &heap->arr[heap->size - 1]);//堆数据个数-1heap->size--;//向下调整AdjustDown(heap->arr,heap->size,0);
}

在这里插入图片描述
上面已经给出了代码和交换的图片,我们首先来讲一下为什么需要通过交换才能删除这个最大(最小)元素,究其原因还是它在根节点的原因,没有办法对它进行一个直接删除,一旦直接删除,就会导致整体的一个堆结构就乱掉了。我们根结点的左子树和右子树是堆,不能破坏它,那么最好的方法就是和尾元素进行交换,然后进行向下调整,这样不但保证了结构的完整性,效率还高。
向下调整如下图:
在这里插入图片描述

2.2.7.堆的判空

bool HeapEmpty(Heap* heap)//堆是否为空
{assert(heap);return heap->size == 0;
}

2.2.8.堆的数据个数

int HeapSize(Heap* heap)//堆的数据个数
{assert(heap);return heap->size;
}

2.2.9.交换

void swap(T* x, T* y)//交换
{T tmp = *x;*x = *y;*y = tmp;
}

2.2.10.打印堆数据

void HeapPrint(Heap* heap)//将堆数据打印出来
{for (int i = 0; i < heap->size; ++i){printf("%d ", heap->arr[i]);}printf("\n");
}

2.2.11.堆的创建

//简单粗暴的一种方法
void HeapCreate1(Heap* heap, T* a, int n)//创建堆1
{assert(heap);HeapInit(heap);for (int i = 0; i < n; i++){HeapPush(heap, a[i]);}
}void AdjustDown(T* arr, int size, int parent);//定义在下面,这边要使用,声明一下void HeapCreate2(Heap* heap, T* a, int n)//创建堆2
{assert(heap);HeapInit(heap);//开辟空间heap->arr = (T*)malloc(sizeof(T) * n);if (heap->arr == NULL){perror("malloc fail");exit(-1);}//拷贝数据memcpy(heap->arr, a, n*sizeof(T));heap->size = heap->capacity = n;//从下至上进行向下调整for (int end = (n - 1 - 1) / 2; end >= 0; end--){AdjustDown(heap->arr, n, end);}
}

在这里插入图片描述

上面展示了代码和图片,堆的创建我们用了两种方法,第一种就比较直接,循环push即可,但它的效率不是很高,于是我们有了第二种方法,想要保证一组毫无序列的元素变成堆,就需要保证每一个子树的父节点都大于(小于)孩子节点,因此我们只能从最后一棵子树开始向下调整(叶子结点不需要调整了),这样当调整到上一层时,下层都已经是堆了,只需要对当前节点也是向下调整即可。一直到根节点完成最后一次向下调整就可以完成堆的构建。
在代码中end两次-1,第一次-1是为了找到最后一个元素在数组中的位置,第二次-1是为了找到父结点。

2.2.12.堆排序

在我们讲述堆排序前,我们先来讨论一下,当我们对于一个随机的数组,将它变成一个堆,是使用向上调整好还是向下调整好呢?我们接下来分析一下
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看到,向下调整和向上调整都能建堆,如果简单来看的话会认为向上调整的时间复杂度是都是O(N*logN),而向下调整就有点难以看出来了,我们来计算一下
在这里插入图片描述
我们的向下调整在前面的代码中已经演示过了,它从最后一个结点的父结点开始调整,并且我们要验证它的时间复杂度就得使用最坏的情况,就是满二叉树。根据上图的详细计算可以得出向下调整的时间复杂度是O(N)
接下来再来看一下,向上调整的时间复杂度计算
在这里插入图片描述
上图为向上调整的计算过程,通过规律,我们很快就算了出来,可以发现向上调整的时间复杂度是O(N *log₂N)。其实我们仔细观察一下,可以发现我们的向上调整次数其实都是聚集在最后一层,最后一层不但结点是整棵二叉树中最多的,调整次数也是最多的;反观向下调整中,结点越是在上面,调整次数越多,但结点越少,反倒是最后一层,并没有进行调整,这就是造成两者时间复杂度差距的原因,因此我们在选择调整时,优先选择向下调整

void HeapSort(int* arr,int n)//堆排序
{//向上调整--O(N*logN)/*for (int i = 0; i < n; i++){AdjustUp(arr, i);}*///向下调整--O(N)for (int i =(n-1-1)/2; i>=0; i--){AdjustDown(arr, n,i);}//堆排序-升序-使用大堆int end = n-1;while (end > 0)//当end==0时说明调整结束{swap(&arr[0],&arr[end]);AdjustDown(arr, end, 0);end--;}}

在这里插入图片描述

接下来就可以看我们的堆排序的代码和图片,其中我们升序时需要使用大堆,降序时使用小堆,我们通过交换+向下调整,将数据依次从数组尾部开始放,这其实就是借鉴了我们的将堆内的top数据取出再pop的思想,并且我们的这个排序只需要写向下调整的代码即可,不需要完整的把堆代码写完。
我们可以看一下如果使用top+pop函数也能达到类似排序效果
在这里插入图片描述
在这里插入图片描述
我们接下来再看一下堆排序的时间复杂度
在这里插入图片描述
经过上面的分析,就可以知道一开始建堆向下调整的时间复杂度是O(N),循环交换+向下调整的时间复杂度是O(N*log₂N),那么根据时间复杂度的计算方式,堆排序的时间复杂度为O(N*log₂N)

2.2.13.完整代码

//Heap.h
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
typedef int T;//堆数据类型typedef struct Heap
{T* arr;//堆的存储位置int size;//堆的数据个数int capacity;//堆的容量
}Heap;void HeapInit(Heap* heap);//堆的初始化
void HeapCreate1(Heap* heap, T* a, int n);//创建堆方法1
void HeapCreate2(Heap* heap, T* a, int n);//创建堆方法2
void HeapDestory(Heap* heap);//将堆销毁
void HeapPush(Heap* heap,T x);//堆的构建
void HeapPrint(Heap* heap);//将堆数据打印出来
T HeapTop(Heap* heap);//取出堆顶数据
void HeapPop(Heap* heap);//删除堆顶数据
int HeapSize(Heap* heap);//堆的数据个数
bool HeapEmpty(Heap* heap);//堆是否为空void swap(T* x, T* y);//交换
void AdjustUp(T* arr, int child);//向上调整
void AdjustDown(T* arr, int size, int parent);//向下调整void HeapSort(int* arr,int n);//堆排序
//Heap.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "Heap.h"
void HeapInit(Heap* heap)//堆的初始化
{assert(heap);heap->arr = NULL;heap->capacity = heap->size = 0;
}//简单粗暴的一种方法
void HeapCreate1(Heap* heap, T* a, int n)//创建堆1
{assert(heap);HeapInit(heap);for (int i = 0; i < n; i++){HeapPush(heap, a[i]);}
}void AdjustDown(T* arr, int size, int parent);//定义在下面,这边要使用,声明一下void HeapCreate2(Heap* heap, T* a, int n)//创建堆2
{assert(heap);HeapInit(heap);//开辟空间heap->arr = (T*)malloc(sizeof(T) * n);if (heap->arr == NULL){perror("malloc fail");exit(-1);}//拷贝数据memcpy(heap->arr, a, n*sizeof(T));heap->size = heap->capacity = n;//从下至上进行向下调整for (int end = (n - 1 - 1) / 2; end >= 0; end--){AdjustDown(heap->arr, n, end);}
}void HeapDestory(Heap* heap)//将堆销毁
{assert(heap);free(heap->arr);//释放空间heap->size = heap->capacity = 0;
}void swap(T* x, T* y)//交换
{T tmp = *x;*x = *y;*y = tmp;
}//我们默认建立大堆哈
void AdjustUp(T* arr,int child)//向上调整
{int parent = (child - 1) / 2;//找父结点//使用parent>=0有点不合理,倒数第二次运行循环时child已经是0了,应该结束//但是(child-1)/2导致parent依旧为0,再次进入循环,然后通过else中的breakwhile (child>0){//孩子结点大于父结点if (arr[child] > arr[parent]){//交换swap(&arr[child], &arr[parent]);//继续向上调整child = parent;parent = (child - 1) / 2;}else{//如果孩子结点小于父结点,就不用向上调整了break;}}
}void HeapPush(Heap* heap, T x)//堆的插入
{assert(heap);//空间不足开辟内存if (heap->size == heap->capacity){int newcapacity = heap->capacity == 0 ? 4 : heap->capacity * 2;//realloc的第一个元素是NULL的话,功能和malloc一样T* newarr = (T*)realloc(heap->arr, newcapacity*sizeof(T));if (newarr == NULL){perror("realloc fail");exit(-1);}//修改已经开辟好空间后的信息heap->arr = newarr;//realloc可能不在原位扩容,所以说这一步是必要的heap->capacity = newcapacity;}//正式插入heap->arr[heap->size] = x;heap->size++;//数据个数+1//向上调整,保证是一个堆AdjustUp(heap->arr, heap->size - 1);
}void HeapPrint(Heap* heap)//将堆数据打印出来
{for (int i = 0; i < heap->size; ++i){printf("%d ", heap->arr[i]);}printf("\n");
}T HeapTop(Heap* heap)//取出堆顶数据
{	assert(heap);assert(heap->size > 0);return heap->arr[0];
}void AdjustDown(T* arr, int size,int parent)
{//选出左孩子和右孩子中较大的那个//假设较大的是左孩子int child = parent * 2 + 1;//孩子结点存在才向下调整while (child<size){if (child + 1 < size && arr[child + 1] > arr[child]){//进行判断,如果右孩子大于左孩子,就+1,因为左孩子和右孩子之间就相差1child++;}//孩子结点大于父节点,就需要交换,保证大堆的特性if (arr[child] > arr[parent]){swap(&arr[child], &arr[parent]);//继续向下调整parent = child;child = parent * 2 + 1;}else{//如果孩子结点小于父结点,说明不需要调整了,跳出循环break;}}}void HeapPop(Heap* heap)//删除堆顶数据
{assert(heap);assert(heap->size>0);//先将堆顶的元素和最后一个元素进行交换swap(&heap->arr[0], &heap->arr[heap->size - 1]);//堆数据个数-1heap->size--;//向下调整AdjustDown(heap->arr,heap->size,0);
}int HeapSize(Heap* heap)//堆的数据个数
{assert(heap);return heap->size;
}bool HeapEmpty(Heap* heap)//堆是否为空
{assert(heap);return heap->size == 0;
}void HeapSort(int* arr,int n)//堆排序
{//向上调整--O(N*logN)/*for (int i = 0; i < n; i++){AdjustUp(arr, i);}*///向下调整--O(N)for (int i =(n-1-1)/2; i>=0; i--){AdjustDown(arr, n,i);}//堆排序-升序-使用大堆int end = n-1;while (end > 0)//当end==0时说明调整结束{swap(&arr[0],&arr[end]);AdjustDown(arr, end, 0);end--;}}
//main.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "Heap.h"
//验证Push能否正常工作
void Test1()
{Heap heap;HeapInit(&heap);int array[] = { 27, 15, 19, 18, 28, 34, 65, 49, 25, 37 };for (int i = 0; i < sizeof(array) / sizeof(T); i++){HeapPush(&heap,array[i]);}HeapPrint(&heap);
}//验证能否正常使用Pop并模拟排序
void Test2()
{Heap heap;HeapInit(&heap);int array[] = { 27, 15, 19, 18, 28, 34, 65, 49, 25, 37 };for (int i = 0; i < sizeof(array) / sizeof(T); i++){HeapPush(&heap, array[i]);}HeapPrint(&heap);for (int i = 0; i < sizeof(array) / sizeof(T); i++){	printf("%d ", HeapTop(&heap));HeapPop(&heap);}printf("\n");
}//验证Create
void Test3()
{Heap heap;int array[] = { 27, 15, 19, 18, 28, 34, 65, 49, 25, 37 };HeapCreate2(&heap,array,sizeof(array)/sizeof(int));HeapPrint(&heap);for (int i = 0; i < sizeof(array) / sizeof(T); i++){printf("%d ", HeapTop(&heap));HeapPop(&heap);}printf("\n");
}//验证堆排序
void Test4()
{Heap heap;int array[] = { 27, 15, 19, 18, 28, 34, 65, 49, 25, 37 };HeapSort(array, sizeof(array) / sizeof(int));for (int i = 0; i < sizeof(array) / sizeof(int); i++){printf("%d ", array[i]);}
}int main()
{Test3();return 0;
}

3.Top-K问题

TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。
比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。
对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:
总共N个数据,用数据集合中前K个元素来建堆
前k个最大的元素,则建小堆
前k个最小的元素,则建大堆
设求前K个最大的元素,建小堆,用剩余的N-K个元素依次与堆顶元素来比较,比堆顶大的就替换,然后进行向下调整
将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素

//Top-K问题
#include <stdio.h>
#include <stdlib.h>
#include <time.h>void swap(T* x, T* y)//交换
{T tmp = *x;*x = *y;*y = tmp;
}void AdjustDown(T* arr, int size, int parent)
{//选出左孩子和右孩子中较小的那个//假设较小的是左孩子int child = parent * 2 + 1;//孩子结点存在才向下调整while (child < size){if (child + 1 < size && arr[child + 1] < arr[child]){//进行判断,如果右孩子小于左孩子,就+1,因为左孩子和右孩子之间就相差1child++;}//孩子结点小于父节点,就需要交换,保证小堆的特性if (arr[child] < arr[parent]){swap(&arr[child], &arr[parent]);//继续向下调整parent = child;child = parent * 2 + 1;}else{//如果孩子结点大于父结点,说明不需要调整了,跳出循环break;}}}int  main()
{int  k = 5;//求Top-10int n = 20;//数据数量srand((unsigned)time(NULL));//1.生成一堆随机数写入文件中//1.1打开文件FILE* fin = fopen("data.txt", "w");int flag = k;//1.2写入for (int i = 0; i < n; i++){//i%3保证是随机添加,而不是连续,flag保证添加了k个if (i % 3 == 0 && flag-- > 0){//1.3方便测试,添加几个大于等于10000的数据fprintf(fin, "%d\n", 10000+i);}else{fprintf(fin, "%d\n", rand() % 10000);}}//1.4关闭文件fclose(fin);//2.建立小堆//2.1开辟空间int* array = (int*)malloc(sizeof(int) * k);//2.2向下调整for (int i = (k - 1 - 1) / 2; i >= 0; i--){AdjustDown(array, n, i);}//3.用剩余的N-K个元素依次与堆顶元素来比较,比堆顶大的就替换,然后进行向下调整//3.1打开文件FILE* fout = fopen("data.txt", "r");//3.2从文件读取int value = 0;while (fscanf(fout, "%d", &value) != EOF){//3.3和堆顶进行比较if (array[0] < value){array[0] = value;//3.4向下调整AdjustDown(array, k, 0);}}//3.4关闭文件fclose(fout);//4.验证结果//10000 10006 10003 10009 10012for (int i = 0; i < k; i++){printf("%d ", array[i]);}printf("\n");return 0;
}

它的时间复杂度的计算过程:前面讲的向下调整的建堆过程的时间复杂度是O(N),N是结点个数,那么代入到这,K也是结点个数(数组大小),那么建堆过程的复杂度为O(K);还需要比较+向下调整,堆的大小是K,需要调整次数为log₂K-1(-1不包含自己层,并且这里的log₂K是比较精确的,不像前面排序会改变end,导致每次调整时间复杂度逐渐减小),时间复杂度是O(log₂K),需要比较N-K次,那么总的时间复杂度就是(N-K) * log₂K。综上所述,K+(N-K) * log₂K=>Top-K时间复杂度为O(N*log₂K),空间复杂度是O(K),这个空间K是存放堆的数据的
假设有100亿整数的数据,找Top10,那么就需要100亿*4byte=400亿byte=40G(1G=1024 *1024 *1024byte≈10亿byte),对于现在的电脑来说40G还是不现实,如果以后真有了,就可以建立一个N个数的大堆,Pop个K次,依次取堆顶,那么它的时间复杂度就为O(N+log₂N *K), 建堆需要O(N),K个数据需要向下调整log₂N,这其实和堆排序的时间复杂度计算方式差不多,只是向下调整K次和全部调整的区别。
其实如果真的能够实现40G的内存的话,两个的时间复杂度差不多,O(N *log₂K)忽略log₂K,O(N+log₂N *K)忽略log₂N *K,都是O(N),在N这个大数量级上其实也是可以忽略的!

🌸🌸二叉树-堆的知识大概就讲到这里啦,博主后续会继续更新更多数据结构的相关知识,干货满满,如果觉得博主写的还不错的话,希望各位小伙伴不要吝啬手中的三连哦!你们的支持是博主坚持创作的动力!💪💪

相关文章:

二叉树-堆(补充)

二叉树-堆 1.二叉树的基本特性2.堆2.1.堆的基本概念2.2.堆的实现2.2.1.基本结构2.2.2.堆的初始化2.2.3.堆的销毁2.2.4.堆的插入2.2.5.取出堆顶的数据2.2.6.堆的删除2.2.7.堆的判空2.2.8.堆的数据个数2.2.9.交换2.2.10.打印堆数据2.2.11.堆的创建2.2.12.堆排序2.2.13.完整代码 3…...

Java面试题2025-并发编程基础(多线程、锁、阻塞队列)

并发编程 一、线程的基础概念 一、基础概念 1.1 进程与线程A 什么是进程&#xff1f; 进程是指运行中的程序。 比如我们使用钉钉&#xff0c;浏览器&#xff0c;需要启动这个程序&#xff0c;操作系统会给这个程序分配一定的资源&#xff08;占用内存资源&#xff09;。 …...

【方法论】ChatGPT与DeepSeek的联合应用,提升工作效率的新解决方案

标题&#xff1a;ChatGPT与DeepSeek的联合应用&#xff0c;提升工作效率的新解决方案 【表格】ChatGPT与DeepSeek联合应用流程 阶段工具主要任务优势备注初稿生成ChatGPT基于用户输入生成初步内容高效、快速生成内容&#xff0c;适应多种主题适合生成长篇文章、报告、分析等验…...

RoboMaster- RDK X5能量机关实现案例(一)识别

作者&#xff1a;SkyXZ CSDN&#xff1a;https://blog.csdn.net/xiongqi123123 博客园&#xff1a;https://www.cnblogs.com/SkyXZ 在RoboMaster的25赛季&#xff0c;我主要负责了能量机关的视觉方案开发&#xff0c;目前整体算法已经搭建完成&#xff0c;实际方案上我使用的上…...

5分钟带你获取deepseek api并搭建简易问答应用

目录 1、获取api 2、获取base_url和chat_model 3、配置模型参数 方法一&#xff1a;终端中临时将加入 方法二&#xff1a;创建.env文件 4、 配置client 5、利用deepseek大模型实现简易问答 deepseek-v3是截止博文撰写之日&#xff0c;无论是国内还是国际上发布的大模型中…...

Ikigai是什么

Ikigai&#xff08;生き甲斐&#xff09; 是一个日语词语&#xff0c;意思是“生活的意义”或“生命的价值所在”。它是一种关于人生意义的哲学概念&#xff0c;源自日本文化&#xff0c;强调通过找到自己热爱、擅长、社会需要以及能带来经济回报的交集来实现幸福和满足感。 I…...

基于PyQt设计的智能停车管理系统

文章目录 一、前言1.1 项目介绍【1】项目开发背景【2】设计实现的功能【3】设计意义【4】国内外研究现状【6】摘要1.2 设计思路1.3 系统功能总结1.4 开发工具的选择【1】VSCODE【2】python【3】ptqt【4】HyperLPR31.5 参考文献二、安装Python环境1.1 环境介绍**1.2 Python版本介…...

Flink (十二) :Table API SQL (一) 概览

Apache Flink 有两种关系型 API 来做流批统一处理&#xff1a;Table API 和 SQL。Table API 是用于 Scala 和 Java 语言的查询API&#xff0c;它可以用一种非常直观的方式来组合使用选取、过滤、join 等关系型算子。Flink SQL 是基于 Apache Calcite 来实现的标准 SQL。无论输入…...

MySQL知识点总结(十三)

执行逻辑备份要具备哪些条件&#xff0c;其优缺点在哪。 逻辑备份是温备&#xff0c;创建逻辑备份文件时&#xff0c;MySQL服务器必须处于运行状态&#xff0c;其他应用程序在逻辑备份期间不能修改但可以执行读取操作。逻辑备份会把表结构和数据转换为SQL语句保存。 逻辑备份…...

ACL-2024 | 具身智能空间理解能力几何?EmbSpatial-Bench:视觉语言大模型在具身任务中空间理解水平测试基准

作者&#xff1a;Mengfei Du, Binhao Wu, Zejun Li, Xuanjing Huang, Zhongyu Wei 单位&#xff1a;复旦大学数据科学学院&#xff0c;复旦大学计算机科学学院 论文标题&#xff1a;EmbSpatial-Bench: Benchmarking Spatial Understanding for Embodied Tasks with Large Vis…...

动手学图神经网络(6):利用图神经网络进行点云分类

利用图神经网络进行点云分类 引言 在本教程中,大家将学习使用图神经网络(Graph Neural Networks, GNN)进行点云分类的基本工具。给定一组对象或点集的数据集,将这些对象嵌入到一个特征空间中,使得它们在特定任务下能够分类。将原始点云作为神经网络的输入,让网络学习捕…...

Ollama+DeepSeek本地大模型部署

1、Ollama 官网&#xff1a;https://ollama.com/ Ollama可以干什么&#xff1f; 可以快速在本地部署和管理各种大语言模型&#xff0c;操作命令和dokcer类似。 mac安装ollama&#xff1a; # 安装ollama brew install ollama# 启动ollama服务&#xff08;默认11434端口&#xf…...

docker安装Redis:docker离线安装Redis、docker在线安装Redis、Redis镜像下载、Redis配置、Redis命令

一、镜像下载 1、在线下载 在一台能连外网的linux上执行docker镜像拉取命令 docker pull redis:7.4.0 2、离线包下载 两种方式&#xff1a; 方式一&#xff1a; -&#xff09;在一台能连外网的linux上安装docker执行第一步的命令下载镜像 -&#xff09;导出 # 导出镜像…...

HTML 标题

HTML 标题 引言 HTML&#xff08;超文本标记语言&#xff09;是构建网页的基础&#xff0c;而标题则是网页中不可或缺的元素。标题不仅能够帮助用户快速了解网页内容&#xff0c;还能够对搜索引擎优化&#xff08;SEO&#xff09;产生重要影响。本文将详细介绍HTML标题的用法…...

记录 | MaxKB创建本地AI智能问答系统

目录 前言一、重建MaxKBStep1 复制路径Step2 删除MaxKBStep3 创建数据存储文件夹Step4 重建 二、创建知识库Step1 新建知识库Step2 下载测试所用的txtStep3 上传本地文档Step4 选择模型补充智谱的API Key如何获取 Step5 查看是否成功 三、创建应用Step1 新建应用Step2 配置AI助…...

Linux 非阻塞IO

Linux 非阻塞IO 1. fcntl() 在Linux操作系统中&#xff0c;fcntl() 是一个用于操作文件描述符的系统调用。它提供了多种功能&#xff0c;包括控制文件描述符的属性、管理文件锁定、设置文件的非阻塞模式等。 本文只截取了用于IO模型的 fcntl() 部分内容&#xff0c; fcntl() …...

美国本科申请文书PS写作中的注意事项

在完成了introduction之后&#xff0c;便可进入到main body的写作之中。美国本科申请文书PS的写作不同于学术论文写作&#xff0c;要求你提出论点进行论证之类。PS更多的注重对你自己的经历或者motivation的介绍和描述。而这一描述过程只能通过对你自己的过往的经历的展现才能体…...

Qt文件操作

目录 一、文件操作相关类 1.QFile 2.QFileInfo 3.QTextStream 4.QDataStream 5.QDir 6.QFileSystemWatcher 7.QTemporaryFile 二、文件操作示例 1.文本文件操作 2.目录操作 3.二进制文件操作 一、文件操作相关类 1.QFile QFile类用于文件的创建、读写、复制、删除…...

赚钱的究极认识

1、赚钱的本质是提供了价值或者价值想象 价值&#xff1a; 比如小米手机靠什么&#xff1f;“性价比”&#xff0c;什么饥饿营销&#xff0c;创新&#xff0c;用户参与&#xff0c;生态供应链&#xff0c;品牌这些不能说不重要&#xff0c;但是加在一起都没有“性价比”这3字重…...

【项目】基于Qt开发的音乐播放软件

目录 项目介绍 项目概述 界面开发 界面分析 创建工程 主界面布局设计 窗口主框架设计 界面美化 主窗口设定 添加图片资源 head处理 播放控制区处理 自定义控件 BtForm 推荐页面 自定义CommonPage 自定义ListItemBox 自定义MusicSlider 自定义VolumeTool 音…...

week08_文本匹配任务

1、文本匹配任务概述 狭义&#xff1a; 给定一组文本&#xff0c;判断其是否语义相似 今天天气不错 match 今儿个天不错呀 √ 今天天气不错 match 你的代码有bug 以分值形式给出相似度 今天天气不错 match 今儿个天不错呀 0.9 今天天气不错 match…...

01-01 五元组

[外链图片转存中…(img-8JR8fhPZ-1737855365022)] 01-01 五元组 网络中的五元组&#xff08;5-Tuple&#xff09; 是用于唯一标识一个网络连接或数据流的五个关键参数组合。这五个参数共同定义了数据包的来源、目的地以及传输方式&#xff0c;是网络设备&#xff08;如防火墙…...

5.2 软件需求分析

文章目录 需求分析的意义软件需求的组成需求分析的5个方面需求分析方法 需求分析的意义 需求分析解决软件“做什么”的问题。由于开发人员比较熟悉计算机而不熟悉领域业务&#xff0c;用户比较熟悉领域业务而不熟悉计算机&#xff0c;双方需要通过交流&#xff0c;制定出完整、…...

OpenCV:在图像中添加噪声(瑞利、伽马、脉冲、泊松)

目录 简述 1. 瑞利噪声 2. 伽马噪声 3. 脉冲噪声 4. 泊松噪声 总结 相关阅读 OpenCV&#xff1a;在图像中添加高斯噪声、胡椒噪声-CSDN博客 OpenCV&#xff1a;高通滤波之索贝尔、沙尔和拉普拉斯-CSDN博客 OpenCV&#xff1a;图像处理中的低通滤波-CSDN博客 OpenCV&…...

进程池的制作(linux进程间通信,匿名管道... ...)

目录 一、进程间通信的理解 1.为什么进程间要通信 2.如何进行通信 二、匿名管道 1.管道的理解 2.匿名管道的使用 3.管道的五种特性 4.管道的四种通信情况 5.管道缓冲区容量 三、进程池 1.进程池的理解 2.进程池的制作 四、源码 1.ProcessPool.hpp 2.Task.hpp 3…...

C++:多继承习题3

题目内容&#xff1a; 声明一个时间类Time&#xff0c;时间类中有3个私有数据成员(Hour&#xff0c;Minute&#xff0c;Second)和两个公有成员函数(SetTime和PrintTime)。要求&#xff1a; &#xff08;1&#xff09; SetTime根据传递的3个参数为对象设置时间&#xff1b; &a…...

数论问题75

命题&#xff0c;证明:存在K∈N&#xff0c;使得对于每个n∈N&#xff0c;Kx2^n1都是合数。 证明:设n2^m&#xff0c;当m0&#xff0c;1&#xff0c;2&#xff0c;3&#xff0c;4时&#xff0c;a(m)2^(2^m)1都是素数。 a(0)213&#xff0c;a(1)2^215&#xff0c;a(2)2^4117&…...

Baklib引领数字化内容管理转型提升企业运营效率

内容概要 在数字化迅速发展的背景下&#xff0c;企业正面临着前所未有的内容管理挑战。传统的内容管理方式已难以适应如今的信息爆炸&#xff0c;企业需要更加高效、智能的解决方案以应对复杂的数据处理需求。Baklib作为行业的先锋&#xff0c;以其创新技术对数字化内容管理进…...

2025年AI手机集中上市,三星Galaxy S25系列上市

2025年被认为是AI手机集中爆发的一年&#xff0c;各大厂商都会推出搭载人工智能的智能手机。三星Galaxy S25系列全球上市了。 三星Galaxy S25系列包含S25、S25和S25 Ultra三款机型&#xff0c;起售价为800美元&#xff08;约合人民币5800元&#xff09;。全系搭载骁龙8 Elite芯…...

Vue2官网教程查漏补缺学习笔记 - 3Vue实例4模板语法5计算属性监听器

3 Vue实例 3.1 创建一个 Vue 实例 每个 Vue 应用都是通过用 Vue 函数创建一个新的 Vue 实例开始的&#xff1a; var vm new Vue({// 选项 })虽然没有完全遵循 MVVM 模型&#xff0c;但是 Vue 的设计也受到了它的启发。因此在文档中经常会使用 vm (ViewModel 的缩写) 这个变…...

2025年数学建模美赛:A题分析(1)Testing Time: The Constant Wear On Stairs

2025年数学建模美赛 A题分析&#xff08;1&#xff09;Testing Time: The Constant Wear On Stairs 2025年数学建模美赛 A题分析&#xff08;2&#xff09;楼梯磨损分析模型 2025年数学建模美赛 A题分析&#xff08;3&#xff09;楼梯使用方向偏好模型 2025年数学建模美赛 A题分…...

WPS数据分析000007

目录 一、分列 智能分列 出生日期 数值转换 公式不运算 二、数据对比 离职员工 新入职员工 都在职的员工 三、合并计算 四、拆分表格 合并表格 一、分列 智能分列 出生日期 数据求和 文本型数字左对齐&#xff1b;数值型数字右对齐 数值转换 方式一&#xff1a; 方…...

oracle 19C RAC打补丁到19.26

oracle 19CRAC打补丁到19.26 本文只保留简介命令和每个命令大概的用时&#xff0c;方便大家直接copy使用&#xff0c;并了解每个命令的预期时间&#xff0c;减少命令执行期的等待焦虑。 1.本次所需的补丁如下 p6880880_190000_Linux-x86-64.zip &#xff08;.43的opatch&…...

动手学图神经网络(8):在消息传递中定制聚合操作

在消息传递中定制聚合操作 安装Pytorch和PyG # 安装所需的包 import os import torch os.environ[TORCH] = torch.__version__# 以下是安装命令,实际运行时可取消注释 #!pip install -q torch-scatter -f https://data.pyg.org/whl/torch-${TORCH}.html #!pip install -q to…...

MySQL安装教程

一、下载 点开下面的链接&#xff1a;下载地址 点击Download 就可以下载对应的安装包了, 安装包如下: 二、解压 下载完成后我们得到的是一个压缩包&#xff0c;将其解压&#xff0c;我们就可以得到MySQL 8.0.34 的软件本体了(就是一个文件夹)&#xff0c;我们可以把它放在你想…...

信息学奥赛一本通 1390:食物链【NOI2001】| 洛谷 P2024 [NOI2001] 食物链

【题目链接】 ybt 1390&#xff1a;食物链【NOI2001】 洛谷 P2024 [NOI2001] 食物链 【题目考点】 1. 种类并查集 2. 带权并查集 【解题思路】 解法1&#xff1a;种类并查集 已知有三类动物A、B、C。A吃B&#xff0c;B吃C&#xff0c;C吃A。 对于B类动物来说&#xff0c…...

Linux网络之序列化和反序列化

目录 序列化和反序列化 上期我们学习了基于TCP的socket套接字编程接口&#xff0c;并实现了一个TCP网络小程序&#xff0c;本期我们将在此基础上进一步延伸学习&#xff0c;实现一个网络版简单计算器。 序列化和反序列化 在生活中肯定有这样一个情景。 上图大家肯定不陌生&a…...

【Django教程】用户管理系统

Get Started With Django User Management 开始使用Django用户管理 By the end of this tutorial, you’ll understand that: 在本教程结束时&#xff0c;您将了解&#xff1a; Django’s user authentication is a built-in authentication system that comes with pre-conf…...

万字长文总结前端开发知识---JavaScriptVue3Axios

JavaScript学习目录 一、JavaScript1. 引入方式1.1 内部脚本 (Inline Script)1.2 外部脚本 (External Script) 2. 基础语法2.1 声明变量2.2 声明常量2.3 输出信息 3. 数据类型3.1 基本数据类型3.2 模板字符串 4. 函数4.1 具名函数 (Named Function)4.2 匿名函数 (Anonymous Fun…...

React基础

前言 &#xff08;2021年笔记&#xff09;补充记录 React基础 前言React讲义一、create-react-app二、关于React1、React的起源和发展2、React与传统MVC的关系3、React高性能的体现&#xff1a;虚拟DOM4、React的特点和优势 三、编写第一个react应用程序四、元素与组件1、函数…...

读书笔记:《华为突围ERP封锁全纪实》

文章背景&#xff1a; 2019年5月&#xff0c;华为被美国制裁&#xff0c;其ERP系统面临断供风险。ERP系统是企业核心管理软件&#xff0c;一旦中断&#xff0c;华为的全球业务将陷入瘫痪。面对这一生死存亡的危机&#xff0c;华为启动了“突围”计划&#xff0c;历经数年艰苦奋…...

Linux的udev详解、安装和使用(dev下的设备每次开机的名称不固定怎么办?)

前言&#xff08;问题与需求&#xff09;&#xff1a; 在传统的devfs 1&#xff1a;设备映射的不确定&#xff1a;一个设备多次加载设备的设备文件可能不同&#xff0c;比如一个hub有可能是ttyUSB0或ttyUSB2或ttyUSB3 2&#xff1a;devfs没有足够的主辅设备号&#xff0c;当设…...

单向循环链表的概念+单向循环链表的结点插入+单向循环链表的结点删除+程序设计与笔试题分析

单向循环链表的原理与应用 思考&#xff1a;对于单向链表而言&#xff0c;想要遍历链表&#xff0c;则必须从链表的首结点开始进行遍历&#xff0c;请问有没有更简单的方案实现链表中的数据的增删改查&#xff1f; 回答&#xff1a;是有的&#xff0c;可以使用单向循环的链表…...

【蓝桥杯嵌入式入门与进阶】2.与开发板之间破冰:初始开发板和原理图2

个人主页&#xff1a;Icomi 专栏地址&#xff1a;蓝桥杯嵌入式组入门与进阶 大家好&#xff0c;我是一颗米&#xff0c;本篇专栏旨在帮助大家从0开始入门蓝桥杯并且进阶&#xff0c;若对本系列文章感兴趣&#xff0c;欢迎订阅我的专栏&#xff0c;我将持续更新&#xff0c;祝你…...

Jetson Xavier NX 安装 CUDA 支持的 PyTorch 指南

本指南将帮助开发者完成在 Jetson Xavier NX 上安装 CUDA 支持的 PyTorch。 安装方法 在 Jetson 上安装 Pytorch 只有两种方法。 一种是直接安装他人已经编译好的 PyTorch 轮子&#xff1b;一种是自己从头开始开始构建 PyTorch 轮子并且安装。 使用轮子安装 可以从我的 Gi…...

“harmony”整合不同平台的单细胞数据之旅

其实在Seurat v3官方网站的Vignettes中就曾见过该算法&#xff0c;但并没有太多关注&#xff0c;直到看了北大张泽民团队在2019年10月31日发表于Cell的《Landscap and Dynamics of Single Immune Cells in Hepatocellular Carcinoma》&#xff0c;为了同时整合两类数据&#xf…...

[权限提升] 操作系统权限介绍

关注这个专栏的其他相关笔记&#xff1a;[内网安全] 内网渗透 - 学习手册-CSDN博客 权限提升简称提权&#xff0c;顾名思义就是提升自己在目标系统中的权限。现在的操作系统都是多用户操作系统&#xff0c;用户之间都有权限控制&#xff0c;我们通过 Web 漏洞拿到的 Web 进程的…...

大模型本地部署流程介绍

大模型本地部署流程介绍 随着人工智能技术的快速发展&#xff0c;大模型&#xff08;如大型语言模型、图像识别模型等&#xff09;的应用越来越广泛。然而&#xff0c;由于这些模型通常体积庞大且计算资源要求高&#xff0c;如何在本地环境中高效部署成为了一个重要的议题。以…...

浅析百度AOI数据与高德AOI数据的差异性

目录 前言 一、AOI属性数据 1、百度AOI数据 2、高德AOI数据 二、AOI矢量边界 1、百度AOI空间范围 2、高德AOI空间范围 三、数据获取频次和难易程度 1、接口限制 2、数据转换成本 四、总结 前言 在当今数字化时代&#xff0c;地理信息数据的精准性和丰富性对于城市规划…...

LeetCode 119. 杨辉三角 II

题意&#xff1a;求杨辉三角&#xff08;帕斯卡三角&#xff09;的第n行&#xff08;n从0开始&#xff09; 杨辉三角的每一行是二项式排列组合的展开式 第n行为: C n 0 , C n 1 , C n 2 , … , C n n C_{n}^{0}, C_{n}^{1}, C_{n}^{2}, \dots, C_{n}^{n} Cn0​,Cn1​,Cn2​,……...