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

《数据结构初阶》【堆 + 堆排序 + TOP-K】

【堆 + 堆排序 + TOP-K】目录

  • 前言:
    • 什么是堆?
    • 堆的实现方式有哪些?我们要选择哪种方式进行实现?
  • ----------------堆的实现----------------
    • 什么是向上调整算法,要怎么实现?
    • 什么是向下调整算法,要怎么实现?
    • 向上调整算法/向下调整算法的注意事项?
  • 堆的数组实现
    • 怎么建堆?建堆的方法有哪些?
    • 为什么堆化法建堆时从最后一个非叶子节点开始?
    • 插入法/堆化法建堆的时间复杂度是怎么计算出来的?
    • 怎么实现堆的插入操作?
    • 怎么实现堆的删除操作?
    • --------------------------------
    • 头文件
    • 实现文件
    • 测试文件
    • 运行结果
  • ----------------堆的应用----------------
  • 堆排序
    • 什么是堆排序?
    • 怎么实现堆排序?
    • 堆排序的效率和属性?
    • --------------------------------
    • 头文件
    • 实现文件
    • 测试文件
    • 运行结果
  • TOP-K问题
    • 什么是TOP-K问题?
    • 解决TOP-K问题的思路和实现的步骤?
    • 使用堆解决TOP-K问题的性能怎么样?好处又是什么?
    • --------------------------------
    • 源文件
    • 运行结果

在这里插入图片描述

往期《数据结构初阶》回顾:
【时间复杂度 + 空间复杂度】
【顺序表 + 单链表 + 双向链表】
【顺序表/链表 精选15道OJ练习】
【顺序栈 + 链式队列 + 循环队列】
【链式二叉树】

前言:

Hi~ 宝子们~(๑・̀ㅂ・́)و✧,今天是周一,阳光明媚。新一周的旅程又要开始了是不是感觉活力满满呀!💪
在之前的数据结构的学习旅程中🚀,咱们已经积累了不少经验啦~🎉
⏳现在,是时候来深入学习《数据结构初阶》里 “堆 + 堆排序 + TOP - K🏆” 的内容了!
这就像是给我们的编程秘籍又添上了超厉害的几页📖💫,解锁新技能指日可待!

什么是堆?

堆(Heap):是一种特殊的完全二叉树结构。

满足以下性质:

  1. 堆序性:每个节点的值必须满足与子节点的大小关系。
    • 最大堆(大根堆):父节点的值 ≥ 子节点的值(根节点最大)
    • 最小堆(小根堆):父节点的值 ≤ 子节点的值(根节点最小)
  2. 完全二叉树:除了最后一层,其他层必须填满,且最后一层从左到右填充。

堆的实现方式有哪些?我们要选择哪种方式进行实现?

堆常见的实现方式主要是基于数组 ,这是因为堆是完全二叉树,而数组能高效地存储完全二叉树结构。

在这里插入图片描述

数组实现的存储规则:把堆中的元素按完全二叉树的层序遍历顺序存放在数组里。

假设根节点在数组中的索引为 0 0 0 ,对于节点 i i i ,其:

  • 左子节点索引为: 2 ∗ i + 1 2 * i + 1 2i+1
  • 右子节点索引为: 2 ∗ i + 2 2 * i + 2 2i+2
  • 父节点索引为: ( i − 1 ) / 2 (i - 1) / 2 (i1)/2

例如:有一个最小堆 [1, 3, 4, 7, 8, 9] ,根节点 1 的索引是 0 ,其左子节点 3 索引为 1 ,右子节点 4 索引为 2

在这里插入图片描述

