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

郑州做网站找赢博科技定制app开发

郑州做网站找赢博科技,定制app开发,创意设计字体,wordpress重新配置ftp目录 第一步:重新思考“队列”——优先级是关键 第二步:探索实现方式(发现问题,寻找更优解) 方案A:使用无序数组或链表 方案B:使用有序数组或链表 寻求平衡:是否存在更好的方案…

目录

第一步:重新思考“队列”——优先级是关键

第二步:探索实现方式(发现问题,寻找更优解)

方案A:使用无序数组或链表

方案B:使用有序数组或链表

寻求平衡:是否存在更好的方案?

第三步:终极武器——堆 (Heap)

1. 堆的结构——“完全二叉树”

2. 堆的属性——“堆序”

第四步:一步步实现堆的操作

1. 优先队列的蓝图

2. 插入操作 (Insert)

3. 提取最大值操作 (Extract-Max)

总结


第一步:重新思考“队列”——优先级是关键

我们之前讨论的队列是“先进先出”(FIFO),所有元素一视同仁,唯一的评判标准是“来得早晚”。

但在现实世界中,很多情况并非如此:

  • 医院急诊室:医生不会按挂号顺序看病,而是优先处理生命垂危的病人。

  • 操作系统:会优先执行系统级的关键任务,而不是用户打开的计算器。

  • 打印任务:你可能希望先打印1页的文档,而不是排在它前面的500页的报告。

这些场景的共同点是:

每个元素都有一个“优先级”,我们总是需要先处理优先级最高的那个元素。

这就是优先队列的核心思想。它不再是 FIFO,而是 “最高优先级先出” (Highest Priority Out)

它的基本操作是:

  1. 插入 (Insert):将一个带优先级的元素放入队列。

  2. 提取最大/最小 (Extract-Max / Extract-Min):从队列中取出并移除优先级最高的元素。


第二步:探索实现方式(发现问题,寻找更优解)

我们如何实现这个“最高优先级先出”的结构呢?让我们用已知的数据结构来试试。假设数字越大,优先级越高。

方案A:使用无序数组或链表

最简单的想法,直接把元素存入一个数组或链表。

  • 插入 (Insert):非常简单,直接在数组末尾或链表尾部添加新元素。时间复杂度是 O(1)

  • 提取最大值 (Extract-Max)

因为元素是无序的,为了找到优先级最高的元素,我们必须遍历整个数组/链表,比较所有元素,找到最大值。时间复杂度是 O(N)

这个方案“入队”很快,“出队”很慢。

如果“出队”操作非常频繁(比如操作系统调度),那每次都遍历一遍是无法接受的。此方案否决。

方案B:使用有序数组或链表

为了让“出队”变快,我们可以时刻保持队列中的元素按优先级有序。

由于元素总是有序的,优先级最高的元素永远在固定的一端(比如数组的末尾)。我们直接取出即可。时间复杂度是 O(1)

  • 提取最大值 (Extract-Max)

  • 插入 (Insert):为了维持有序性,当一个新元素到来时,我们必须找到它在序列中正确的位置,然后把它插入进去。

    • 对于数组,找到位置(例如用二分查找 O(log N)),但插入需要把后面的所有元素都向后移动一位,这个移动操作是 O(N)

    • 对于链表,需要遍历找到插入点,也是 O(N)

这个方案“出队”很快,“入队”很慢。

如果“入队”操作非常频繁(比如日志系统收到大量不同优先级的日志),那每次都为了插入而移动大量元素也是无法接受的。此方案亦否决。

寻求平衡:是否存在更好的方案?

我们发现,方案A和B是两个极端:

一个牺牲了出队效率,另一个牺牲了入队效率。是否存在一种“中庸之道”,能让入队和出队都比较快,比如都是 O(log N)?

答案是肯定的。我们需要一个数据结构,它:

  1. 查找最大值很快,最好是 O(1)。

  2. 在插入或删除后,调整结构的速度比 O(N) 快得多。

这个完美的数据结构,就是 “堆” (Heap)


第三步:终极武器——堆 (Heap)

“堆”是一种非常巧妙的、基于树的结构,它专门为优先队列而生。

1. 堆的结构——“完全二叉树”

首先,堆在逻辑上是一棵完全二叉树 (Complete Binary Tree)

