贪心算法 - 学习笔记 【C++】

news2025/1/6 7:40:55

2024-12-09 - 第 38 篇
贪心算法 - 学习笔记
作者(Author): 郑龙浩 / 仟濹(CSND账号名)

贪心算法

学习课程:

https://www.bilibili.com/video/BV1f84y1i7mv/?spm_id_from=333.337.search-card.all.click&vd_source=2683707f584c21c57616cc6ce8454e2b

一、基本概念

贪心算法是一种在每个步骤都选择当前最优决策的算法。它只考虑当下的最好情况,希望通过这些局部最优选择来得到全局最优解。 特点是简单高效、具有无后效性。不过它不是万能的,有些情况不能通过它得到全局最优解,但在像哈夫曼编码这类有最优子结构的问题中很有效。

可以分为这四步( AI 生成 )

  • 分解问题:将一个复杂的大问题分解为一系列的子问题。例如在活动安排问题中,把安排所有活动这个大问题,分解为一个个单独活动的选择。
  • 确定贪心策略:找到一个合适的贪心策略,即确定在每个子问题中怎样选择才是局部最优。比如在找零问题中,贪心策略是优先使用面值大的硬币。
  • 按照策略进行选择:依据确定好的贪心策略,在每个子问题里做出选择。以活动安排为例,就按照结束时间最早这个策略来选择活动。
  • 合并结果:将所有子问题的局部最优解合并起来,得到最终的解。不过要注意这个解不一定是全局最优解。

简单一点讲就是:

  • 无论能否吞得下,能吞一点。
  • 根据当前的情况做出当前情况的最佳选择。
  • 当做出选择的时候,要永不改变。反之,回溯算法可以“反悔”。
  • 循环下去,使用“局部最优解”,逐步得到“整体最优解”

二、入门案例

海盗打劫商船,商船上装满古董,每件古董的重量不同,但每件古董的价值都相同,海盗船有最大载重的限制。

  • 假设 最大载重 500

问,最多装几件古董,既不翻船又获利最大?

思路:

// 思路:
// 1. 先排序,从小到大 --> 目的就是从最轻的算起,逐步往重的方放,直到放到放不下为止
// 2. 从 0 开始,逐步累加,直到累加和大于 500 为止 --> 意思就是,边放边计算数量,直到船上放不开。
// 先累加,然后判断累加后的是否 >  最大载重
     // 若 > 最大载重,就退出循环
     // 若 < 最大载重,就继续累加
#include <iostream>
#include <algorithm>
using namespace std;
int main( void ){
    int data[] = { 8, 20, 5, 80, 3, 420, 14, 330, 70 };
    int max = 500, ans = 0, sum = 0; // 最大载重, 古董数量, 总重量
    int cnt = sizeof( data ) / sizeof( data[ 0 ] ); // 计算数组元素个数
    sort( data, data + cnt ); // 排序
    for ( int i = 0; i < cnt; i ++ ){
        // 先累加,然后判断累加后的是否 >  最大载重
        // 若 > 最大载重,就退出循环
        // 若 < 最大载重,就继续累加
        sum += data[ i ]; // 总重量累加
        if( sum > max ) // 如果当前重量大于最大载重,就退出循环
            break;
        ans ++; // 古董数量累加
    }
    cout << "古董数量:" << ans << endl;
    return 0;
}

三、初级案例 - 字节跳动笔试题

一个很长的花坛,一部分地已经种植了花,另一部分却没有。
花不能种植在相邻的地块上,否则它们会争夺水源,两者都会死去。

给你一个整数数组表示花坛,0 表示没种植花,1 表示种植了花。

给定一个数n,能不能种下 n 朵花?

