【基础算法】贪心算法

news/2024/7/5 4:16:01

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

贪心算法的基本思想

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


假设有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://lihuaxi.xjx100.cn/news/1313840.html

相关文章

伪类元素content,icon变形倾斜问题

![在这里插入图片描述](https://img-blog.csdnimg.cn/b58d128a80fd4a069a5e47cf2e87e256.png 检查发现原本设置了一个 font-style 为 italic&#xff0c;为倾斜样式 解决办法&#xff1a; font-style: normal;

seatunnel数据同步参数优化

执行引擎: Flink集群 数据同步脚本(tidb到tidb) env {job.name = tidb2tidbexecution.parallelism = 1job.mode = "BATCH"checkpoint.interval = 10000 } source {Jdbc {url = "jdbc:mysql://192.168.21.110:4000/test?useUnicode=true

骨传导耳机音质怎么样,推荐几款音质表现不错的骨传导耳机

最近体验了几款骨传导耳机&#xff0c;分享下我的使用感受。首先说一下为什么要选择骨传导耳机&#xff0c;我之前是使用入耳式耳机&#xff0c;戴久了耳朵会疼&#xff0c;而且晚上睡觉不能戴。于是就考虑骨传导耳机&#xff0c;因为骨传导耳机在传声的过程中不需要经过耳膜&a…

分布式中灰度方案就该这样设计!

一、背景简介 分布式系统中会存在这样的开发场景&#xff0c;不同需求可能涉及到对同一个服务的开发&#xff0c;那么该服务在研发期间就会存在多个版本并行的状态&#xff0c;为了保持不同版本之间的隔离性&#xff0c;验收需要将请求路由到指定版本号的服务上处理&#xff1…

AIOps智能运维应用案例 | 人行某中心加速完成信创国产化替代

本案例来自擎创落地案例集——某全国性金融服务组织 一、案例背景 1.政策需求 近些年&#xff0c;国内金融机构信创数字化转型的浪潮不断翻涌&#xff0c;信创作为主要的产业发展战略之一&#xff0c;已成为国内经济发展的新动能&#xff0c;是各行各业开拓创新的新风向标。【…

vue 打包去除console.log

vue 打包去除console.log 1、插件安装 npm install babel-plugin-transform-remove-console --save-dev2、声明插件&#xff0c;打开项目中 babel.config.js 文件 const prodPlugins [] if (process.env.NODE_ENV production) {prodPlugins.push(transform-remove-console…

浅析Lambda架构

大家好&#xff0c;今天我们来介绍一个用于亿级实时数据分析架构Lambda架构。 Lambda架构 Lambda架构&#xff08;Lambda Architecture&#xff09;是由Twitter工程师南森马茨&#xff08;Nathan Marz&#xff09;提出的大数据处理架构。这一架构的提出基于马茨在BackType和Tw…

【解决】Pyinstaller打包报错IndexError: tuple index out of range

问题 这个问题主要是在Python3.7以上的版本中遇到&#xff0c;用pyinstaller打包的时候发现报错 /usr/local/lib/python3.10/dis.py argval const_list[const_index], IndexError: tuple index out of range解决方案 vim 进入报错的文件&#xff0c;/usr/local/lib/python…