【算法】贪心+堆排序实现大根堆及标准库容器类的融合使用

📢博客主页:https://blog.csdn.net/2301_779549673
📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!
📢本文由 JohnKi 原创,首发于 CSDN🙉
📢未来很长,值得我们全力奔赴更美好的生活✨

在这里插入图片描述

在这里插入图片描述

文章目录

  • 📢前言
  • 🏳️‍🌈一、什么是贪心
  • 🏳️‍🌈二、大根堆算法概述
  • 🏳️‍🌈三、深入剖析大根堆实现细节
  • 🏳️‍🌈四、标准库容器类的融合使用
    • ❤️一、vector 与 set
    • 🧡二、vector与map
    • 💛三、list与queue
    • 💚四、deque与stack
    • 💙
  • 👥总结


📢前言

先看题
在这里插入图片描述

这个算法,是要通过循环找到所有元素中最大的那个偶元素,对其除以2,然后再找这之后的最大的那个偶元素以此类推

笔者的经历不多,第一次做的时候没有想到 大根堆 的算法,只是用 sort 将每次除以2后的整个数组进行降序排序,这样的作法

  • 使用 sort 对数组进行排序后,虽然可以确定数组中的元素大小顺序,但在每次选择偶数进行减半操作时,需要遍历整个数组来找到合适的偶数,效率较低。
  • 对于一个较大的数组,排序后可能需要花费较多的时间来找到下一个要进行减半操作的偶数,尤其是当操作次数较多时,这种遍历的方式会变得非常耗时。

这不符合贪心的要求,但使用 大根堆 的方法,在进行操作的过程中,能够实时地根据当前数组的状态进行调整。每次操作后,只需要对受影响的元素进行堆的调整操作,时间复杂度相对较低。

因此这题代码解析如下

#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>
#include<queue>using namespace std;typedef long long LL;int main()
{int n, k;cin >> n >> k;priority_queue<LL> heap; // 优先队列,可以理解为大根堆LL tmp;LL ans = 0;for (int i = 0; i < n; i++){cin >> tmp;ans += tmp;if (tmp % 2 == 0)heap.push(tmp);}while (k-- && !heap.empty()){LL t = heap.top() / 2;ans -= t;heap.pop();if (t % 2 == 0)	heap.push(t);}printf("%lld", ans);return 0;
}

🏳️‍🌈一、什么是贪心

贪心算法是一种在每一步选择中都采取当前状态下的最优决策的算法设计策略。

贪心算法有以下几个特点:

一、局部最优选择

在每一步决策时,贪心算法总是选择当前看起来最优的选项,而不考虑整体的最优解是否一定能够通过这些局部最优选择得到。这种局部最优选择是基于某种特定的贪心策略,例如选择当前价值最大的物品、选择距离最近的节点等。

例如,在找零钱问题中,如果要使用最少的硬币找零,贪心策略可能是每次选择面值尽可能大的硬币。比如要找零 63 元,有 1 元、5 元、10 元、20 元、50 元的硬币,贪心算法会先选择一个 50 元硬币,然后再选择一个 10 元硬币,接着选择一个 1 元、1 元、1 元硬币,共使用了五枚硬币。

二、不一定得到全局最优解

虽然贪心算法在很多情况下能够快速得到一个可行解,但它不能保证一定能得到全局最优解。这是因为贪心算法只考虑了局部最优,而没有考虑到所有可能的情况。

例如,在背包问题中,如果物品可以分割,使用贪心算法按照物品的单位价值(价值/重量)从大到小选择物品放入背包,可能会得到一个接近最优解的结果,但不一定是全局最优解。而对于 0-1 背包问题(物品不可分割),贪心算法通常不能得到最优解。

三、适用性和高效性

贪心算法适用于一些具有特定性质的问题,这些问题通常具有贪心选择性质和最优子结构性质。贪心选择性质是指通过局部最优选择可以得到全局最优解的一个必要条件;最优子结构性质是指问题的最优解可以由子问题的最优解组合而成。