分析 + 思路:

  • 首先要知道:给定了一个数组(表示的花坛),我们需要在给定的数组中种花。Eg: 10001 --> 可以种一朵花。 10010 --> 不可以种花. 10000001 / 1000001 --> 可以种两朵花
  • 可以种花的条件是:当前位置的左边和右边都没有花。 Eg: 10001 --> 可以种一朵花。 10010 --> 不可以种花. 10000001 / 1000001 --> 可以种两朵花
  • 最终计算的是,n朵花是否能全部种到 给定的花坛中去(数组)
  1. 封装一个函数,用来判断 是否可以将 n 朵花 种完
    这个地方的判断需要特别注意,主要是 最两边的元素判断。
    特别容易出错。
  2. 函数过程:遍历数组,如果当前位置可以种花,则 将当前位置的元素变为 “1”,并且 种植数量 ++
  3. 判断 种植数量 是否等于 n ,如果等于 n ,则打印 Yes ,否则打印 No
// [字节跳动]笔试真题
// 一个很长的花坛,一部分地已经种植了花,另一部分却没有。
// 花不能种植在相邻的地块上,否则它们会争夺水源,两者都会死去。

// 给你一个整数数组表示花坛,0 表示没种植花,1 表示种植了花。

// **给定一个数n,能不能种下 n 朵花?**  能打印 Yes, 不能打印 No

// 分析 + 思路:
// 首先要知道:给定了一个数组(表示的花坛),我们需要在给定的数组中种花。
// 可以种花的条件是:当前位置的左边和右边都没有花。 Eg: 10001 --> 可以种一朵花。 10010 --> 不可以种花. 10000001 / 1000001 --> 可以种两朵花 
// 最终计算的是,n朵花是否能全部种到 给定的花坛中去(数组)

// 1. 封装一个函数,用来判断 是否可以将 n 朵花 种完
// 这个地方的判断需要特别注意,主要是 最两边的元素判断。
// 特别容易出错。
// 2. 函数过程:遍历数组,如果当前位置可以种花,则 将当前位置的元素变为 “1”,并且 种植数量 ++ 
// 3. 判断 种植数量 是否等于 n ,如果等于 n ,则打印 Yes ,否则打印 No

#include <iostream>
#include <vector>
// 判断是否可以将 n 朵花 种完
bool canPlant( bool *data, int dataSize, int n ){ //  data 表示数组首地址 dataSize 表示 存储的数据的宽度 即花坛的长度, n 表示需要种植的花的数量
    int count = 0; // 表示 种植数量
    for( int i = 0; i < dataSize;  i ++ ){
        if( data[ i ] == 0 && data[ i - 1 ] == 0 && data[ i + 1 ] == 0 && i - 1 >= 0 && i + 1 < dataSize ){ // 种植条件: 左右两边都没有花 && 边界条件:不能越界
            data[ i ] = 1; // 种花
            count ++; // 种植数量 ++
        }
        if( count >= n )    return 1; // 如果 count 种植数量 已经满足了 n 朵花,无需再进行多余的循环
    }
    return 0;
}
using namespace std;
int main( void ){
    bool data[] = { 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1 };
    int n;
    cin >> n; // 输入种植几朵花
    if ( canPlant( data, sizeof(data) / sizeof(data[0]), n ) )
        cout << "Yes" << endl;
    else
        cout << "No" << endl;

    return 0;
}

四、中级案例 - 华为笔试题

给定一个整数数组,表示在同一行的行星。

每一个元素,其绝对值表示行星的大小,正负表示行星的移动方向

正 表示向右移动, 负 表示向左移动.

每一颗行星 速度相同。

碰撞规则

  1. 两个行星碰撞,较小的行星会爆炸。
  2. 如果大小相同,则两颗都会爆炸。
  3. 两颗移动方向相同的行星,永远不会发生碰撞。

分析 + 思路

分析 + 思路:

  1. 封装一个函数,用来判断 + 计算 全部星球碰撞以后留下来的行星。并存入一个新的数组。

  2. 函数过程:两个变量分别表示的 左边的行星右边的行星

    几种情况:(1)为相同的情况;(2)(3)为不先相同的情况

    (1). 两个行星方向 【相同】,则永远不会发生碰撞

    (2). 两个行星方向 【相反】 && 左边质量 > 右边质量 --> 右边行星爆炸

    (3). 两个行星方向 【相反】 && 左边质量 < 右边质量 --> 左边行星爆炸

    (4). 两个行星方向 【相反】 && 左边质量 == 右边质量 --> 两个行星都爆炸

    注: 【情况(2)】 与 【情况(3)】【情况(4)】 合并为一种情况。

    循环 + 判断 完所有的情况以后,最后将留下来的行星 【存入】 新的数组中。

  3. 将留下来的行星质量存入 传来的数组参数中。

