【基础算法】贪心算法

news2024/12/23 15:53:12

贪心算法又称贪婪算法,是一种常见的算法思想。贪心算法的优点是效率高,实现较为简单,缺点是可能得不到最优解。

贪心算法的基本思想

贪心算法就是在求解问题时,总是做出当前看来最好的选择。也就是说贪心算法并不从整体最优上考虑问题,算法得到的是局部最优解。而局部最优解叠加在一起便构成了问题的整体最优解,或者近似最优解。


假设有3枚硬币,面值分别为1元、5角、1角。这3种硬币数量不限,现在要找给顾客2元7角。请问怎样才能使得找给顾客的硬币数量最少?


最直观的策略是尽量选择面值较大的硬币,在选取硬币时可以依照以下步骤:

  1. 找出不超过2元7角面值最大的硬币,也就是1元硬币。
  2. 此时还差1元7角,找出不超过1元7角的面值最大的硬币,也就是1元硬币。
  3. 此时还差7角,找出不超过7角的面值最大的硬币,也就是5角的硬币。
  4. 此时还差2角,找出一个不超过2角的面值最大的硬币,即1角硬币。
  5. 此时还差1角,找出一个不超过1角的面值最大的硬币,即1角硬币。
  6. 找钱过程结束。

上述找钱过程遵循了贪心算法的思想。在每次找钱的时候不关注整体最优,只关注当前还亏欠顾客的钱数子问题,并以此为基础选取不超过这个钱数的面值最大的硬币,即局部最优解。按照这个策略,最终找给顾客的硬币数量就是最少的。

贪心算法每一步只考虑局部最优解,所以在处理问题的时候可能得不到整体最优解。要使贪心算法得到最优解,问题应具备以下性质:

  • 贪心选择性质

所求问题的整体最优解可以通过一系列局部最优解得到。例如在上面的找钱问题中,当前状态下最优的选择就是使找过硬币后还亏欠顾客的钱数最接近0,所以在每次找钱的时候都要选择面值尽可能大的硬币,这样硬币的总数才会更少。

  • 最优子结构性质

当一个问题的最优解包含它的子问题的最优解时,则称该问题具有最有子结构性质。上述找钱问题就是典型的具有最优子结构性质的问题。
实际应用中的许多问题都可以使用贪心算法得到最优解,即使得不到最优解,也能得到最优解的近似解。所以在解决一般性问题时,我们可以大胆尝试使用贪心算法。
哈夫曼编码算法、图算法中的最小生成树Prim算法和Kruskal算法,以及计算图的单源最短路径的Dijkstra算法等都是基于贪心算法的思想设计的。

分薄饼问题


幼儿园的老师给小朋友们分薄饼。已知每个小朋友最多只能分到一块薄饼,对于每个小朋友i,都有一个需求值gi,即能让小朋友i满足需求的薄饼的最小尺寸。同时每块薄饼j都有一个尺寸sj,如果sj≥gi,就可以将薄饼j分给小朋友i。输出最多能满足几位小朋友。


这道题可以使用穷举法解决,去除不满足要求的组合,剩下的组合数量即为本题的答案。
我们只要遵循“用尽量小尺寸的薄饼满足不同小朋友的需求值”这一贪心策略,就可以得到本题的最优解。

#include<iostream>
#include<algorithm>

int getContentedChildren(int g[], int sizeofG, int s[], int sizeofS) {
	std::sort(g, g + sizeofG);
	std::sort(s, s + sizeofS);
	//g需求下标,s饼下标,count记录满足的数量
	int i = 0, j = 0, count = 0;
	while (i < sizeofG && j < sizeofS) {
		//g需求的尺寸小于等于s
		if (g[i] <= s[j]) {
			count++;
			i++;
			j++;
		}
		else {
			//g需求的尺寸大于s,则使用更大尺寸的s去匹配
			j++;
		}
	}
	return count;
}

int main() {
	int g[] = { 1,2 };
	int s[] = { 1,2,3 };
	std::cout << getContentedChildren(g, 2, s, 3);
}

C++标准算法库提供sort()排序方法,参数为STL始末迭代器或数组左右边界。
这个算法并不难理解,需要注意的是算法的开头部分,要对数组进行排序。

集合覆盖问题


有一个广播节目,要让全美50个州的听众都能收听到,为此,我们要决定在哪些广播台播出这个节目,在每个广播台播出都需要支付费用,所以要在尽可能少的广播台播出。现有广播台名单如下:

广播台名称覆盖的州
KONEID、NV、UT
KTWOWA、ID、MT
KTHREEOR、NV、CA
KFOURNV、UT
KFIVECA、ZA