贪心算法通常比较简单直观,实现起来相对容易,并且在很多情况下具有较高的效率。因为它不需要进行复杂的搜索和回溯,只需要在每一步做出一个局部最优选择即可。

总的来说,贪心算法是一种在特定问题中能够快速得到可行解的算法策略,但需要谨慎使用,并且在使用时需要分析问题是否满足贪心选择性质和最优子结构性质,以确保得到的解是合理的。

🏳️‍🌈二、大根堆算法概述

(一)贪心算法与大根堆
贪心算法在大根堆构建中有着巧妙的运用。以力扣中的一些问题为例,比如在解决可达到最远建筑问题时,贪心算法通过局部最优解的选择,逐步逼近全局最优解。在大根堆 + 贪心算法的场景中,如神偷 Jacky 在楼顶穿梭的问题,当遇到下一个楼顶大于当前楼顶爬不上去的时候,优先选择丢砖头,保留梯子以备更需要的时候使用。这体现了贪心算法在大根堆构建中的思想,即每次选择当前最优的决策,以达到最终的目标。

(二)队列结合大根堆
队列可以与大根堆结合,实现优先级队列等功能。例如在手机上玩游戏的时候,如果有来电,系统应该优先处理打进来的电话,这就引入了优先级队列这种数据结构。例如在解决最优的方式安排会议问题时,按照会议结束时间建立比较器,将会议放入小顶堆;循环所有会议,符合限制开始时间时结果加一,然后限制开始时间后移到当前会议的结束时间。

(三)堆排序实现大根堆
堆排序在构建大根堆过程中有着重要的作用。大根堆就是对于每一个元素,它的值大于等于它的孩子节点。大根堆保存在一个数组中,第 0 个元素作为堆的根。堆排序主要分为两步:

  • 构建大根堆和调整大根堆。构建大根堆时我们是自底向上的进行比较,使得每次构建的堆都是大根堆;
  • 当构建完毕后,每次则是自顶向下的进行比较,保证形成新的大根堆。

例如在对一个无序数列进行递增排序时,先按照完全二叉树的层序顺序,插入叶子结点,然后比较该叶子结点与其父结点,如果比父结点大,则与父结点交换,此时可以保证当前的子树是大根堆。重复这个过程,直到所有数都添加至这个树中,完成大根堆的构建。接着进行调整大根堆的操作,将根结点与树中最后一个结点进行交换,并将这个最大值剔除这个树,对剩下的数进行调整,自顶向下的进行判断,如果根结点比两个孩子结点的最大值要小,则把最大值与根结点交换,重复这个过程,直到这个树为空,此时我们可以得到一个有序的数列。

🏳️‍🌈三、深入剖析大根堆实现细节

在考试时我们可以直接使用库函数 queue 中的 priority_queue 优先队列来实现大根堆,但是因为我们要充分理解大根堆的实现方式我们

  1. 堆的概念:堆是一颗完全二叉树,若根节点索引从 0 开始编号,对于大根堆,若:Ki >= K2i+1 且 Ki >= K2i+2,i =0,1,2…,则称为大堆。将根节点最大的堆叫做最大堆或大根堆。
  2. 存储方式:堆是一颗完全二叉树,因此可以采用顺序方式进行存储。对于非完全二叉树,则不适合使用顺序方式进行存储,因为为了能够还原二叉树,空间中必须要存储空节点,就会导致空间利用率比较低。
  3. 创建过程:给定一个任意整型数组,将其调整为最大堆。核心思路是从最后一个非叶子节点开始进行元素下沉操作,不断向根节点倒着向下调整,慢慢的将左右子树调整为最大堆。