可能会有错误。

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

// 判断 + 计算 全部星球碰撞以后留下来的行星。并存入一个新的数组。
void newdata ( vector <int> &data, vector <int> &data2 ){
    // 使用 ector 就不用单独计算 数组的大小了

// 参数: 原来容器, 容器大小, 新容器(存放星球爆炸以后的数据)
    int left = 0, right = 1; // 左边的星球 右边的星球;
    while( right < data.size()){ // 循环条件:右边的星球 【不超过】 星球容器宽度
        

        // 两个行星方向 【相同】,则永远不会发生碰撞
        if( data[left] * data[right] > 0 ){ // 方向一致,则永远不会发生碰撞【不爆炸】 【情况(1)】

            left = right;
            right ++;
            continue; // 没有爆炸,数据未更新,表示位置变量发生变化,重新进入循环
            // 注: 下面这种写法是有错误的,因为 left 并不一定 移动到 下一个星球 --> 可能会出现 bug
            // 只有 right 可以进行 ++
            // left++;
            // right++;

        } else if( data[left] * data[right] < 0 ){ // 两个行星方向 【相反】 --> 两种情况

            //【情况(2)】 --> 左边质量 > 右边质量 --> 右边行星爆炸
            if( abs(data[left]) > abs(data[right]) ){//【右边消失】
                data.at( right ) = 0; // 0 表示爆炸的星球
                left = right;
                right ++;
                continue; // 已经爆炸,数据更新,表示位置变量发生变化,重新进入循环
            } else if ( abs(data[left]) < abs(data[right]) )//【情况(3)】 左边质量 < 右边质量 --> 左边行星爆炸
            { 
                data.at( left ) = 0; // 0 表示爆炸的星球
                left = right;
                right ++;
                continue; // 已经爆炸,数据更新,表示位置变量发生变化,重新进入循环
            } else // 【情况(4)】左边质量 == 右边质量 --> 两个行星全都爆炸
            {
                data.at( left ) = 0; // 0 表示爆炸的星球
                data.at( right ) = 0; 
                left = right;
                right ++;
                continue;
            }
        }
        // 判断 left 与 right 的指针位置已经爆炸
        if ( data[ left ] == 0 ) // 左边的星球已经爆炸
            {
            left = right;
            right ++;
            continue; // 没有爆炸,数据未更新,表示位置变量发生变化,重新进入循环
            }
            else if ( data[ right ] == 0 ){ // 右边的星球已经爆炸
                right ++; // 只需进行 右边位置移动即可
                continue;
            }
        // cout << right << endl;
        // cout << "123" << endl; // 测试死循环
    }
        
    // 将更新后的 为 "1" 的数据存入 新的容器中
    for( auto i = data.begin() ; i != data.end(); i++ ){
        if( *i!= 0 ){ // 将没爆炸的星球存入新的容器中
            data2.push_back( *i );
        }
    }
}
int main( void ){
    vector <int> data = {10, 2, -2, -2, -5}; // 随机写几个数据进行测试
    vector <int> data2; // 新的容器
    newdata ( data, data2 ); // 计算留下的星球
    cout << "爆炸个数:" << data.size() - data2.size() << endl; // 打印 爆炸的星球个数
    cout << "留下的星球:" << endl;
    for ( auto i = data2.begin(); i != data2.end(); i++ ){
        cout << *i << " "; // 打印 留下的星球
    }
    return 0;
}

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

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

相关文章

scale index的计算