“完全”意味着:除了最底层,其他层都是满的,并且最底层的节点都尽可能地靠左排列。

这个结构有一个巨大的优势:

可以用一个普通的数组来高效地表示,而不需要任何指针!

假设一个节点在数组中的下标是 i

  • 它的父节点下标是:floor((i - 1) / 2)

  • 它的左子节点下标是:2 * i + 1

  • 它的右子节点下标是:2 * i + 2 这个映射关系是堆实现的基础。

2. 堆的属性——“堆序”

光有结构还不够,堆还有一个核心规则,称为堆序属性 (Heap Property)。分为两种:

任何一个父节点的值,都大于或等于它的所有子节点的值。

这条规则的直接推论是:堆顶(数组的第0个元素)永远是整个堆中的最大值。

  • 最大堆 (Max-Heap)

  • 最小堆 (Min-Heap):任何一个父节点的值,都小于或等于它的所有子节点的值。

堆只保证“父子”关系,不保证“兄弟”关系。它是一种“部分有序”,而不是“完全有序”,正是这种“不完全”带来了效率的提升。

现在,我们的目标明确了:用数组实现一个最大堆,来作为优先队列的底层结构。


第四步:一步步实现堆的操作

1. 优先队列的蓝图

我们需要一个数组来存数据,一个变量记录容量,一个变量记录当前堆中有多少个元素。

【代码实现 1:结构体定义】

#include <stdio.h>
#include <stdlib.h>typedef struct {int* data;     // 指向数据数组int capacity;  // 数组的总容量int size;      // 当前堆中的元素数量
} PriorityQueue;// 为了方便,写一个交换函数
void swap(int* a, int* b) {int temp = *a;*a = *b;*b = temp;
}

2. 插入操作 (Insert)

当一个新元素要插入时,我们如何维护堆的结构和属性?

  1. 结构维护:为了保持完全二叉树的结构,我们先把新元素放在数组的最后(即 data[size] 的位置),然后 size 加一。

  2. 属性恢复:新加进来的元素可能会破坏最大堆的属性(比如一个很大的数被放在了叶子节点)。我们需要让这个新元素“上浮” (Sift Up / Heapify Up) 到它正确的位置。

“上浮”的过程

  • 将新元素和它的父节点比较。

  • 如果它比父节点大,就和父节点交换位置。

  • 重复这个过程,不断地与新的父节点比较、交换,直到它不再比父节点大,或者它已经到达了堆顶。

【代码实现 2:插入与上浮】

// "上浮"操作的实现
// heap: 堆的指针
// index: 需要上浮的节点的起始下标
void heapifyUp(PriorityQueue* pq, int index) {// 如果节点已经在堆顶,或者它的值不比父节点大,就停止// 父节点下标: (index - 1) / 2while (index > 0 && pq->data[index] > pq->data[(index - 1) / 2]) {// 和父节点交换swap(&pq->data[index], &pq->data[(index - 1) / 2]);// 更新当前节点的下标为它父节点的下标,继续向上检查index = (index - 1) / 2;}
}// 插入操作
void insert(PriorityQueue* pq, int value) {if (pq->size >= pq->capacity) {printf("插入失败:优先队列已满。\n");return;}// 1. 将新元素放在数组末尾pq->data[pq->size] = value;// 2. 对这个新元素执行“上浮”操作heapifyUp(pq, pq->size);// 3. 堆的元素数量加一pq->size++;
}

3. 提取最大值操作 (Extract-Max)

如何取出并移除最大值,同时维持堆的结构和属性?

  1. 获取最大值:最大值永远在堆顶 (data[0]),我们先把它取出来。

  2. 结构维护:堆顶空了,形成一个“窟窿”。为了维持完全二叉树,我们用一个元素来填补它。用哪个?用数组的最后一个元素。我们把它挪到堆顶,然后 size 减一。

  3. 属性恢复:这个从末尾挪上来的元素几乎肯定会破坏最大堆属性(因为它一般很小)。我们需要让这个新堆顶“下沉” (Sift Down / Heapify Down) 到它正确的位置。

“下沉”的过程

  • 将当前节点和它的左右子节点比较。

  • 找出它两个子节点中值较大的那个。

  • 如果当前节点比它较大的子节点还要小,就和那个较大的子节点交换位置。

  • 重复这个过程,直到它的值大于它所有的子节点,或者它已经到达了叶子节点。

