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

news2024/9/25 22:46:07

📢博客主页: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.coloradmin.cn/o/2165013.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

centos7安装Redis单机版

一、检查是否有GCC环境 gcc --version # 提示-bash: gcc: 未找到命令 说明没有gcc环境# 安装gcc环境 yum install gcc# 如果yum源报错 # 1.检查网络是否正常 ping www.baidu.com # 2.备份当前的yum源 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo…

IntraWeb制作汉堡菜单

IntraWeb开发web网站时如何制作手机页面&#xff1f; delphi源代码&#xff1a;示例两列布局带顶部汉堡菜单&#xff08;兼容电脑与手机&#xff09; 功能&#xff1a;交互式网页&#xff0c;两列布局&#xff0c;顶部汉堡菜单&#xff0c;点击汉堡图标关闭左侧栏&#xff0c;…

【NLP】基于“检测器-纠错器”中文文本纠错框架

前言 许多方法将中文拼写纠正&#xff08;检测和纠正给定中文句子中的错误字符&#xff09;视为序列标注任务&#xff0c;并在句子对上进行微调。一些方法使用错误检测器作为初步任务&#xff0c;然后将检测结果用于辅助后续的错误纠正过程。然而&#xff0c;现有方法在使用检…

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

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

java项目之新闻稿件管理系统(源码+文档)

项目简介 新闻稿件管理系统实现了以下功能&#xff1a; 新闻稿件管理系统的主要使用者管理员功能有个人中心&#xff0c;用户管理&#xff0c;记者管理&#xff0c;审批员管理&#xff0c;新闻分类管理&#xff0c;新闻信息管理&#xff0c;系统管理等。记者发布新闻信息&…

uniapp 整合 OpenLayer3

安装openLayer插件 命令行&#xff1a;npm install ol 安装sass插件 命令行&#xff1a;npm install -D sass 使用方法&#xff1a; *** *** <style scoped lang"scss"> </style> 安装ElementPlus 命令行&#xff1a;npm install element-plus -…

汽车零部件开发流程关键阶段

目录 1、定点阶段 1.1、定点前的准备工作 1.2、定点决策过程 1.3、定点后的工作交接 2、A样阶段&#xff1a;设计验证与基本功能实现 2.1、样件制作&#xff1a;从设计图纸到实物转化 2.2、功能测试&#xff1a;初步验证与性能评估 2.3、评估与优化&#xff1a;A样阶段…

Java数据库连接jdbc

Java数据库连接jdbc 导入java包 1、根目录&#xff0c;新建一个lib目录&#xff08;Dire&#xff09; 2、将jar包放入lib目录下 3、File -> Project Structure&#xff08;项目结构&#xff09; 4、Libraries-> ->java->找到项目的lib目录 5、Apply->OK使用JD…

下载2001年版英特尔开发手册与使用网易有道词典

本专栏的任务&#xff0c;是翻译2001年版英特尔开发手册的第3卷。上一节&#xff0c;我写了开篇语。本节&#xff0c;我是打算将这个版本的英特尔开发手册的下载方式公布出来。使得大家可以将其下载回去。如果你看的块的话&#xff0c;你可以自行翻译与学习。 一. 下载英特…

数据结构const char *INSTNAME[]

代码片段解析 #include <cstring> #include <fstream> #include <sstream> #include <string>const char *INSTNAME[]{"lui", "auipc", "jal", "jalr", "beq", "bne", "blt…

从理论到实践:解锁《数字化专业知识体系》助力企业数字化转型的落地之道

全面解码数字化转型——从理论构想到实践落地 在全球数字化浪潮的推动下&#xff0c;企业正面临前所未有的变革压力。虽然数字化转型的概念已经深入人心&#xff0c;但将其从战略蓝图转化为实际成果的过程仍充满挑战。《数字化专业知识体系》&#xff08;《Towards a Digital …

双目视觉路线,为什么一直没有存在感

“在大疆之前没有人做双目&#xff0c;现在基本上主流的都是单目加多传感器融合&#xff0c;推给车企的时候就会经历一个更长的过程。”一位前大疆车载员工曾这样向雷峰网《新智驾》表示。 双目视觉方案在车载上的应用起起伏伏&#xff0c;从早期的高端车型专属&#xff0c;到…

快手B端商业化技术探索:基于LLM构建智能RAG与Agent平台

导读&#xff1a;大模型技术正以前所未有的速度与各领域融合&#xff0c;为各行各业带来变革&#xff0c;围绕快手B端商业化的业务场景&#xff0c;本文详细阐述了构建基于LLM的Agent技术平台的策略、挑战及解决方案&#xff0c;为您带来宝贵的见解与启示。 一、大模型应用建设…

一日连发两款视频大模型,火山引擎杀疯了!

9月24日&#xff0c;字节跳动旗下火山引擎在深圳举办AI创新巡展&#xff0c;并首次对外发布豆包视频生成-PixelDance、豆包视频生成-Seaweed两款AI大模型&#xff0c;并公布了多项AI大模型的全新升级&#xff0c;以一种全新的姿态迎接AI时代的到来。 雷科技此次受邀参与巡展&a…

机器学习算法与Python实战 | 三万字详解!GPT-5:你需要知道的一切(上)建议收藏!

本文来源公众号“机器学习算法与Python实战”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;三万字详解&#xff01;GPT-5&#xff1a;你需要知道的一切 作者&#xff1a;Alberto Romero &#xff08;青稞AI整理&#xff09; 原…

2024杭电多校7——1007创作乐曲

补题链接 也是好久没补题了&#xff0c;欠了好多好多,我的评价是加训 题目如下: 官方题解: 官方题解一如既往的简洁&#xff0c; 对于不会做的人要读懂真的挺难 \sout{对于不会做的人要读懂真的挺难} 对于不会做的人要读懂真的挺难 首先询问最少删除多少音符(一般人来说不…

【教学类-52-14】20240925动物数独(N宫格通用版)1图、2图、6图、有答案、无答案 组合版18套

背景需求&#xff1a; 制作了3-5宫格&#xff08;1、2、6图&#xff09;样式18组&#xff0c;它们用的都是&#xff08;1、2、6图&#xff09;的word模板&#xff0c;只是宫格数量不同&#xff0c;图片插入大小不同&#xff0c;是否可以做一个通用代码&#xff1f; 【教学类-…

C#算法(16)—获取上升沿和下降沿信号

前言 我们在上位机软件开发的时候有时候需要抓取一个信号的上升沿或者下降沿,这时候就需要我们自己编写程序来实现抓取上升沿和下降沿的功能,详细内容如下: 一个信号从“无”到“有”,然后再从“有”到“无”,经历的过程如下:无-上升沿-有-下降沿-无,这样5个过程。 1…

基于微信小程序的电影院订票选座系统ssm(lw+演示+源码+运行)

摘 要 通过移动互联网这几年的发展&#xff0c;单独的开发某些APP已经到了日暮西山的地步&#xff0c;原因不在于单个的APP功能丰富与否&#xff0c;而是因为用户生态体系的问题。使用智能手机的用户经过多年的使用习惯&#xff0c;已经越来越不喜欢安装各种的APP&#xff0c;…

mysql 05 InnoDB数据页结构

01.数据页结构的快速浏览 02.记录在页中的存储 在页的7个组成部分中&#xff0c;我们自己存储的记录会按照我们指定的 行格式 存储到 User Records 部分。但是在一开始生成页的时候&#xff0c;其实并没有 User Records 这个部分&#xff0c;每当我们插入一条记录&#xff0c…