typedef int HPDataType;
typedef struct Heap
{//核心思路:堆是完全二叉树所以使用数组实现比较好//1.记录数组的容量 ---> 一个int变量//2.记录当前数组中元素的数量 --> 一个int变量//3.使用数组存储堆中的节点 ---> 定义一个动态数组int capacity;int size;HPDataType* a;}HP;

----------------堆的实现----------------

堆作为一种重要的数据结构,其高效性依赖于两个核心算法:向上调整算法(Heapify Up)向下调整算法(Heapify Down)

这两种算法是堆操作的基础,直接支撑了堆的各类接口实现。

基于这些,在使用数组实现堆之前我们要先来学习一下这两个算法。

什么是向上调整算法,要怎么实现?

向上调整算法当新元素插入堆的末尾,通过向上调整使其满足堆序性。

向上调整的执行流程

  1. 将新元素插入堆的末尾(数组的最后位置)
  2. 比较新元素与其父节点的值:
    • 若不满足堆序性(如:在最小堆中,新元素比父节点小),则交换两者
    • 重复此过程,直到新元素到达合适位置成为根节点

时间复杂度 O ( log ⁡ n ) O(\log n) O(logn),其中 n n n 为堆的元素数量。

  • 因为:堆的高度为 log ⁡ n \log n logn,最坏情况下需调整至根节点。

什么是向下调整算法,要怎么实现?

向下调整算法堆顶元素被删除或替换,通过向下调整重新恢复堆序性。

向下调整的执行流程

  1. 将堆顶元素替换为堆的最后一个元素(或者:删除堆顶元素后,移动最后元素至堆顶)
  2. 比较当前节点与其子节点的值:
    • 在最小堆中,若当前节点大于其子节点中的最小值,则与该最小值交换。
    • 在最大堆中,若当前节点小于其子节点中的最大值,则与该最大值交换。
    • 重复此过程,直到当前节点到达合适位置或成为叶子节点。

时间复杂度 O ( log ⁡ n ) O(\log n) O(logn)

  • 因为:最坏情况下需从根节点调整至叶子节点。

向上调整算法/向下调整算法的注意事项?

向上调整算法要求原树必须已经是一个堆

因为向上调整算法是在已知当前节点的父节点到根节点的路径上已经是一个堆的情况下,将新插入的节点从下往上调整,使其满足堆的性质。

  • 例如:在向小根堆中插入一个新元素时,将新元素先放在堆的最后一个位置,然后从该位置开始向上调整,比较当前节点与父节点的值,如果当前节点的值小于父节点的值,则交换它们,直到满足堆的性质或者到达根节点。

向下调整算法要求根节点的左右子树必须是一个堆

因为向下调整算法在执行过程中,是假设根节点的左右子树已经是符合堆性质的堆结构,然后通过比较根节点与左右孩子节点的值,并在必要时进行交换,来使整个树重新满足堆的性质。如果左右子树不是堆,那么算法无法保证最终调整后的树是一个堆。

  • 例如:对于一个小根堆,要将根节点向下调整,会先比较根节点与左右孩子中较小的那个,如果左右子树不是小根堆,那么这个较小值不一定是真正应该与根节点交换的值,就无法正确调整。

现在我们给出一个数组,在逻辑上可以将其看作一颗完全二叉树。

例如:对于数组 int array[] = {27, 15, 19, 18, 28, 34, 65, 49, 25, 37},在使用向下调整算法前,需要保证其左右子树符合堆的特性,否则无法正确调整为小堆 。

在这里插入图片描述

堆的数组实现

怎么建堆?建堆的方法有哪些?

建堆:是将一个无序的完全二叉树调整为堆结构的过程。

常见的建堆方法有两种自顶向下的调整方法自底向上的调整方法

自底向上(插入法):将元素逐个插入空堆,每次插入后通过 向上调整(Heapify Up) 维护堆性质。

步骤:(示例:建大根堆)

  • 假设初始时堆中只有一个元素,即数组的第一个元素,它本身就是一个堆。
  • 依次将数组中的其他元素插入到堆中。
    1. 对于每个插入的元素,将其与父节点比较,如果大于父节点,则交换位置
    2. 继续向上比较,直到满足堆的性质(父节点大于等于子节点)

在这里插入图片描述


自顶向下(堆化法):从最后一个非叶子节点开始,向前遍历并对每个节点执行 向下调整(Heapify Down) 来维护堆性质。

步骤

  1. 找到最后一个非叶子节点(索引为: ( n − 1 ) / 2 (n-1)/2 (n1)/2
  2. 从该节点开始向前遍历到根节点
  3. 对每个节点执行向下调整,使其子树满足堆性质

在这里插入图片描述

温习提示:建堆的时候我们更多的使用的 堆化法 进行建堆,因为

方法时间复杂度空间复杂度
自底向上(插入法) O ( n l o g ⁡ n ) O(nlog⁡n) O(nlogn) O ( n ) O(n) O(n)
自顶向下(堆化法) O ( n ) O(n) O(n) O ( 1 ) O(1) O(1)

为什么堆化法建堆时从最后一个非叶子节点开始?

在建堆过程中,我们需要从最后一个非叶子节点(即:(n-1)/2)开始,自底向上依次调用向下调整,这是因为:

  1. 所有叶子节点位于该节点之后,叶子节点本身天然满足堆性质(因为它们没有子节点,无需调整)
  2. 从最后一个非叶子节点开始调整,可以保证每次调用 向下调整(Heapify Down) 时,其左右子树已经是堆

插入法/堆化法建堆的时间复杂度是怎么计算出来的?

我们先来看看 插入法的时间复杂度

  • 每次插入的向上调整操作需 O ( l o g ⁡ n ) O(log⁡n) O(logn),共 n n n 次操作 → 总复杂度 O ( n l o g ⁡ n ) O(nlog⁡n) O(nlogn)

接下来我们再来看看 堆化法的时间复杂度

因为堆是完全二叉树,而满二叉树也是完全二叉树,所以此处为了简化问题,使用满二叉树来证明(时间复杂度本来看的 就是近似值,所以多几个结点并不影响最终结果)

在这里插入图片描述

因此:使用堆化法建堆的时间复杂度为: O ( n ) O(n) O(n)

怎么实现堆的插入操作?

堆的插入操作实现步骤(以数组表示完全二叉树)

前提条件

  • 大顶堆:每个父节点的值 子节点的值
  • 小顶堆:每个父节点的值 子节点的值

通用步骤

  1. 将新元素插入堆尾
    • 将元素添加到数组末尾,保持完全二叉树的结构特性
    • 新元素的索引为 n(数组长度),其父节点索引为 (n-1)/2(向下取整)
  2. 向上调整(Heapify Up)/插入法
    • 从新节点开始,自底向上比较并交换,直到满足堆性质或到达根节点。
    • 比较逻辑
      • 大顶堆:若当前节点 > 父节点,则交换两者(确保父节点值最大)
      • 小顶堆:若当前节点 < 父节点,则交换两者(确保父节点值最小)
  3. 终止条件
    • 大顶堆:当前节点 父节点 到达根节点。
    • 小顶堆:当前节点 父节点 到达根节点。

图像演示:小根堆的插入

在这里插入图片描述

怎么实现堆的删除操作?

堆的删除操作实现步骤(以数组表示完全二叉树)

前提条件

  • 大顶堆:每个父节点的值 子节点的值
  • 小顶堆:每个父节点的值 子节点的值

通用步骤

  1. 移除堆顶元素
    • 将堆顶元素(数组首元素)与堆尾元素(数组尾元素)交换。
    • 删除数组最后一个元素(原堆顶),保持完全二叉树的结构特性。
    • 新的堆顶元素移至索引 0,数组长度减 1
  2. 向下调整(Heapify Down)/堆化法
    • 从新堆顶开始,自顶向下比较并交换,直到满足堆性质或到达叶子节点。
    • 比较逻辑
      • 大顶堆:若当前节点 < 任一子节点,选择 较大的子节点 交换(确保父节点值最大)
      • 小顶堆:若当前节点 > 任一子节点,选择 较小的子节点 交换(确保父节点值最小)
  3. 终止条件
    • 大顶堆:当前节点 所有子节点 到达叶子节点。
    • 小顶堆:当前节点 所有子节点 到达叶子节点。

图像演示:小根堆的删除

在这里插入图片描述

在这里插入图片描述

--------------------------------

头文件

---------------------------------Heap.h---------------------------------
#pragma once//任务1:声明需要使用的头文件
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>//任务2:定义堆的结构体(存储类型)
typedef int HPDataType;
typedef struct Heap
{//核心思路:堆是完全二叉树所以使用数组实现比较好//1.记录数组的容量 ---> 一个int变量//2.记录当前数组中元素的数量 --> 一个int变量//3.使用数组存储堆中的节点 ---> 定义一个动态数组int capacity;int size;HPDataType* a;}HP;//任务3:声明堆的常用的工具函数
//1.交换数组中两个元素
//2.堆的向上调整算法(用于插入元素后维护堆的性质)
//3.堆的向下调整算法(用于删除元素后维护堆的性质)void Swap(HPDataType* a, HPDataType* b);
void AdjustUp(HPDataType* a, int child);
void AdjustDown(HPDataType* a, int parent, int n);//注意:这里我们使用的这些工具函数的形参不是:堆,;而是:动态数组
//原因:传参时传入动态数组是因为这些工具函数不仅可以用在实现“堆”这种的数据结构上,还可以继续使用在“堆排序”上//任务4:声明堆的接口
//1.堆的初始化
//2.堆的销毁//3.堆的插入
//4.堆的删除//5.堆的取出堆元素
//6.堆的判空void HPInit(HP* php);
void HPDestroy(HP* php);void HPPush(HP* php, HPDataType x);
void HPPop(HP* php);HPDataType HPTop(HP* php);
bool HPEmpty(HP* php);

实现文件

--------------------------------Heap.c--------------------------------#include "Heap.h"//1.实现:“交换两个元素的值”工具函数
void Swap(HPDataType* a, HPDataType* b)//形参是是指针类型,函数中的修改会影响到原数组中的值
{HPDataType tmp = *a;*a = *b;*b = tmp;
}//2.实现:“向上调整算法”工具函数
void AdjustUp(HPDataType* a, int child)
{//1.计算出父节点在数组中的下标(我们有孩子 ---> 得到双亲)int parent = child - 1 >> 1;//2.接下来不断的进行向上调整,何时调整结束? ---> 回答:1.当孩子已经调整成根节点时 2.当孩子不满小堆的性质时(这里我们模拟小根堆)while (child > 0){//3.使用if-else语句进行小根堆的条件检查(小根堆:谁小谁当爹)if (a[child] >= a[parent]) return;else{//4.1:交换的孩子节点和双亲节点的值Swap(&a[child], &a[parent]);//4.2:更新孩子的索引 ---> 因为我们要为孩子找到一个合适的位置child = parent;//4.3:求出现在孩子的双亲节点的索引parent = child - 1 >> 1;}}
}//3.实现:“向下调整算法”工具函数
void AdjustDown(HPDataType* a, int parent, int n)
{//1.计算出孩子节点在数组中的下标(我们有双亲节点 --->  得到“值最小的”孩子)//这里和向上调整有点不一样:找双亲节点就只有一个,但是找孩子节点有两个,要找哪一个呢?---> 找值最小的孩子//注:这里使用假设法:假设双亲的左孩子是最小的孩子int minchild = parent << 1 + 1;//2.接下来不断地进行向下调整,何时调整结束 --->  1.当孩子节点的索引已经大于数组的容量,即:孩子不存在时 2.当孩子不满足小根堆的条件检查时while (minchild < n){//3.调整出minchild代表是最小的孩子if (minchild + 1 < n && a[minchild] > a[minchild + 1]){minchild++; //切换到右孩子}//3.使用if-else语句进行小根堆的条件检查if (a[minchild] >= a[parent]) return;else{//4.1:交换孩子节点和双亲节点的值Swap(&a[minchild], &a[parent]);//4.1:更新双亲节点的索引parent = minchild;//4.2:求出现在双亲节点的孩子节点的索引minchild = parent << 1 + 1;}}}//--------------------------------------------------------------------------------------------------------------------------------------//1.实现:“堆的初始化”操作
void HPInit(HP* php)
{assert(php);php->capacity = 0;php->size = 0;php->a = NULL;}//2.实现:“堆的销毁”操作
void HPDestroy(HP* php)
{assert(php);php->capacity = 0;php->size = 0;free(php->a);php->a = NULL;
}//3.实现:“堆的插入”操作
void HPPush(HP* php,HPDataType x)
{assert(php);//1.插入操作要先判断:isfull + 选择进行扩容if (php->size == php->capacity){//1.1进行扩容:(扩容可能有两种情况:1.我们没有分配空间进行扩容 2.我们现在的空间不足进行的扩容)//1.1.1:先判断一下我们是因为哪种情况才进行扩容的int newCapacity = php->capacity == 0 ? 4 : 2 * php->capacity;//1.1.2:realloc新的内存空间HPDataType* tmp = (HPDataType*)realloc(php->a, newCapacity * sizeof(HPDataType));//1.2.判断空间是否分配成功if (tmp == NULL){perror("realloc fail");}//1.3.更新动态数组的内存空间php->a = tmp;//1.4.更新动态数组的容量php->capacity = newCapacity;}//2.1.将元素插入到数组的末尾php->a[php->size] = x;//2.2.更新动态数组的容量php->size++;//3.向上调整新插入的元素AdjustUp(php->a, php->size - 1);// 注意:向上调整算法的第二个形参是“孩子在数组中的索引”
}//4.实现:“堆的删除”操作
void HPPop(HP* php)
{assert(php);///0.删除元素要先判断堆中是否还有元素assert(php->size > 0);//1.将堆顶的元素与末尾的元素进行交换Swap(&php->a[0], &php->a[php->size - 1]);//2.更新动态数组的容量-->删除堆末尾的元素php->size--;//3.从堆顶开始向下调整AdjustDown(php->a, 0, php->size);
}//5.实现:“堆的取堆顶元素”操作
HPDataType HPTop(HP* php)
{assert(php);assert(php->size > 0);return php->a[0];
}//6.实现:“堆的判空”操作
bool HPEmpty(HP* php)
{assert(php);return php->size == 0;
}

测试文件

#include "Heap.h"/*-------------------------打印堆内容(辅助函数)-------------------------*/
void PrintHeap(HP* php) 
{printf("堆内容(大小=%d, 容量=%d): ", php->size, php->capacity);for (int i = 0; i < php->size; i++) {printf("%d ", php->a[i]);}printf("\n");
}void TestHeap() 
{HP hp;HPInit(&hp);printf("===== 开始堆(Heap)测试 =====\n");/*------------------测试堆的插入和向上调整------------------*/printf("\n------测试插入和向上调整------\n");HPPush(&hp, 5);HPPush(&hp, 3);HPPush(&hp, 8);HPPush(&hp, 1);HPPush(&hp, 2);PrintHeap(&hp);printf("堆顶元素: %d (应为1)\n", HPTop(&hp));printf("当前堆大小: %d (应为5)\n", hp.size);/*------------------测试堆的删除和向下调整------------------*/printf("\n------测试删除和向下调整------\n");HPPop(&hp);PrintHeap(&hp);printf("删除堆顶后新堆顶: %d (应为2)\n", HPTop(&hp));HPPop(&hp);PrintHeap(&hp);printf("再次删除堆顶后新堆顶: %d (应为3)\n", HPTop(&hp));/*------------------测试堆的判空------------------*/printf("\n------测试堆判空------\n");printf("当前堆是否为空: %s (应为否)\n", HPEmpty(&hp) ? "是" : "否");/*------------------测试边界情况------------------*/printf("\n------测试边界情况------\n");while (!HPEmpty(&hp)) {HPPop(&hp);}PrintHeap(&hp);printf("清空堆后大小: %d (应为0)\n", hp.size);printf("清空堆后是否为空: %s (应为是)\n", HPEmpty(&hp) ? "是" : "否");/*------------------清理堆------------------*/HPDestroy(&hp);printf("\n堆已销毁\n");printf("\n===== 堆测试完成 =====\n");
}int main() 
{TestHeap();return 0;
}

运行结果

在这里插入图片描述

----------------堆的应用----------------

堆排序

什么是堆排序?

堆排序(Heap Sort):是一种基于二叉堆数据结构的高效排序算法,利用堆的 最大堆(大 顶/根 堆)最小堆(小 顶/根 堆) 性质,通过反复提取堆顶元素实现排序。

堆的性质

  • 大顶堆:每个父节点的值 ≥ 子节点的值 (堆顶为最大值)
  • 小顶堆:每个父节点的值 ≤ 子节点的值 (堆顶为最小值)

从上面关于大顶堆和小顶堆的性质我们可以发现:堆这种数据结构有一种(微弱的)顺序性:我们可能不知道堆中所有元素的排列顺序,但是我们一定可以确定的是堆顶元素一定就是这些元素中最大或最小的那一个。

怎么实现堆排序?

堆排序的核心就两步操作分别是:建堆排序,这两步操作会在堆排序中的重复执行。

建堆:将待排序数组构建成一个堆。

  • 从最后一个非叶子节点开始,自下而上、从右到左对每个节点进行向下调整,使其满足堆的性质。

排序:将堆顶元素与堆尾元素交换,此时堆尾元素就是当前堆中的最大(大顶堆)或最小(小顶堆)元素。

排序策略

  • 升序排序:使用大顶堆,每次将堆顶最大值交换到数组末尾。
  • 降序排序:使用小顶堆,每次将堆顶最小值交换到数组末尾。

接下来就是:

  1. 对堆顶元素所在的子树进行调整,使其重新成为一个堆
  2. 继续进行排序的操作,将堆顶元素与堆尾元素进行交换

重复这个过程,直到堆中只剩下一个元素,此时数组已经有序。

在这里插入图片描述

堆排序的效率和属性?

时间复杂度:堆排序的时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)

  • 建堆的时间复杂度为 O ( n ) O(n) O(n),排序过程中每次调整堆的时间复杂度为 O ( l o g n ) O(logn) O(logn),一共需要进行 n − 1 n - 1 n1次调整,所以总的时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)

空间复杂度:堆排序的空间复杂度为 O ( 1 ) O(1) O(1)

  • 因为它是在原数组上进行排序,不需要额外的存储空间。

稳定性:堆排序是一种 不稳定 的排序算法,在排序过程中,可能会改变相同元素的相对顺序。

  • 例如:在大顶堆中,如果有两个相同的元素,其中一个是堆顶元素,另一个是其兄弟节点,当堆顶元素与堆尾元素交换后,这两个相同元素的相对顺序就可能会改变。

--------------------------------

头文件

---------------------------------Heap.h---------------------------------#pragma once
#include <stdio.h>typedef int HPSortType;//任务1:声明堆的常用的工具函数
//1.交换数组中两个元素
//2.堆的向上调整算法(用于插入元素后维护堆的性质)
//3.堆的向下调整算法(用于删除元素后维护堆的性质)void Swap(HPSortType* a, HPSortType* b);
void AdjustUp(HPSortType* a, int child);
void AdjustDown(HPSortType* a, int parent, int n);//注意:这里我们使用的这些工具函数的形参不是:堆,;而是:动态数组
//原因:传参时传入动态数组是因为这些工具函数不仅可以用在实现“堆”这种的数据结构上,还可以继续使用在“堆排序”上//任务2:实现:“堆排序”函数
void Heapsort(HPSortType* arr, int n);   

实现文件

---------------------------------HeapSort.c---------------------------------#include "Heap.h"//1.实现:“交换两个元素的值”工具函数
void Swap(HPSortType* a, HPSortType* b)//形参是是指针类型,函数中的修改会影响到原数组中的值
{HPSortType tmp = *a;*a = *b;*b = tmp;
}//2.实现:“向上调整算法”工具函数
void AdjustUp(HPSortType* a, int child)
{//1.计算出父节点在数组中的下标(我们有孩子 ---> 得到双亲)int parent = child - 1 >> 1;//2.接下来不断的进行向上调整,何时调整结束? ---> 回答:1.当孩子已经调整成根节点时 2.当孩子不满小堆的性质时(这里我们模拟小根堆)while (child > 0){//3.使用if-else语句进行小根堆的条件检查(小根堆:谁小谁当爹)if (a[child] >= a[parent]) return;else{//4.1:交换的孩子节点和双亲节点的值Swap(&a[child], &a[parent]);//4.2:更新孩子的索引 ---> 因为我们要为孩子找到一个合适的位置child = parent;//4.3:求出现在孩子的双亲节点的索引parent = child - 1 >> 1;}}
}//3.实现:“向下调整算法”工具函数
void AdjustDown(HPSortType* a, int parent, int n)
{//1.计算出孩子节点在数组中的下标(我们有双亲节点 --->  得到“值最小的”孩子)//这里和向上调整有点不一样:找双亲节点就只有一个,但是找孩子节点有两个,要找哪一个呢?---> 找值最小的孩子//注:这里使用假设法:假设双亲的左孩子是最小的孩子int minchild = (parent << 1) + 1;//2.接下来不断地进行向下调整,何时调整结束 --->  1.当孩子节点的索引已经大于数组的容量,即:孩子不存在时 2.当孩子不满足小根堆的条件检查时while (minchild < n){//3.调整出minchild代表是最小的孩子if (minchild + 1 < n && a[minchild + 1] < a[minchild]){minchild++; //切换到右孩子}//3.使用if-else语句进行小根堆的条件检查if (a[minchild] >= a[parent]) return;else{//4.1:交换孩子节点和双亲节点的值Swap(&a[minchild], &a[parent]);//4.1:更新双亲节点的索引parent = minchild;//4.2:求出现在双亲节点的孩子节点的索引minchild = (parent << 1) + 1;}}
}//实现:“堆排序”操作
void Heapsort(HPSortType* arr, int n)
{//1.建堆阶段:(在原数组上进行建堆)//1)实现:向上调整建堆:时间复杂度O(nlogn)/*//强调:这里的i是孩子节点的索引for (int i = 0; i < n; i++){AdjustUp(arr, i);}*///2)实现:向下调整建堆:时间复杂度O(n)//强调:这里的i是双亲节点的索引for (int i = n - 1 - 1 >> 1; i >= 0; i--){AdjustDown(arr, i, n);}//2.排序阶段:(本质是删除堆的堆顶元素)//2.0:存储堆中最后一个元素的索引int end = n - 1;while (end > 0){//2.1:将堆顶元素和末尾元素进行交换Swap(&arr[0], &arr[end]);//2.2:更新数组的容量 --- 将堆的末尾的元素删除掉end--;//2.3:重新调整堆AdjustDown(arr, 0, end + 1); //注意:这里的第三个参数:是堆中元素的数量}
}

测试文件

--------------------------------Test.c--------------------------------#include "Heap.h"int main()
{//1.定义一个无序数组int arr[] = { 15,1516,14,645,3,9,65,63,4,54 };//2.调用HeapSort函数进行排序Heapsort(arr, sizeof(arr) / sizeof(int));//3.打印原来的无序数组for (int i = 0; i < sizeof(arr) / sizeof(int); i++){printf("%d ", arr[i]);}return 0;
}

运行结果

在这里插入图片描述

TOP-K问题

什么是TOP-K问题?

TOP-K 问题:是指从一个大规模数据集中快速找出前 K 个最大(或最小)的元素

解决TOP-K问题的思路和实现的步骤?

核心思路:

  1. 选择堆的类型
    • 找最大的K个元素:使用最小堆(堆顶是当前第K大的元素,堆顶为门槛值,便于快速淘汰较小值)
    • 找最小的K个元素:使用最大堆(堆顶是当前第K小的元素,堆顶为门槛值,便于快速淘汰较大值)
  2. 遍历数据并动态调整堆
    • 初始时,堆为空或部分填充。
    • 遍历每个元素时,将其与堆顶比较:
      • 若元素优于堆顶(如更大或更小),则替换堆顶并调整堆。
      • 否则,跳过该元素。
  3. 最终堆中的元素即为所求
    • 遍历结束后,堆中保存的 K 个元素即为最大或最小的 K 个元素。

实现步骤(以找最大的K个元素为例):

步骤1:初始化最小堆

  • 创建一个大小为K的最小堆
  • 若数据量小于等于 K,直接返回全部数据

步骤2:遍历数据集中的每个元素并更新堆

  • 若堆未满(元素数 < K):直接插入堆。

  • 若堆满(元素数 = K):

    • 若当前元素 > 堆顶,替换堆顶并调整堆(下沉操作)
    • 若当前元素 ≤ 堆顶,跳过该元素
  • 遍历结束后,堆中元素即为最大的 K 个元素

    • 堆顶为第 K 大元素,整个堆按小顶堆有序

使用堆解决TOP-K问题的性能怎么样?好处又是什么?

时间复杂度分析

  • 构建初始堆的时间复杂度为 O ( K ) O(K) O(K),因为最多需要对 K K K 个元素进行简单的插入操作,每个插入操作的时间复杂度为 O ( 1 ) O(1) O(1)
  • 遍历数据集中剩余的 n − K n - K nK 个元素,对于每个元素,进行一次堆顶元素的比较和可能的替换以及堆调整操作。
    • 堆调整操作的时间复杂度为 O ( l o g K ) O(logK) O(logK),因为堆的高度为 l o g K logK logK
    • 所以这部分的时间复杂度为 O ( ( n − K ) l o g K ) O((n - K)logK) O((nK)logK),近似为 O ( n l o g K ) O(nlogK) O(nlogK)
  • 总体时间复杂度为 O ( K + n l o g K ) O(K + nlogK) O(K+nlogK),由于在实际应用中 n n n 通常远大于 K K K,所以时间复杂度可以近似看作 O ( n l o g K ) O(nlogK) O(nlogK)

空间复杂度分析

使用堆解决 TOP - K 问题只需要维护一个大小为 K K K 的堆来存储当前的 TOP - K 元素,所以空间复杂度为 O ( K ) O(K) O(K)


使用堆的优点:

高效性

时间复杂度为 O ( n l o g K ) O(nlogK) O(nlogK),优于一些简单排序算法如直接排序再取前 K K K 个元素的 O ( n l o g n ) O(nlogn) O(nlogn),在处理大规模数据且 K K K 远小于 n n n 时优势显著。

内存友好

只需维护大小为 K K K 的堆,空间复杂度为 O ( K ) O(K) O(K),适合处理海量数据,可处理超出内存容量的数据,无需一次性加载全部数据。

动态适应性

数据动态变化时,新数据可方便地与堆顶比较并按需插入调整,无需重新处理整个数据集,能实时更新 TOP - K 元素。

--------------------------------

源文件

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <time.h>/****************************************************************** @brief 测试TopK算法实现(查找最大的前k个数)* @note 算法核心思想:* 1. 使用小堆维护当前最大的k个数* 2. 堆顶始终是这k个数中的最小值* 3. 遇到更大的数时替换堆顶并调整堆* 时间复杂度:O(N logK)  空间复杂度:O(K)* 适用场景:海量数据中查找极值(内存无法容纳全部数据时)*****************************************************************///任务1:实现:“交换两个元素的值”工具函数
void Swap(int* a, int* b)//形参是是指针类型,函数中的修改会影响到原数组中的值
{int tmp = *a;*a = *b;*b = tmp;
}//任务2:实现:“堆的向下调整的算法”工具函数
void AdjustDown(int* a, int parent, int n)
{//1.计算出孩子节点在数组中的下标(我们有双亲节点 --->  得到“值最小的”孩子)//这里和向上调整有点不一样:找双亲节点就只有一个,但是找孩子节点有两个,要找哪一个呢?---> 找值最小的孩子//注:这里使用假设法:假设双亲的左孩子是最小的孩子int minchild = parent << 1 + 1;//2.接下来不断地进行向下调整,何时调整结束 --->  1.当孩子节点的索引已经大于数组的容量,即:孩子不存在时 2.当孩子不满足小根堆的条件检查时while (minchild < n){//3.调整出minchild代表是最小的孩子if (minchild + 1 < n && a[minchild] > a[minchild + 1]){minchild++; //切换到右孩子}//3.使用if-else语句进行小根堆的条件检查if (a[minchild] >= a[parent]) return;else{//4.1:交换孩子节点和双亲节点的值Swap(&a[minchild], &a[parent]);//4.1:更新双亲节点的索引parent = minchild;//4.2:求出现在双亲节点的孩子节点的索引minchild = parent << 1 + 1;}}}//任务3:生成随机的测试数据文件
void CreateData()
{/*--------------------第一阶段:参数设置--------------------*///1.定义数据量的大小int n = 1e5;//2.定义数据源文件路径const char* file = "data.txt";/*--------------------第二阶段:初始化随机数发生器--------------------*/srand(time(0));/*--------------------第三阶段:打开输出文件--------------------*/FILE* fin = fopen(file, "w");//2.处理打开失败的情况if (fin == NULL){perror("file fail");return;}/*--------------------第四阶段:生成数据到文件中--------------------*///1.循环生成随机数for (int i = 0; i < n; i++){/*1. rand()生成伪随机数2. 加入循环变量i避免连续重复3. 取模限制数值范围*/int x = (rand() + i) % 10000000; //注意:这里10000000不要写成1e7的形式//2.将生成的随机数写入到文件中fprintf(fin, "%d\n", x);}/*--------------------第五阶段:资源清理--------------------*/fclose(fin); //关闭文件}//在main函数中实现topK问题的测试
int main()
{CreateData();/*--------------------第一阶段:初始化--------------------*///1.定义topK的Kint k;printf("请输入K的值\n");scanf("%d", &k);//2.动态创建堆数组(模拟容量是K的小根堆)//2.1:使用malloc开辟动态空间int* kminheap = (int*)malloc(k * sizeof(int));//2.2:处理空间开辟失败的情况if (kminheap == NULL){perror("malloc fail");return 0;}/*--------------------第二阶段:加载数据--------------------*///1.定义数据源文件路径const char* file = "data.txt";//2.将文件以只读的模式打开FILE* fout = fopen(file, "r");//3.处理文件打开失败的情况if (fout == NULL){//3.1:输出错误的信息perror("fopen fail");//3.2:释放已经分配的内存空间 + 直接进行返回free(kminheap);return 0;}//4.加载文件中的前K个数到数组for (int i = 0; i < k; i++){fscanf(fout, "%d", &kminheap[i]);}/*--------------------第三阶段:堆的构建--------------------*///使用向下调整堆进行堆的构建操作for (int i = k - 1 - 1 >> 1; i >= 0; i--){AdjustDown(kminheap, i, k);}/*--------------------第三阶段:topK筛选--------------------*///1.存储从文件中读到的数据int x = 0;//2.逐行从文件中读取剩余的数据 (直到读到文件结束符)while (fscanf(fout, "%d", &x) > 0){//3.将大于小根堆堆顶的元素添加到堆中if (x > kminheap[0]){//3.1:替换堆顶元素kminheap[0] = x;//3.2:调整堆的结构AdjustDown(kminheap, 0, k);}}/*--------------------第三阶段:输出结果--------------------*/printf("最大的前K个元素是:\n");for (int i = 0; i < k; i++){printf("%d ", kminheap[i]);}printf("\n");/*--------------------第三阶段: 资源释放--------------------*/free(kminheap); //释放堆内存fclose(fout); //关闭文件return 0;
}

运行结果

TOP-K

在这里插入图片描述

相关文章:

《数据结构初阶》【堆 + 堆排序 + TOP-K】

【堆 堆排序 TOP-K】目录 前言&#xff1a;什么是堆&#xff1f;堆的实现方式有哪些&#xff1f;我们要选择哪种方式进行实现&#xff1f; ----------------堆的实现----------------什么是向上调整算法&#xff0c;要怎么实现&#xff1f;什么是向下调整算法&#xff0c;要怎…...

sqlilab-Less-18

知识铺垫 User-Agent 首部包含了一个特征字符串&#xff0c;用来让网络协议的对端来识别发起请求的用户代理软件的应用类型、操作系统、软件开发商以及版本号。user-agent的作用。通过识别用户身份&#xff0c;响应合适的web界面&#xff0c;所以更改可以让电脑返回一个手机界…...

mapbox进阶,使用mapbox-plugins插件加载饼状图

👨‍⚕️ 主页: gis分享者 👨‍⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍⚕️ 收录于专栏:mapbox 从入门到精通 文章目录 一、🍀前言1.1 ☘️mapboxgl.Map 地图对象1.1 ☘️mapboxgl.Map style属性二、🍀使用mapbox-plugins插件加载饼状图1. ☘…...

【Java继承】——面向对象编程的基石

&#x1f381;个人主页&#xff1a;User_芊芊君子 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 &#x1f50d;系列专栏&#xff1a;【Java】内容概括 【前言】 在Java面向对象编程中&#xff0c;继承是一个非常重要的概念。它允许我们创建一个新类&…...

【数据结构】——队列

一、队列的概念和结构 概念&#xff1a; 只允许在⼀端进⾏插⼊数据操作&#xff0c;在另⼀端进⾏删除数据操作的特殊线性表&#xff0c;队列具有先进先 出FIFO(First In First Out)。 入队&#xff1a;进行数据插入的一端叫做队尾 出队&#xff1a;进行删除操作的一端叫做队…...

如何找出所有不重复的三位偶数:详细解法与思考过程

问题描述 给定一个包含数字&#xff08;0-9&#xff09;的数组digits&#xff0c;其中可能包含重复元素。我们需要找出所有满足以下条件且互不相同的整数&#xff1a; 该整数由digits中的三个元素按任意顺序依次连接组成 该整数不含前导零&#xff08;即必须是100-999之间的数…...

每日Prompt:超现实交互场景

提示词 一幅铅笔素描画&#xff0c;描绘了 一个女孩 与 一朵玫瑰 互动的场景&#xff0c;其中 一朵玫瑰 以逼真的全彩风格呈现&#xff0c;与 一个女孩及背景的手绘素描风格形成超现实的对比。...

基于大模型预测的多发性硬化综合诊疗方案研究报告大纲

目录 一、引言二、文献综述三、大模型预测系统构建四、术前预测与手术方案制定五、术中监测与决策支持六、术后护理与并发症预测七、麻醉方案智能优化八、统计分析与技术验证九、实验验证与证据支持十、健康教育与指导系统十一、结论与展望一、引言 (一)研究背景与意义 多发…...

五、Hive表类型、分区及数据加载

在 Hive 中高效构建、管理和查询数据仓库&#xff0c;核心在于精准运用表类型&#xff08;内部/外部&#xff09;与分区策略&#xff08;静态/动态/多重&#xff09;。这不仅决定数据的生命周期归属&#xff0c;更是优化海量数据查询性能的关键手段。 一、表的身份权责&#x…...

在选择合适的实验室铁地板和铸铁试验平板,帮分析​

铸铁测试底板是一种采用铸铁材料经过加工制成的基准测量工具&#xff0c;主要用于工业检测、机械加工和实验室等高精度要求的场合。其核心功能是为各类测量、检验、装配工作提供稳定的水平基准面&#xff0c;确保测量数据的准确性和一致性。 一、铸铁测试底板的基本特性 1.材质…...

阿里云人工智能大模型通义千问Qwen3开发部署

本文主要描述阿里云人工智能大模型开源社区ModelScope提供的通义千问Qwen3开发部署。 与阿里云一起 轻松实现数智化 让算力成为公共服务&#xff1a;用大规模的通用计算&#xff0c;帮助客户做从前不能做的事情&#xff0c;做从前做不到的规模。让数据成为生产资料&#xff1a;…...

网络基础知识梳理和Muduo库使用

文章目录 网络基础知识梳理和Muduo库使用1.知识储备2.阻塞、非阻塞、同步、异步我的总结 3.Unix/Linux上的五种IO模型0.铺垫1.阻塞IO&#xff08;blocking&#xff09;2.非阻塞IO&#xff08;non-blocking&#xff09;3.IO复用&#xff08;IO multiplexing&#xff09;4.信号驱…...

IDEA 插件推荐:提升编程效率

通过安装和使用合适的插件&#xff0c;可以大幅提升开发效率和代码质量。本文将从多个维度推荐实用的 IDEA 插件&#xff0c;并提供安装与使用指南。 一、代码辅助类插件 1. Key Promoter X —— 快捷键学习利器 功能介绍&#xff1a;当你使用鼠标点击某个功能时&#xff0c;…...

001大模型-认识大模型以及大模型应用场景

大模型是一种基于海量数据训练的人工智能系统&#xff0c;具备强大的语言理解和生成能力。其工作原理是通过深度学习算法&#xff0c;分析大量文本数据&#xff0c;学习语言模式和知识&#xff0c;从而能够处理复杂的任务。大模型的应用广泛&#xff0c;包括自然语言处理、机器…...

Qt进阶开发:QTcpServer的的详解

文章目录 一、QTcpServer 简介二、常用成员函数的使用三、信号函数的使用四、虚函数的使用五、连接多客户端-服务端示例一、QTcpServer 简介 QTcpServer 是 Qt 网络模块中的一个核心类,用于实现 基于 TCP 协议的服务端(Server),它负责监听端口、接收客户端连接请求,并通过…...

Spark,集群搭建之Yarn模式

以下是Spark基于Yarn模式的集群搭建关键步骤&#xff08;需先部署Hadoop Yarn集群&#xff09;&#xff1a; 一、环境准备 1. 确认Hadoop已运行 - 确保HDFS、Yarn ResourceManager和NodeManager正常启动。 2. 安装Java - 所有节点安装JDK 8&#xff0c;配置 JAVA_HOME 环境变量…...

fiddler 配置ios手机代理调试

平时做移动移动端开必的时候经常需要抓包手机&#xff0c;用于接口请求跟踪&#xff0c;但iOS的抓包经常性的配不成功&#xff0c;经过踩过不少坑后终于知道了整个配置流程&#xff0c;此文记录Fiddler抓包iOS手机的配置流程。 Step 1&#xff1a;Fiddler配置 通过工具栏Tool…...

iOS即时通信的技术要点

iOS即时通信开发的关键技术要点总结&#xff1a; 一、通讯协议选择 Socket通信 基础实现&#xff1a;使用原生BSD Socket或CFNetwork框架&#xff08;复杂&#xff09;&#xff0c;推荐第三方库如CocoaAsyncSocket&#xff08;封装GCDAsyncSocket&#xff09;&#xff0c;简化T…...

Windows 安装 Milvus

说明 操作系统&#xff1a;Window 中间件&#xff1a;docker desktop Milvus&#xff1a;Milvus Standalone&#xff08;单机版&#xff09; 安装 docker desktop 参考&#xff1a;Window、CentOs、Ubuntu 安装 docker-CSDN博客 安装 Milvus 参考链接&#xff1a;Run Mil…...

5G网络:能源管理的“智能电网“革命,Python如何成为关键推手?

5G网络&#xff1a;能源管理的"智能电网"革命&#xff0c;Python如何成为关键推手&#xff1f; 大家好&#xff0c;我是Echo_Wish。今天咱们聊一个既硬核又接地气的话题——5G网络如何用Python代码重构全球能源管理。 不知道你们有没有注意过&#xff1a; • 家里装…...

解决WSL、Ubuntu的.ico图标不正确显示缩略图

解决WSL、Ubuntu的.ico图标不正确显示缩略图 问题描述 Win10系统中由于更新了某些软件&#xff0c;篡改了默认的图像显示软件&#xff0c;导致WSL等软件未能成功显示图标&#xff0c;表现如下&#xff1a; 解决方法 将ico文件的默认打开方式更改为“画图”&#xff0c;如下…...

Redis+Caffeine构造多级缓存

一、背景 项目中对性能要求极高&#xff0c;因此使用多级缓存&#xff0c;最终方案决定是RedisCaffeine。其中Redis作为二级缓存&#xff0c;Caffeine作为一级本地缓存。 二、Caffeine简单介绍 Caffeine是一款基于Java 8的高性能、灵活的本地缓存库。它提供了近乎最佳的命中…...

Redis+Caffeine构建高性能二级缓存

大家好&#xff0c;我是摘星。今天为大家带来的是RedisCaffeine构建高性能二级缓存&#xff0c;废话不多说直接开始~ 目录 二级缓存架构的技术背景 1. 基础缓存架构 2. 架构演进动因 3. 二级缓存解决方案 为什么选择本地缓存&#xff1f; 1. 极速访问 2. 减少网络IO 3…...

【机器人】复现 UniGoal 具身导航 | 通用零样本目标导航 CVPR 2025

UniGoal的提出了一个通用的零样本目标导航框架&#xff0c;能够统一处理多种类型的导航任务。 支持 对象类别导航、实例图像目标导航和文本目标导航&#xff0c;而无需针对特定任务进行训练或微调。 本文分享UniGoal复现和模型推理的过程&#xff5e; 查找沙发&#xff0c;模…...

LeetCode 373 查找和最小的 K 对数字题解

LeetCode 373 查找和最小的 K 对数字题解 题目描述 给定两个以升序排列的整数数组 nums1 和 nums2&#xff0c;以及一个整数 k。定义一对值 (u,v)&#xff0c;其中第一个元素来自 nums1&#xff0c;第二个元素来自 nums2。请找到和最小的 k 个数对。 解题思路 最小堆优化法…...

搜索二维矩阵 II 算法讲解

搜索二维矩阵 II 算法讲解 一、问题描述 给定一个 m x n 的二维矩阵 matrix ,需要在其中搜索一个目标值 target 。该矩阵具有以下特性: 每行的元素从左到右升序排列。每列的元素从上到下升序排列。要求编写一个高效的算法来完成搜索任务。 二、解题思路 方法一:暴力枚举 …...

三层交换机,单臂路由(用DHCP自动配置ip+互通+ACL

三层交换机&#xff0c;单臂路由&#xff08;用DHCP自动配置ip互通ACL 任务 1.用DHCP自动配置ip 2.三层交换机SVI、 3.单臂路由 4.互通 5.ACL三层交换机SVI 交换机 Switch>en Switch#conf t Enter configuration commands, one per line. End with CNTL/Z. Switch(conf…...

OpenCV CUDA 模块中在 GPU 上对图像或矩阵进行 翻转(镜像)操作的一个函数 flip()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 cv::cuda::flip 是 OpenCV 的 CUDA 模块中的一个函数&#xff0c;用于在 GPU 上对图像或矩阵进行 翻转&#xff08;镜像&#xff09;操作。它类似…...

链表面试题7之相交链表

来了来了&#xff0c;这道题才是值得我们奇思妙想的题,链接在下面。 160. 相交链表 - 力扣&#xff08;LeetCode&#xff09; 看完题目一脸懵吗&#xff0c;没关系&#xff0c;我们还得看示例 还是一脸懵怎么办&#xff1f;&#xff1f; 两个链表相交的方式有几种&#xff1f;…...

Excel-to-JSON插件专业版功能详解:让Excel数据转换更灵活

前言 在数据处理和系统集成过程中&#xff0c;Excel和JSON格式的转换是一个常见需求。Excel-to-JSON插件提供了一套强大的专业版功能&#xff0c;能够满足各种复杂的数据转换场景。本文将详细介绍这些专业版功能的应用场景和使用方法。 订阅说明 在介绍具体功能之前&#xf…...

【C++】”如虎添翼“:模板初阶

泛型编程&#xff1a; C中一种使用模板来实现代码重用和类型安全的编程范式。它允许程序员编写与数据类型无关的代码&#xff0c;从而可以用相同的代码逻辑处理不同的数据类型。模板是泛型编程的基础 模板分为两类&#xff1a; 函数模板&#xff1a;代表了一个函数家族&#x…...

【K8S学习之探针】详细了解就绪探针 readinessProbe 和存活探针 livenessProbe 的配置

参考 Pod健康检查 Kubernetes 学习笔记Kubernetes 就绪探针&#xff08;Readiness Probe&#xff09; - 人艰不拆_zmc - 博客园Kubernetes存活探针&#xff08;Liveness Probe&#xff09; - 人艰不拆_zmc - 博客园 Pod健康检查 Pod的健康状态由两类探针来检查&#xff1a;…...

WSL 安装 Debian 12 后,Linux 如何安装 redis ?

在 WSL 的 Debian 12 上安装 Redis 的步骤如下&#xff1a; 1. 更新系统包列表 sudo apt update && sudo apt upgrade -y2. 安装 Redis sudo apt install redis-server -y3. 启动 Redis 服务 sudo systemctl start redis4. 设置开机自启 sudo systemctl enable red…...

python打卡day22

复习日 仔细回顾一下之前21天的内容&#xff0c;没跟上进度的同学补一下进度。 作业&#xff1a; 自行学习参考如何使用kaggle平台&#xff0c;写下使用注意点&#xff0c;并对下述比赛提交代码 kaggle泰坦里克号人员生还预测 就是很简单很草率地走了一遍机器学习的经典流程&am…...

国产化Excel处理控件Spire.XLS系列教程:如何通过 C# 删除 Excel 工作表中的筛选器

在 Excel 文件中&#xff0c;筛选器&#xff08;Filter&#xff09;是一个常用的数据处理工具&#xff0c;可以帮助用户快速按条件筛选数据行。但在数据整理完成、导出、共享或打印之前&#xff0c;往往需要 删除 Excel 工作表中的筛选器&#xff0c;移除列标题中的下拉筛选按钮…...

使用 DMM 测试 TDR

TDR&#xff08;时域反射计&#xff09;可能是实验室中上升时间最快的仪器&#xff0c;但您可以使用直流欧姆表测试其准确性。 TDR 测量什么 在所有高速通道中&#xff0c;反射都很糟糕。我们尝试设计一个通道来减少反射&#xff0c;这些反射都会导致符号间干扰 &#xff08;…...

基于卡尔曼滤波的传感器融合技术的多传感器融合技术(附战场环境模拟可视化代码及应用说明)

基于卡尔曼滤波的传感器融合技术的多传感器融合技术(附战场环境模拟可视化代码及应用说明) 1 目标运动状态空间建模1.1 状态向量定义1.2 状态转移方程1.3 观测模型构建2 卡尔曼滤波核心算法实现2.1 初始化2.2 预测步骤2.3 更新步骤3 多传感器融合仿真验证3.1 传感器模型模拟3…...

M8040A/M8199助力数据中心收发信机测试

随着数字通信和大数据的不断发展&#xff0c;误码率测试变得越来越重要。高性能误码率测试仪作为一种关键的测试设备&#xff0c;可以对数字信号进行高速、高精度的误码率测试&#xff0c;广泛应用于通信、数据中心、半导体等行业。 M8040A误码仪系统当前不仅在上游IP和顶层芯…...

傲云源墅:以五傲价值重构北京主城别墅格局

在高端别墅市场中&#xff0c;产品自身的品质与特色是吸引客户的关键。北京傲云源墅以其独特的 “五傲” 价值&#xff0c;重新定义了北京主城别墅的标准。 首先是低密之傲&#xff0c;傲云源墅的容积率低至约 0.9&#xff0c;与市场上 1.0 以上容积率的别墅相比&#xff0c;为…...

精益数据分析(56/126):创业阶段的划分与精益数据分析实践

精益数据分析&#xff08;56/126&#xff09;&#xff1a;创业阶段的划分与精益数据分析实践 在创业和数据分析的探索之旅中&#xff0c;理解创业阶段的划分以及与之对应的精益数据分析方法至关重要。今天&#xff0c;依旧怀揣着与大家共同进步的心态&#xff0c;深入研读《精…...

[redis进阶六]详解redis作为缓存分布式锁

目录 一 什么是缓存 缓存总结板书: 二 使⽤Redis作为缓存 三 缓存的更新策略 1) 定期⽣成 2) 实时⽣成 四 面试重点:缓存预热,缓存穿透,缓存雪崩 和缓存击穿 1)缓存预热 2)缓存穿透 3)缓存雪崩 4)缓存击穿 五 分布式锁 板书: 1)什么是分布式锁 2)分布式锁的基…...