如果我们使用穷举法。假设全美有n个广播台可供选择,每种广播台都有“选择”和“不选择”两种状态,将这n个广播台中每个广播台的两种选择方式任意组合,共有 2 n 2^n 2n种组合方式。现在我们要从这 2 n 2^n 2n个集合中找出1个符合题意的集合。
这种方法简单直观,但非常耗时,时间复杂度达到了 O ( 2 n ) O(2^n) O(2n)。如果广播台数量不多,那么穷举法是可以的,可以在有限时间内找到问题的最优解。但是随着广播台的增多,消耗的时间将呈指数级增长,穷举法将不是可行的方案。

使用贪心算法进行解决。

我们通过一个简单的例子来理解贪心算法的精髓。假设现在只有9个州:ABCDEFGHI和5个广播台:12345。广播台对各州的覆盖情况如图所示:

各个广播台覆盖情况如下:

  1. 广播台1覆盖的州为ABDE
  2. 广播台2覆盖的州为EDGH
  3. 广播台3覆盖的州为GHI
  4. 广播台4覆盖的州为CFI
  5. 广播台5覆盖的州为BC

现在,使用贪心算法来解决这个问题,步骤如下:

  1. 最初,未被覆盖的州为ABCDEFGHI。
  2. 选择可覆盖最多未覆盖州的广播台。广播台1、2均可覆盖4个州,这里选择广播台1。于是未覆盖的州变为CFGHI。
  3. 接下来选择能覆盖CFGHI中州最多的广播台,我们可选择计算交集的方法来找出这个广播台。广播台3和广播台4都可以覆盖3个未覆盖的州,这里选择广播台3。于是未覆盖的州变为CF。
  4. 接下来我们选择能覆盖CF中最多州的广播台。我们选择广播台4。

至此,9个州被广播台134覆盖:

上述计算过程中,利用贪心策略逐步找出最优的广播台组合。使用贪心算法解决问题时并不从问题的整体最优解出发,而是“贪心“地着眼当下。贪心算法的时间复杂度为 O ( n 2 ) O(n^2) O(n2),其中n为广播台的数量。
首先是函数的声明部分,我们要传入所有的州,所有的广播台以及每个广播台所能覆盖到的州。我们分别使用了HashSetLinkedHashMap存储。返回值为所有最合适的广播台,我们使用HashSet存储。

//所有的州,所有的广播站以及对应的范围
public static HashSet<String> getBestBroadCasts(HashSet<String> allStatesSet, LinkedHashMap<String, HashSet<String>> broadCast) {
    HashSet<String> result = new HashSet<>();
    //待写
    return result;
}

接下来写中间部分,主要的函数体:

//外层循环控制覆盖所有周
while (allStatesSet.size() > 0) {
    HashSet<String> maxCovered = new HashSet<>();
    String tmpResult = "";
    //内层循环遍历每一个广播台,得到其覆盖的州
    for (Map.Entry<String, HashSet<String>> map : broadCast.entrySet()) {
        //得到该广播台可覆盖的州的集合
        HashSet<String> set = map.getValue();
        //计算该广播台可覆盖的州与未覆盖的州的交集
        HashSet<String> covered = new HashSet<>(set);
        covered.retainAll(allStatesSet);
        //maxCovered指向当前覆盖最广的广播台
        if (covered.size() > maxCovered.size()) {
            maxCovered = covered;
            tmpResult = map.getKey();
        }
    }
    result.add(tmpResult);
    allStatesSet.removeAll(maxCovered);
}

为了简化问题,我们使用了HashMapHashSet结构存放数据。该容器类已经封装好了交、并、补操作。当我们需要去重时,可以直接调用。

import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashMap;

public class Main {
    public static void main(String[] args) {
        //初始化allStates,存放所有的州
        String allStates[] = {"mt", "wa", "or", "id", "nv", "ut", "ca", "az"};
        //将字符串数组转换为集合HashSet
        HashSet<String> allStatesSet = new HashSet<>(Arrays.asList(allStates));
        //创建一个Hash,存放广播台和每个广播台可覆盖的州
        LinkedHashMap<String, HashSet<String>> broadCasts = new LinkedHashMap<>();
        //初始化broadCasts
        broadCasts.put("kone", new HashSet<String>(Arrays.asList("id", "nv", "ut")));
        broadCasts.put("ktwo", new HashSet<String>(Arrays.asList("wa", "id", "mt")));
        broadCasts.put("kthree", new HashSet<String>(Arrays.asList("or", "nv", "ca")));
        broadCasts.put("kfour", new HashSet<String>(Arrays.asList("nv", "ut")));
        broadCasts.put("kfive", new HashSet<String>(Arrays.asList("ca", "az")));
        //验证结果
        System.out.println(Cast.getBestBroadCasts(allStatesSet, broadCasts));
    }
}