// 模拟实现 priority_heap
template<typename T>
class my_priority_heap // 优先队列,大根堆
{
public:// 尾插,实现自动大根堆void push(T val){heap.push_back(val);adjustup(heap.size() - 1);}// 获得堆顶的值T top(){if (heap.size() == 0)return T();return heap[0];}// 头删,实现自动大根堆void pop(){if (heap.size() == 0)return;heap[0] = heap.back();heap.pop_back();// vector 在创建的时候可能会开辟空间,不能直接 size-- 会造成内存泄漏	adjustdown(0);}// swap 实现void swap(int i, int j){T tmp = heap[i];heap[i] = heap[j];heap[j] = tmp;}private:vector<T> heap;int parent(int index) { return (index - 1) / 2; }// 方法 - 获得父节点int leftchild(int index) { return (index * 2) + 1; } // 方法 - 获得左子节点int rightchild(int index)	 { return (index * 2) + 2; } // 方法 - 获得右子节点// 向上调整void adjustup(int index){if (index > 0 && (heap[index] > heap[parent(index)])){swap(index, parent(index));index = parent(index);adjustup(index);}}// 向下调整void adjustdown(int index){int largest = index;int left = leftchild(index);int right = rightchild(index);if (left < heap.size() && heap[left] > heap[largest]) largest = left;if (right < heap.size() && heap[right] > heap[largest]) largest = right;if (largest != index){swap(heap[largest], heap[index]);adjustdown(largest);}}
};

🏳️‍🌈四、标准库容器类的融合使用

在这里大根堆的模拟实现是利用了 vector 和 heap 两种容器,实现了有队列的删对头,填队尾的能力,也就是优先队列

可见标准库容器是可以融合使用贯通的,以此来达到某种目的

举几个例子

❤️一、vector 与 set

  1. 数据去重与排序:
    可以先使用vector存储数据,然后将其内容复制到set中。由于set的特性是自动排序且不允许重复元素,这样可以快速实现对数据的去重和排序。

示例代码:

   std::vector<int> vec = {3, 1, 2, 3, 4, 1};std::set<int> s(vec.begin(), vec.end());// s 现在是 {1, 2, 3, 4},已排序且无重复元素。
  1. 高效查找与随机访问结合:
    如果需要频繁进行随机访问,可以使用vector。而当需要快速查找特定元素是否存在时,可以利用set。
    例如,在处理大量数据时,可以先将数据存储在vector中,然后根据需要在特定操作中使用set进行快速查找。

🧡二、vector与map

  1. 关联数据存储与快速访问:
    vector可以存储一系列数据,而map可以建立数据之间的关联关系。例如,可以用vector存储对象列表,然后用map建立对象的某个属性与对象在vector中的索引的映射,以便快速根据属性值查找对应的对象。

示例代码:

   std::vector<Person> people;people.push_back(Person("Alice", 25));people.push_back(Person("Bob", 30));std::map<std::string, int> nameToIndex;for (int i = 0; i < people.size(); ++i) {nameToIndex[people[i].name] = i;}int index = nameToIndex["Bob"];if (index!= -1) {std::cout << "Bob's age is " << people[index].age << std::endl;}

💛三、list与queue

  1. 灵活的链表操作与队列行为:
    list提供了高效的插入和删除操作,可以作为底层存储结构。然后,可以通过封装list来实现一个具有队列行为的容器,例如使用push_back在尾部插入元素,使用pop_front在头部删除元素,模拟队列的先进先出(FIFO)特性。

示例代码:

   std::list<int> dataList;// 模拟队列操作dataList.push_back(1);dataList.push_back(2);dataList.push_back(3);while (!dataList.empty()) {int frontElement = dataList.front();std::cout << frontElement << " ";dataList.pop_front();}

💚四、deque与stack

  1. 双端队列作为栈使用:
    deque支持在两端进行高效的插入和删除操作。可以将deque封装成一个栈,只在一端进行插入(push_back)和删除(pop_back)操作,实现栈的后进先出(LIFO)特性。

示例代码:
加粗样式

💙


👥总结


本篇博文对 大根堆 及 标准库容器类的融合使用 做了一个较为详细的介绍,不知道对你有没有帮助呢

觉得博主写得还不错的三连支持下吧!会继续努力的~

请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/431827.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

git push错误:Out of memory, malloc failed (tried toallocate 947912704 bytes)