【代码实现 3:提取最大值与下沉】

// "下沉"操作的实现
// index: 需要下沉的节点的起始下标
void heapifyDown(PriorityQueue* pq, int index) {int maxIndex = index;while (1) {int leftChild = 2 * index + 1;int rightChild = 2 * index + 2;// 找出当前节点和它的左右孩子中,值最大的那个节点的下标if (leftChild < pq->size && pq->data[leftChild] > pq->data[maxIndex]) {maxIndex = leftChild;}if (rightChild < pq->size && pq->data[rightChild] > pq->data[maxIndex]) {maxIndex = rightChild;}// 如果值最大的节点就是当前节点自己,说明它已经到了正确的位置,下沉结束if (maxIndex == index) {break;}// 否则,和值更大的子节点交换,并继续向下检查swap(&pq->data[index], &pq->data[maxIndex]);index = maxIndex;}
}// 提取最大值操作
int extractMax(PriorityQueue* pq) {if (pq->size <= 0) {fprintf(stderr, "提取失败:优先队列为空。\n");return -1; // 错误值}// 1. 堆顶就是最大值int maxValue = pq->data[0];// 2. 将最后一个元素挪到堆顶pq->data[0] = pq->data[pq->size - 1];pq->size--;// 3. 对新的堆顶执行“下沉”操作if (pq->size > 0) {heapifyDown(pq, 0);}return maxValue;
}

总结

实现方式插入操作复杂度提取最大值复杂度适用性
无序数组O(1)O(N)入队远多于出队
有序数组O(N)O(1)出队远多于入队
堆 (Heap)O(log N)O(log N)完美平衡,适用各种场景

通过一步步推导,我们发现,简单的数组和链表都无法同时高效地支持优先队列的两个核心操作。为了寻求平衡,我们引入了“堆”这个专门为此设计的结构。

它通过维护“完全二叉树”的结构和“最大/最小堆”的属性,巧妙地利用“上浮”和“下沉”操作,在 O(log N) 的时间内完成了插入和删除,是实现优先队列最正统、最高效的方式。

http://www.dtcms.com/a/540757.html

相关文章:

  • 使用Docker安装Jenkins:完整指南与最佳实践
  • 手写一个C++字符串类:从底层理解String的实现
  • 大学学院教授委员会制度研究(四)职能设置--杨立恒毕业论文
  • Docker 命令自动补全:临时与持久化配置指南
  • 简单使用Nest+Nacos+Kafka实现微服务
  • 了解学习Redis主从复制
  • 【含文档+PPT+源码】基于java web的篮球馆管理系统系统的设计与实现
  • 眉山建设银行官方网站html5的网站设计与实现是做什么
  • 【音视频】图像与音频的3A技术:ISP相机与音频3A算法的对比
  • 字节码的“字节”含义
  • 做天然文化石的网站锦州网站建设多少钱
  • HarmonyOS实战项目:打造智能家居控制中心(设备发现与控制)
  • Linux存储软件栈剖析之第5篇:F2FS文件系统
  • iis7 网站权限设置chromeseo是什么
  • 新网站建设服务在线crm视频在线crm
  • MongoDB入门指南基础篇
  • 【洛谷】高精度专题 加减乘除全实现
  • 6.1.1.1 大数据方法论与实践指南-Spark/Flink 任务开发规范
  • _金仓数据库平替MongoDB实战:制造业生产进度管理的国产化升级之路
  • java-learn(8):拼图小游戏
  • 建设银行 福建分行招聘网站山西城乡建设厅网站首页
  • STM32学习(MCU控制)(SysTick and TIM)
  • 【高并发服务器】十一、Acceptor监听套接字模块 LoopThreadPool线程池模块
  • uniapp vue3 点击跳转外部网页
  • 基于“开源AI智能名片链动2+1模式S2B2C商城小程序”的会员制培养策略研究
  • 做家居网站设计o2o型网站
  • IDEA推送github,身份认证错误:Cannot assign requested address: getsockopt 解决方法
  • Rust Actix-Web框架源码解析:基于Actor模型的高性能Web开发
  • LLM辅助轻量级MES编排系统低代码开发方案介绍
  • 网站国际网络备案号百度收录提交之后如何让网站更快的展示出来