我们将String[]数组添加到HashSet<String>集合中,需要用到Arrays工具类,需要注意的是:这个工具类结尾是有s的;这个工具类的转换结果不只是数组。

总结

这三道贪心算法都包含递归特性,处理下一步的方法与处理上一步类似:

  • 找零钱中是递归地寻找剩余零钱允许的最大硬币。
  • 分薄饼是递归地寻找最小需求(人)的最小需求(饼)。
  • 广播站是递归地寻找能覆盖剩余未覆盖州的最大广播站。

上面给的代码是用循环代替了层层调用。我们都可以尝试使用递归算法来解决。
这并非偶然,这一递归特征已经隐含在贪心算法的定义中:不断地寻找局部最优解。
如果将寻找局部最优解的过程封装为函数,在函数的结尾调用自身,寻找下一个局部最优解。那么就变成了一个递归算法。

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

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

相关文章

word转PDF后图片为何会变小?怎么解决?

有些同学反映将Word文档转换为PDF后&#xff0c;发现里面的图片居然变小了&#xff0c;这是什么原因造成的&#xff1f;该怎么解决呢&#xff1f; 先来说说原因&#xff0c;我个人认为可能是由以下原因造成的&#xff1a; 1、word插入图片后压缩“太狠”了。当你在word中插入…

GENMARK控制器维修S08S4P.D工业电脑维修

机器人GENMARK SYSTEM CONTROLLER系统控制器维修S08S4P.D工业电脑&#xff1b;晶圆转移机器人SΛΛALL CONTROLLER&#xff1b; SΛΛC1100 半导体设备机械臂GENMARK控制器等 GenMark的两大构架&#xff1a;eSensor&#xff08;电子传感&#xff09;和Elecitrowetting&#xf…

ibaq intensity 蛋白组学 蛋白质组学两个定量方法(iBAQ和LFQ)的区别及常见的标准化方法

4.MaxQuant中的Intensity&#xff0c;LFQ和iBAQ 大佬的软件&#xff0c;三种定量算法都发了文章。 Intensity是将某Protein Groups里面的所有Unique和Razor peptides的信号强度加起来&#xff0c;作为一个原始强度值。用得很少。iBAQ是在Intenstiy的基础上&#xff0c;将原始…

JUC#线程池加锁逻辑梳理

带着问题看源码 为什么要用线程池?Java是实现和管理线程池有哪些方式? 请简单举例如何使用。为什么很多公司不允许使用Executors去创建线程池? 那么推荐怎么使用呢?ThreadPoolExecutor有哪些核心的配置参数? 请简要说明ThreadPoolExecutor可以创建的是哪三种线程池呢?当…

【Web3】Web3Js高频Api

目录 Web3Js方法 初始化Web3实例 Web3Api 创建账号Api 获取余额Api 单位转换工具函数 Web3Js方法 web3.eth&#xff1a;用于与以太坊区块链和智能合约之间的交互。 web3.utils&#xff1a;包含一些辅助方法。 web3.shh&#xff1a;用于协议进行通信的P2P和广播。 web3…

1066 Root of AVL Tree (PAT甲级)

这道题类似1123题。 #include <cstdio> #include <algorithm>struct node{int key;node* left nullptr;node* right nullptr; };int N, t; node* root nullptr;int getHeight(node* r){if(!r){return 0;}return std::max(getHeight(r->left), getHeight(r-&…

【json-server】json-server安装与使用:

文章目录 一、下载安装:二、启动db.json数据及相关参数&#xff1a;三、创建json数据——db.json&#xff1a;四、修改端口号&#xff1a;五、操作数据&#xff1a;【1】常规获取&#xff1a;【2】过滤获取 Filter:【3】分页 Paginate&#xff1a;【4】排序 Sort&#xff1a;【…

使用 .editorconfig 文件来统一编程风格

做过长期开发的程序员都知道保持编程风格统一的重要性, 统一的风格能够降低各种成本. 有一句名言是咋说的来着? 代码主要是给人看的, 其次才是给电脑去运行. 但另一方面, 大家又普遍是偷懒的, 对于这些长期会受益, 但短期收益不明显甚至带来麻烦的事, 许多团队中的成员不能说抵…

产品经理进阶:硬件产品定价指南