目录 一、错误截图 二、解决办法 一、错误截图 因项目文件过大&#xff0c;http.postBuffer设置的内存不够&#xff0c;所以报错。 二、解决办法 打开cmd窗口&#xff0c;执行如下命令即可 git config --global http.postBuffer 1024000000 如图所示 执行完成以后&#…

NCNN 源码(1)-模型加载-数据预处理-模型推理

参考 ncnn 第一个版本的代码。 0 整体流程 demo&#xff1a;squeezenet ncnn 自带的一个经典 demo&#xff1a;squeezenet 的代码: // 网络加载 ncnn::Net squeezenet; squeezenet.load_param("squeezenet_v1.1.param"); squeezenet.load_model("squeezenet_…

RIFormer:保持你的视觉主干有效但移除令牌混合器

摘要 https://arxiv.org/pdf/2304.05659 本文研究了如何在去除其基本构建块中的标记混合器&#xff08;token mixers&#xff09;的同时保持视觉主干的有效性。标记混合器作为视觉变换器&#xff08;Vision Transformers, ViTs&#xff09;的自注意力机制&#xff0c;旨在实现…

【排序算法】选择排序、堆排序

文章目录 选择排序选择排序的概念选择排序的基本步骤&#xff1a;选择排序的特点选择排序的代码实现&#xff08;C语言&#xff09; 选择排序-优化双向选择排序的步骤 堆堆的基本概念堆排序详细步骤堆排序代码讲解 选择排序 选择排序的概念 选择排序是一种简单直观的排序算法。…

SpringBoot项目编译运行成功,但有些包名类名仍然下划线标红的解决方法 | Idea

目录 问题解决方案&#xff1a;方法一&#xff1a;方法二【我用这个成功的】 问题 如图&#xff0c;成功运行但有些包名类名仍然下划线标红&#xff0c;强迫症抓狂 成功运行&#xff1a; 有些包导入标红&#xff1a; 解决方案&#xff1a; 方法一&#xff1a; 点击fil…

分布式框架 - ZooKeeper

一、什么是微服务架构 1、单体架构 顾名思义一个软件系统只部署在一台服务器上。 ​ 在高并发场景中&#xff0c;比如电商项目&#xff0c;单台服务器往往难以支撑短时间内的大量请求&#xff0c;聪明的架构师想出了一个办法提高并发量&#xff1a;一台服务器不够就加一台&am…

整合SpringSecurity框架经典报错

报错描述Description: Field userDetailsService in com.atguigu.security.config.WebSecurityConfig required a bean of type org.springframe 这是整合SpringSecurity权限认证中经常出现的一个问题&#xff0c;由于SpringSecurity中这个UserDetailsService未找到 解决方案…

【线程】线程的同步---生产消费者模型

本文重点&#xff1a;理解条件变量和生产者消费者模型 同步是在保证数据安全的情况下&#xff0c;让我们的线程访问资源具有一定的顺序性 条件变量cond 当一个线程互斥地访问某个变量时&#xff0c;它可能发现在其它线程改变状态之前&#xff0c;它什么也做不了&#xff0c;…

Java 微服务框架 HP-SOA v1.1.4

HP-SOA 功能完备&#xff0c;简单易用&#xff0c;高度可扩展的Java微服务框架。 项目主页 : https://www.oschina.net/p/hp-soa下载地址 : https://github.com/ldcsaa/hp-soa开发文档 : https://gitee.com/ldcsaa/hp-soa/blob/master/README.mdQQ Group: 44636872, 66390394…

ctf.show---->re2

做题笔记。 下载 查壳 32 ida打开。 WSL先运行一下&#xff1a; &#xff1f; 创建呗。 函数如下&#xff1a; 逻辑很清晰&#xff0c;写脚本咯 &#xff1a; #include <stdio.h> #include <string.h> #include <stdlib.h>int main() {char encode[] &qu…

TCP网络编程概述、相关函数、及实现超详解