【RabbitMQ】应用问题、仲裁队列(Raft算法)和HAProxy负载均衡

&#x1f525;个人主页&#xff1a; 中草药 &#x1f525;专栏&#xff1a;【中间件】企业级中间件剖析 一、幂等性保障 什么是幂等性&#xff1f; 幂等性是指对一个系统进行重复调用&#xff08;相同参数&#xff09;&#xff0c;无论同一操作执行多少次&#xff0c;这些请求…...

国产密码新时代!华测国密 SSL 证书解锁安全新高度

在数字安全被提升到国家战略高度的今天&#xff0c;国产密码算法成为筑牢网络安全防线的关键力量。华测国密SSL证书凭借其强大性能与贴心服务&#xff0c;为企业网络安全保驾护航&#xff0c;成为符合国家安全要求的不二之选&#xff01;​ 智能兼容&#xff0c;告别浏览器适配…...

【Linux篇章】Linux 进程信号2:解锁系统高效运作的 “隐藏指令”,开启性能飞跃新征程(精讲捕捉信号及OS运行机制)

本篇文章将以一个小白视角&#xff0c;通俗易懂带你了解信号在产生&#xff0c;保存之后如何进行捕捉&#xff1b;以及在信号这个话题中&#xff1b;OS扮演的角色及背后是如何进行操作的&#xff1b;如何理解用户态内核态&#xff1b;还有一些可以引出的其他知识点&#xff1b;…...