scale index定义 基本实现 需要注意&#xff0c;scale index的提出者分别构建了MATLAB和R语言的实现方式。 但是&#xff0c;需要注意&#xff0c;经过我向作者求证。 MATLAB编写的代码已经“过时了”&#xff0c;为了拥抱时代&#xff0c;作者构建了R语言包&#xff0c;名称为…

虚幻5描边轮廓材质

很多游戏内都有这种描边效果&#xff0c;挺实用也挺好看的&#xff0c;简单复刻一下 效果演示&#xff1a; Linethickness可以控制轮廓线条的粗细 这样连完&#xff0c;然后放到网格体细节的覆层材质上即可 可以自己更改粗细大小和颜色

【Java Nio Netty】基于TCP的简单Netty自定义协议实现(万字,全篇例子)

基于TCP的简单Netty自定义协议实现&#xff08;万字&#xff0c;全篇例子&#xff09; 前言 有一阵子没写博客了&#xff0c;最近在学习Netty写一个实时聊天软件&#xff0c;一个高性能异步事件驱动的网络应用框架&#xff0c;我们常用的SpringBoot一般基于Http协议&#xff0…

Ubuntu 20.04LTS 系统离线安装5.7.44mysql数据库

Ubuntu 20.04LTS 系统离线安装5.7.44mysql数据库 环境下载 MySQL 5.7.44 包安装标题检查服务是否启动成功遇到的问题登陆&修改密码&远程访问 环境 操作系统&#xff1a;Ubuntu 20.04.4 LTS 数据库&#xff1a;MySQL 5.7.34 内核版本&#xff1a;x86_64&#xff08;amd…

后端-redis的使用

redis的服务端启动命令&#xff0c;打开redis的目录&#xff0c;输入cmd redis的客户端启动命令 设置redis密码 redis连接 指定ip地址的服务端,没设密码&#xff1a;redis-cli.exe -h localhost -p 6379 edis连接 指定ip地址的服务端,设置了密码&#xff1a;redis-cli.ex…

前端成长之路:CSS字体、文本属性和引入方式

本文主要介绍CSS的字体属性和文本属性&#xff0c;最后再介绍CSS在HTML中的引入方式。 CSS字体属性 CSS Fonts&#xff08;字体&#xff09;属性能用于定义字体系列属性&#xff0c;包括但不限于字体大小、粗细、字体样式等。 字体系列 在CSS中使用font-family属性定义文本…

基于windows环境使用nvm安装多版本nodejs

目录 前言 一、卸载node 二、nvm是什么&#xff1f; 三、nvm安装 1.官网下载 nvm 包 2. 安装nvm-setup.exe 3. 配置路径和下载镜像 4. 检查安装是否完成 四、 使用nvm安装node 五、修改npm默认镜像源为淘宝镜像 六、环境变量配置 1. 新建目录 2. 设置环境变量 七…

排序算法(2):选择排序

问题 排序 [30, 24, 5, 58, 18, 36, 12, 42, 39] 选择排序 选择排序每次从待排序序列中选出最小&#xff08;或最大&#xff09;的元素&#xff0c;将其放到序列的起始位置&#xff0c;然后&#xff0c;再从剩余未排序元素中继续寻找最小&#xff08;或最大&#xff09;元素…

009-jvm-对象相关的概念

#案例&#xff1a; 对象的创建过程 初始化默认值 成员变量显示赋值 构造代码块的初始化 构造器中的初始化 jvm

【硬件测试】基于FPGA的4FSK调制解调通信系统开发与硬件片内测试,包含信道模块,误码统计模块,可设置SNR

目录 1.算法仿真效果 2.算法涉及理论知识概要 3.Verilog核心程序 4.开发板使用说明和如何移植不同的开发板 5.完整算法代码文件获得 1.算法仿真效果 本文是之前写的文章: 《基于FPGA的4FSK调制解调系统,包含testbench,高斯信道模块,误码率统计模块,可以设置不同SNR》 的…

20 go语言(golang) - gin框架安装及使用(一)