文章目录 TCP网络编程概述1. TCP协议的特点2. TCP与UDP的差异3. TCP编程流程 TCP网络编程相关函数详解1. socket()&#xff1a;创建套接字参数说明&#xff1a;返回值&#xff1a;示例&#xff1a; 2. connect()&#xff1a;客户端连接服务器参数说明&#xff1a;返回值&#x…

IDEA去除掉虚线,波浪线,和下划线实线的方法

初次安装使用IDEA&#xff0c;总是能看到导入代码后&#xff0c;出现很多的波浪线&#xff0c;下划线和虚线&#xff0c;这是IDEA给我们的一些提示和警告&#xff0c;但是有时候我们并不需要&#xff0c;反而会让人看着很不爽&#xff0c;这里简单记录一下自己的调整方法&#…

Ubunt系统设置NVIDIA显卡性能模式

文章目录 前言一、了解自己的显卡1、输入nvidia-smi指令搞清楚表中的含义 二、通过英伟达官方设置进行修改1、此时的状态3、改完后变为P0 总结 前言 工欲善其事&#xff0c;那性能直接拉满&#xff0c;宁可累坏显卡&#xff0c;也不能影响自己&#xff0c;首先了解自己的显卡以…

OpenAPI鉴权(二)jwt鉴权

一、思路 前端调用后端可以使用jwt鉴权&#xff1b;调用三方接口也可以使用jwt鉴权。对接多个三方则与每个third parth都约定一套token规则&#xff0c;因为如果使用同一套token&#xff0c;token串用可能造成权限越界问题&#xff0c;且payload交叉业务不够清晰。下面的demo包…

uni-app页面调用接口和路由(四)

文章目录 一、路由二、页面调用接口二、路由跳转1.uni.navigateTo(OBJECT)2.uni.redirectTo(OBJECT)3.uni.reLaunch(OBJECT)4.uni.switchTab(OBJECT)5.uni.navigateBack(OBJECT) 总结 一、路由 路由配置 uni-app页面路由为框架统一管理&#xff0c;开发者需要在pages.json里配…

iOS六大设计原则设计模式

六大设计原则&#xff1a; 一、单一职责原则 一个类或者模块只负责完成一个职责或者功能。 类似于&#xff1a;UIView 和 CALayer 二、开放封闭原则 对扩展开放&#xff0c;对修改封闭。 我们要尽量通过扩展软件实体来解决需求变化&#xff0c;而不是通过修改已有的代码来…

ESP32-WROOM-32 [创建AP站点-客户端-TCP透传]

简介 基于ESP32-WROOM-32 开篇(刚买)&#xff0c; 本篇讲的是基于固件 ESP32-WROOM-32-AT-V3.4.0.0&#xff08;内含用户指南, 有AT指令说明&#xff09;的TCP透传设置与使用 设备连接 TTL转USB线, 接ESP32 板 的 GND&#xff0c;RX2&#xff0c; TX2 指令介绍 注意,下面指…

Facebook Marketplace无法使用的原因及解决方案

Facebook Marketplace是一项广受欢迎的买卖平台&#xff0c;然而&#xff0c;有时候用户可能会遇到无法访问或使用该功能的问题。通常&#xff0c;这些问题可以归结为以下几类原因&#xff1a; 地理位置限制&#xff1a; Facebook Marketplace并非在全球每个地区都可用。在某些…

【C++】C++中如何处理多返回值

十四、C中如何处理多返回值 本部分也是碎碎念&#xff0c;因为这些点都是很小的点&#xff0c;构不成一篇文章&#xff0c;所以本篇就是想到哪个点就写哪个点。 1、C中如何处理多个返回值 写过python的同学都知道&#xff0c;当你写一个函数的返回时&#xff0c;那是你想返回…

MySQL(日志)

日志 日志分为三种&#xff1a; undo log &#xff08;回滚日志&#xff09;&#xff1a;用于事务回滚和MVCC redo log &#xff08;重做日志&#xff09;&#xff1a;用于故障恢复 binlog &#xff08;归档日志&#xff09;&#xff1a;用于数据备份和主从复制 undo log undo…