目录 介绍 基于成本的定价 基于市场的定价 基于价值的定价 总结一下 CSDN学员课程 优惠活动通知 介绍 定价本身其实是一个相对复杂的过程。 因为有很多变量会影响到你最终的定价。 比如说&#xff1a;客户的维度、竞争对手的维度、成本的维度等等。 但是无论如何&am…

Impala3.4源码阅读笔记(三)data-cache的Store实现

前言 本文为笔者个人阅读Apache Impala源码时的笔记&#xff0c;仅代表我个人对代码的理解&#xff0c;个人水平有限&#xff0c;文章可能存在理解错误、遗漏或者过时之处。如果有任何错误或者有更好的见解&#xff0c;欢迎指正。 正文 本文顺承前文Impala3.4源码阅读笔记&a…

mac电脑上,webm格式怎么转换成mp4?

mac电脑上&#xff0c;webm格式怎么转换成mp4&#xff1f;webm格式的视频也是最近几年也越来越多的&#xff0c;小编最近就不止一次的下载到过webm格式的视频&#xff0c;很多小伙伴肯定对它还并不是很了解&#xff0c;webm是由谷歌公司所提出以及开发出来的视频文件格式&#…

matlab读取STK生成的报告

一、STK 和 Matlab的生成的图片对比 &#xff08;一&#xff09;STK图片 &#xff08;二&#xff09;Matlab图片 &#xff08;三&#xff09;STK生成的报表数据 "Time (UTCG)","Azimuth (deg)","Elevation (deg)","Range (km)" 20 J…

编译Android平台的OpenCV库并启用OpenCL及Contrib

1.下载好OpenCV与OpenCV_Contirb 版本: 4.7 编译主机系统: Ubuntu 20.04 LTS 准备环境与工具: ANDRIOD SDK 与 NDK ,CMAKE ,NINJA ,GCC,G++ ,MAKE 开始编译: ../opencv/platforms/android/build_sdk.py --extra_modules_path=../opencv_contrib/modules --no_samples_bu…

坚固型3DMAG™ A31315LOLATR-XZ-S-SE-10、A31315LOLATR-XY-S-AR-10霍尔效应磁性位置传感器IC

A31315 3D磁性位置传感器IC是完全集成的坚固型3DMAG™ 霍尔效应磁性位置传感器IC&#xff0c;主要用于支持汽车、工业和消费类应用中的各种非接触式旋转和线性位置测量。 A31315传感器IC集成了垂直和平面霍尔效应元件&#xff0c;可检测三个磁场分量&#xff08;X、Y和Z&#x…

spring如何使用junit进行测试

第一步maven的pom.xml引入坐标&#xff1a; <dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.11</version></dependency> 第二步编写测试方法&#xff1a; 第三步 定义scope类型

gpt4实现对摄像头帧缓冲区图像的LAB阈值选择界面(python-opencv)

代码全是GPT4写的&#xff0c;我就提出Prompt和要改的地方而已。 图形界面效果 代码 import cv2 import numpy as np import time from tkinter import * from PIL import Image, ImageTkclass App:def __init__(self, window, window_title, video_source0):self.window wi…

【面试】美团面试真题和答案

文章目录 前言1.线程池有几种实现方式&#xff1f;2.线程池的参数含义&#xff1f;3.锁升级的过程&#xff1f;4.i 如何保证线程安全&#xff1f;5.HashMap和ConcurrentHashMap有什么区别&#xff1f;6.Autowired和Resource区别&#xff1f;7.说说常用的设计模式8.Redis为什么这…

react中使用 websocket

react中使用 websocket&#xff0c;使用socket.io库 参考官网地址&#xff1a; https://socket.io/zh-CN/docs/v4/client-installation/#from-npm 1.安装 npm install socket.io-client2.示例代码 import React, { useEffect, useRef, useState } from "react"; i…

Mysql数据库(四) Mysql命令行客户端数据条件查询、排序、分组、聚合函数

目录 一、where条件查询 ① 查询年龄大于/等于18岁的学生记录。 ② 查询名字以张开头的学生记录。 ③ 范围查询 二、order by 排序 ① 按照name列升序排序 ② 按照name列降序排序 ③ 先按 name 降序&#xff0c;再按 age 升序排序 ④ 可以使用表达式或函数来进行排序 …

【Nginx】Nginx负载均衡

Nginx 负载均衡 1.Nginx 负载均衡1.1 官方文档1.2 默认方式&#xff1a;轮询&#xff08;round-robin&#xff09;1.3 链接最少、空闲&#xff08;least-connected&#xff09;1.4 会话持续&#xff0c;也叫ip 哈希&#xff08;Session persistence&#xff09;1.5 服务器权重&…