C# 基础 try-catch代码块

​ try-catch代码块是C#中用于异常处理的核心机制。异常是在程序执行过程中可能出现的错误&#xff0c;而try-catch代码块允许您在执行代码时捕获并处理这些异常。 一、基础结构 try {//可能抛出异常的代码 } catch (ArgumentException ex) {//处理特定异常 } catch (Excepti…...

为什么 mac os .bashrc 没有自动加载?

原因说明 在macOS中&#xff0c;默认情况下&#xff0c;终端使用的是Bash或Zsh作为shell。对于较新版本的macOS&#xff08;从Catalina开始&#xff09;&#xff0c;默认的shell已经切换为Zsh。因此&#xff0c;如果你正在使用Zsh&#xff0c;.bashrc文件不会自动生效&#xf…...

【HarmonyOS Next之旅】DevEco Studio使用指南(二十二)

目录 1 -> 开发静态共享包 1.1 -> 创建库模块 1.2 -> 编译库模块 2 -> 开发动态共享包 2.1 -> 使用约束 2.2 -> 开发动态共享包 2.2.1 -> 创建HSP模块 2.2.2 -> 编译HSP模块 3 -> 发布共享包 1 -> 开发静态共享包 HAR(Harmony Archive…...

QT6.8安装教程

官网下载 链接&#xff1a; Index of /official_releases/online_installers 这个比较慢 建议去 清华大学开源软件镜像站&#xff1a;Index of /qt/archive/online_installers/4.9/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror 根据自己什么系统选择 点击打开…...

【Rust泛型】Rust泛型使用详解与应用场景

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…...

一周学完计算机网络之三:1、数据链路层概述

简单的概述 数据链路层是计算机网络体系结构中的第二层&#xff0c;它在物理层提供的基本服务基础上&#xff0c;负责将数据从一个节点可靠地传输到相邻节点。可以将其想象成一个负责在两个相邻的网络设备之间进行数据 “搬运” 和 “整理” 的 “快递中转站”。 几个重要概念…...