一、简介 Gin是一个用Go语言编写的高性能Web框架&#xff0c;专注于构建快速、可靠的HTTP服务。它以其速度和简洁性而闻名&#xff0c;非常适合用于开发RESTful API。 高性能&#xff1a;Gin使用了httprouter进行路由管理&#xff0c;这是一个轻量级且非常快速的HTTP请求路由器…

检查读取数据寄存器输出的多扇出

为使第二寄存器被 RAM 原语吸收&#xff0c;来自存储器阵列的数据输出位的扇出必须为 1 。这在下图中进行了说明。 检查地址 / 读取数据寄存器上的复位信号 不应复位存储器阵列。只有 RAM 的输出可以容许复位。复位必须是同步的&#xff0c;以便将输出寄存器推断到 RAM 基元…

rk3588-ubuntu22.04系统网关实现路由器功能:

rk3588-ubuntu22.04系统网关实现路由器功能&#xff1a; 场景需求描述&#xff1a; 需求背景&#xff1a; 场景一&#xff1a;通过网线eth0/(路由器wlan0)访问外网&#xff1a; 如果网关 和 设备所处的环境可以通过网线联网或者路由器联网&#xff0c;那么不需要将网关配置成…

Tomcat的下载和使用,配置控制台输出中文日志

目录 1. 简介2. 下载3. 使用3.1 文件夹展示3.1.1 控制台输出乱码 3.2 访问localhost:80803.3 访问静态资源 4. 总结 1. 简介 Tomcat&#xff0c;全称为Apache Tomcat&#xff0c;是一个开源的Web应用服务器和Servlet容器&#xff0c;由Apache软件基金会的Jakarta项目开发。它实…

【银河麒麟高级服务器操作系统】有关dd及cp测试差异的现象分析详解

了解更多银河麒麟操作系统全新产品&#xff0c;请点击访问 麒麟软件产品专区&#xff1a;https://product.kylinos.cn 开发者专区&#xff1a;https://developer.kylinos.cn 文档中心&#xff1a;https://documentkylinos.cn dd现象 使用银河麒麟高级服务器操作系统执行两次…

【在Linux世界中追寻伟大的One Piece】自旋锁

目录 1 -> 概述 2 -> 原理 3 -> 优缺点及使用场景 3.1 -> 优点 3.2 -> 缺点 3.3 -> 使用场景 4 -> 纯软件自旋锁类似的原理实现 4.1 -> 结论 5 -> 样例代码 1 -> 概述 自旋锁是一种多线程同步机制&#xff0c;用于保护共享资源避免受并…

顺序表的使用,对数据的增删改查

主函数&#xff1a; 3.c #include "3.h"//头文件调用 SqlListptr sql_cerate()//创建顺序表函数 {SqlListptr ptr(SqlListptr)malloc(sizeof(SqlList));//在堆区申请连续的空间if(NULLptr){printf("创建失败\n");return NULL;//如果没有申请成功&#xff…

利用卷积神经网络进行手写数字的识别

数据集介绍 MNIST&#xff08;Modified National Institute of Standards and Technology&#xff09;数据集是一个广泛使用的手写数字识别数据集&#xff0c;常用于机器学习和计算机视觉领域中的分类任务。它包含了从0到9的手写数字样本&#xff0c;常用于训练和测试各种图像…

题解 - 取数排列

题目描述 取1到N共N个连续的数字&#xff08;1≤N≤9&#xff09;&#xff0c;组成每位数不重复的所有可能的N位数&#xff0c;按从小到大的顺序进行编号。当输入一个编号M时&#xff0c;就能打印出与该编号对应的那个N位数。例如&#xff0c;当N&#xff1d;3时&#xff0c;可…

如何在 ASP.NET Core 3.1 应用程序中使用 Log4Net

介绍 日志记录是应用程序的核心。它对于调试和故障排除以及应用程序的流畅性非常重要。 借助日志记录&#xff0c;我们可以对本地系统进行端到端的可视性&#xff0c;而对于基于云的系统&#xff0c;我们只能提供一小部分可视性。您可以将日志写入磁盘或数据库中的文件&#xf…