贪心算法 Greedy Algorithm

1) 贪心例子

称之为贪心算法或贪婪算法,核心思想是

  1. 将寻找最优解的问题分为若干个步骤

  2. 每一步骤都采用贪心原则,选取当前最优解

  3. 因为没有考虑所有可能,局部最优的堆叠不一定让最终解最优

  v2已经不会更新v3因为v3更新过了

贪心算法是一种在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致结果是最好或最优的算法。这种算法通常用于求解优化问题,如最小生成树、背包问题等。

贪心算法的应用:

  1. 背包问题:给定一组物品和一个背包,每个物品有一定的重量和价值,要求在不超过背包容量的情况下,尽可能多地装入物品。

  2. 活动选择问题:在一个活动集合中,每次只能参加一个活动,问如何安排时间以最大化所有活动的收益。

  3. 编辑距离问题:给定两个字符串,求它们之间的最小编辑距离(即将一个字符串转换为另一个字符串所需的最少操作次数)。

  4. 网络流问题:给定一张有向图和一些起点和终点,求最大流量。

  5. 找零问题:给定一定数量的硬币和需要找零的金额,求使用最少的硬币数。

常见问题及解答:

  1. 贪心算法一定会找到最优解吗? 答:不一定。贪心算法只保证在每一步选择中都是最优的,但并不能保证整个问题的最优解。例如,背包问题中的贪心算法可能会导致最后一个物品没有被装入背包。

  2. 如何判断一个问题是否适合用贪心算法解决? 答:一个问题如果可以用递归的方式分解成若干个子问题,且每个子问题都有明确的最优解(即局部最优),那么这个问题就可以用贪心算法解决。

  3. 贪心算法的时间复杂度是多少? 答:贪心算法的时间复杂度取决于问题的规模和具体实现。一般来说,对于规模较小的问题,贪心算法的时间复杂度可以达到O(nlogn)或O(n^2);对于规模较大的问题,可能需要O(n^3)或更高。

几个贪心的例子

Dijkstra
// ...
while (!list.isEmpty()) {// 选取当前【距离最小】的顶点Vertex curr = chooseMinDistVertex(list);// 更新当前顶点邻居距离updateNeighboursDist(curr);// 移除当前顶点list.remove(curr);// 标记当前顶点已经处理过curr.visited = true;
}
  • 没找到最短路径的例子:负边存在时,可能得不到正确解

  • 问题出在贪心的原则会认为本次已经找到了该顶点的最短路径,下次不会再处理它(curr.visited = true)

  • 与之对比,Bellman-Ford 并没有考虑局部距离最小的顶点,而是每次都处理所有边,所以不会出错,当然效率不如 Dijkstra

Prim
// ...
while (!list.isEmpty()) {// 选取当前【距离最小】的顶点Vertex curr = chooseMinDistVertex(list);// 更新当前顶点邻居距离updateNeighboursDist(curr);// 移除当前顶点list.remove(curr);// 标记当前顶点已经处理过curr.visited = true;
}
Kruskal
// ...
while (list.size() < size - 1) {// 选取当前【距离最短】的边Edge poll = queue.poll();// 判断两个集合是否相交int i = set.find(poll.start);int j = set.find(poll.end);if (i != j) { // 未相交list.add(poll);set.union(i, j); // 相交}
}

其它贪心的例子

  • 选择排序、堆排序

  • 拓扑排序

  • 并查集合中的 union by size 和 union by height

  • 哈夫曼编码

  • 钱币找零,英文搜索关键字

    • change-making problem

    • find Minimum number of Coins

  • 任务编排

  • 求复杂问题的近似解

2) 零钱兑换问题

有几个解(零钱兑换 II)Leetcode 518

[1,2,5]  5  暴力递归

有解:[1, 1, 1, 1, 1]
无解:[1, 1, 1, 1, 2]
无解:[1, 1, 1, 1, 5]
有解:[1, 1, 1, 2]
无解:[1, 1, 1, 5]
无解:[1, 1, 2, 2]
无解:[1, 1, 2, 5]
无解:[1, 1, 5]
有解:[1, 2, 2]
无解:[1, 2, 5]
无解:[1, 5]
无解:[2, 2, 2]
无解:[2, 2, 5]
无解:[2, 5]
有解:[5]
4

public int rec(int index,int[] coins,int remainder){//1.情况1:剩余金额 < 0 - 无解//2.情况2:剩余金额 > 0 - 继续递归//3.情况3:剩余金额 = 0 - 有解if(remainder < 0){return 0;}else if(remainder == 0){return 1;}else{int count = 0;for(int i = index;i<coins.length;i++){count+=rec(i,coins,remainder-coins[i]);}return count;}}

那这个递归是怎么运作的呢?

/*
//第一次传入 index= 0=>1 处理硬币和剩余金额rec(1,5)    remainder > 0 所以走else逻辑 再循环中的递归是多路递归rec(1,4)rec(1,3)rec(1,2)rec(1,1)rec(1,0) <==  1rec(2,-1) <== 0rec(5,-4) <== 0rec(2,0)   <==1rec(5,-3) <== 0rec(2,1)rec(2,-1) <== 0rec(5,-4) <== 0rec(5,-2)  <== 0rec(2,2)rec(2,0)  <== 1rec(5,-3) <== 0rec(5,-1) <== 0rec(2,5-2=3)rec(2,1)rec(2,-1)  <== 0rec(5,-4)  <== 0rec(5,-2)  <==0rec(5,5-5=0) < ==1*/

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.ListIterator;/*** 零钱兑换* 可以凑成总金额所需的所有组合可能数*/
public class Leetcode518 {public int coinChange(int[] coins, int amount) {return rec(0, coins, amount, new LinkedList<>(), true);}/*** 求凑成剩余金额的解的个数** @param index     当前硬币索引* @param coins     硬币面值数组* @param remainder 剩余金额* @param stack     -* @param first     -* @return 解的个数*/public int rec(int index, int[] coins, int remainder, LinkedList<Integer> stack, boolean first) {if(!first) {//第一次不压栈stack.push(coins[index]);}// 情况1:剩余金额 < 0 - 无解int count = 0;if (remainder < 0) {print("无解:", stack);
//            if(!stack.isEmpty()){
//                stack.pop();
//            }}// 情况2:剩余金额 == 0 - 有解else if (remainder == 0) {print("有解:", stack);
//            if(!stack.isEmpty()){
//                stack.pop();
//            }count = 1;}// 情况3:剩余金额 > 0 - 继续递归else {for (int i = index; i < coins.length; i++) {count += rec(i, coins, remainder - coins[i], stack, false);}}if (!stack.isEmpty()) {stack.pop();}return count;}private static void print(String prompt, LinkedList<Integer> stack) {ArrayList<Integer> print = new ArrayList<>();ListIterator<Integer> iterator = stack.listIterator(stack.size());while (iterator.hasPrevious()) {print.add(iterator.previous());}System.out.println(prompt + print);}public static void main(String[] args) {Leetcode518 leetcode = new Leetcode518();
//        int count = leetcode.coinChange(new int[]{1, 5, 10, 25}, 41);
//        int count = leetcode.coinChange(new int[]{25, 10, 5, 1}, 41);
//        int count = leetcode.coinChange(new int[]{5, 2, 1}, 5);int count = leetcode.coinChange(new int[]{1, 2, 5}, 5);
//        int count = leetcode.change(new int[]{15, 10, 1}, 21);System.out.println(count);}
}

但是这个代码放在leetcode上面跑会超时,因为重复处理了很多次相同的操作

我们可以考虑用记忆法 & 动态规划来优化

我们也可以考虑顺序优化 ==>规模下降明显

/*
[5,2,1]   5
rec(5,5)rec(5,0) <== 1rec(2,3) rec(2,1)rec(2,-1) <==0rec(1,0)  <==1rec(1,1)rec(1,0) <==1rec(1,4)rec(1,3)rec(1,2)rec(1,1)rec(1,0) <==1*/

但即使是这样写在leetcode上也是会 StackOverflowError

class Solution {public int change(int amount, int[] coins1) {int[] coins = sort(coins1);return rec(0,coins,amount);}int rec(int index,int[] coins,int remainder){int count = 0;if(remainder == 0){return 1;}else if(remainder<0){return 0;}else{for(int i = index;i<coins.length;i++){count+=rec(index,coins,remainder);}return count;}}/***传入一个有序的数组a(从小到大排序),返回一个从大到小的数组*@param a 传入的数组(有序)*@return 返回一个数组(从大到小)*/int[] sort(int[] a){int[] temp = a;if(temp.length % 2 ==0){//数组里面的个数为偶数for(int i = 0;i<=temp.length/2;i++){int temp1 = a[i];temp[i] = temp[temp.length-1-i];temp[temp.length-1] = temp1;}}else{//数组里面的个数为奇数for(int i = 0;i<temp.length/2;i++){int temp1 = a[i];temp[i]=temp[temp.length-1-i];temp[temp.length-1-i] = temp1;}}return temp;}
}

import java.util.Arrays;public class Main {public static void main(String[] args) {int[] arr = sort(new int[]{1,2,3});System.out.println(Arrays.toString(arr));//为什么我这么选择是因为用Arrays.sort要将数组转化为Integer[]}/***传入一个有序的数组a(从小到大排序),返回一个从大到小的数组* @param a 传入的数组(有序)* @return 返回一个数组(从大到小)*/public static int[] sort(int[] a){int[] temp = a;if(temp.length%2==0){//数组里面的个数为偶数for (int i = 0; i <= temp.length/ 2; i++) {int temp1 = a[i];temp[i]=temp[temp.length-1-i];temp[temp.length - 1-i] = temp1;}}else{//数组里面的个数为奇数for (int i = 0; i < temp.length / 2; i++) {int temp1 = a[i];temp[i]=temp[temp.length-1-i];temp[temp.length - 1-i] = temp1;}}return  temp;}
}

 动态规划->会在动态规划章节说明

最优解(零钱兑换)- 穷举法 Leetcode 322
import java.util.LinkedList;
import java.util.concurrent.atomic.AtomicInteger;public class Leetcode322 {static int min = -1; // 需要的最少硬币数  2 3public int coinChange(int[] coins, int amount) {rec(0, coins, amount, new AtomicInteger(-1), new LinkedList<>(), true);return min;}// count 代表某一组合 钱币的总数 可变的整数对象public void rec(int index, int[] coins, int remainder, AtomicInteger count, LinkedList<Integer> stack, boolean first) {if (!first) {stack.push(coins[index]);}count.incrementAndGet(); // count++if (remainder == 0) {System.out.println(stack);if (min == -1) {min = count.get();} else {min = Integer.min(min, count.get());}} else if (remainder > 0) {for (int i = index; i < coins.length; i++) {rec(i, coins, remainder - coins[i], count, stack, false);}}count.decrementAndGet(); // count--if (!stack.isEmpty()) {stack.pop();}}public static void main(String[] args) {Leetcode322 leetcode = new Leetcode322();
//        int count = leetcode.coinChange(new int[]{5, 2, 1}, 5);int count = leetcode.coinChange(new int[]{25, 10, 5, 1}, 41);
//        int count = leetcode.coinChange(new int[]{2}, 3);
//        int count = leetcode.coinChange(new int[]{15, 10, 1}, 21);System.out.println(count);}
}
最优解(零钱兑换)- 贪心法 Leetcode 322

自己看看就行因为有些测试样例过不了

假定传过来的数据就是从大到小排序,因为java对int数组从大到小排序比较麻烦
public class Leetcode322 {public int coinChange(int[] coins, int amount) {int remainder = amount;int count = 0;for (int coin : coins) {while (remainder - coin > 0) {remainder -= coin;count++;}if (remainder - coin == 0) {remainder = 0;count++;break;}}if (remainder > 0) {return -1;} else {return count;}}public static void main(String[] args) {Leetcode322 leetcode = new Leetcode322();int count = leetcode.coinChange(new int[]{5, 2, 1}, 5);
//        int count = leetcode.coinChange(new int[]{25, 10, 5, 1}, 41);
//        int count = leetcode.coinChange(new int[]{2}, 3);// 问题1 没有回头,导致找到更差的解
//        int count = leetcode.coinChange(new int[]{15, 10, 1}, 21);  // 问题2 没有回头,导致无解
//        int count = leetcode.coinChange(new int[]{15, 10}, 20);  System.out.println(count);}
}

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

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

相关文章

Ps 滤镜:智能锐化

Ps菜单&#xff1a;滤镜/锐化/智能锐化 Filter/Sharpen/Smart Sharpen 智能锐化 Smart Sharpen滤镜可以用来提高图像的视觉清晰度和边缘细节&#xff0c;同时最大限度地减少常见的锐化问题如噪点和光晕等。 “智能锐化”滤镜通过自适应算法分析图像内容&#xff0c;针对不同的细…

省级财政收入、支出、第一、二、三产业增加值、工业增加值、金融业增加值占GDP比重数据(1978-2022年)

01、数据介绍 财政收支作为国家治理的基础&#xff0c;越来越受到社会各界的关注。同时&#xff0c;产业结构的优化与升级也是中国经济持续增长的关键因素。本数据对中国省级财政收入、支出占GDP的比重以及第一、二、三产业的增加值占GDP的比重和工业增加值占GDP的比重、金融业…

Pandas入门篇(二)-------Dataframe篇5(进阶)(Dataframe的时间序列Dataframe最终篇!!)(机器学习前置技术栈)

目录 概述一、pandas的日期类型&#xff08;一&#xff09;datetime64类型的特点&#xff08;二&#xff09; 时间序列的创建1.从字符串创建datetime64类型2. 整数&#xff08;Unix时间戳&#xff09;创建datetime64类型3.导入数据时直接转换 &#xff08;三&#xff09;dateti…

打印机-STM32版本 硬件部分

最终PCB EDA工程: 一、确定芯片型号 根据项目需求&#xff0c;梳理需要用到的功能&#xff0c; 电量检测&#xff1a;ADC 按键&#xff1a;IO input外部中断 LED&#xff1a;IO output 温度检测&#xff1a;ADC 电机控制&#xff1a;IO output 打印通讯&#xff1a;SPI …

C++string类使用大全

目录 温馨提示&#xff1a;这篇文章有约两万字 什么是string类&#xff1f; 一. 定义和初始化string对象 1.string的构造函数的形式&#xff1a; 2.拷贝赋值运算符 3.assign函数 二.string对象上的操作 1.读写string对象 2.读取未知数量的string对象 3.使用getline …

windows ubuntu sed,awk,grep篇:10.awk 变量的操作符

目录 62. 变量 64. 算术操作符 65. 字符串操作符 66. 赋值操作符 67. 比较操作符 68. 正则表达式操作符 62. 变量 Awk 变量以字母开头&#xff0c;后续字符可以是数字、字母、或下划线。关键字不能用作 awk 变量。 不像其他编程语言&#xff0c; awk 变量可以直接使…

实习面试之算法准备:数学题

目录 1 技巧2 例题2.1 Nim 游戏2.2 石子游戏2.3 灯泡开关 1 技巧 稍加思考&#xff0c;找到规律 2 例题 2.1 Nim 游戏 你和你的朋友&#xff0c;两个人一起玩 Nim 游戏&#xff1a; 桌子上有一堆石头。 你们轮流进行自己的回合&#xff0c; 你作为先手 。 每一回合&#xf…

SpringBoot 打包所有依赖

SpringBoot 项目打包的时候可以通过插件 spring-boot-maven-plugin 来 repackage 项目&#xff0c;使得打的包中包含所有依赖&#xff0c;可以直接运行。例如&#xff1a; <plugins><plugin><groupId>org.springframework.boot</groupId><artifact…

2024五一杯数学建模B题思路代码文章教学-交通需求规划与可达率问题

交通需求规划与可达率问题 问题总结&#xff1a; 问题一&#xff1a;在一个小型交通网络中&#xff0c;给定的起点和终点之间的交通需求需分配到相应路径上。目标是最大化任意一条路段出现突发状况时的交通需求期望可达率。 问题二&#xff1a;在一个较大的交通网络中&#xff…

负债56亿,购买理财产品遭违约,操纵虚假粉丝,流量在下滑,客户数量减少,汽车之家面临大量风险(三)

本文由猛兽财经历时5个多月完成。猛兽财经将通过以下二十二个章节、8万字以上的内容来全面、深度的分析汽车之家这家公司。 由于篇幅限制&#xff0c;全文分为&#xff08;一&#xff09;到&#xff08;十&#xff09;篇发布。 本文为全文的第七章、第八章、第九章。 目录 …

【Linux—进程间通信】共享内存的原理、创建及使用

什么是共享内存 共享内存是一种计算机编程中的技术&#xff0c;它允许多个进程访问同一块内存区域&#xff0c;以此作为进程间通信&#xff08;IPC, Inter-Process Communication&#xff09;的一种方式。这种方式相对于管道、套接字等通信手段&#xff0c;具有更高的效率&…

一文入门交叉编译

前言: 在阅读本文之前&#xff0c;你哦需要了解makefile文件的编写规则&#xff0c;这里我们推荐两篇入门: Makefile 规则-CSDN博客 Makefile 快速入门-CSDN博客 编译定义 编译是指将源代码文件&#xff08;如C/C文件&#xff09;经过预处理、编译、汇编和链接等步骤&#x…

如何从0深入PostgreSQL内核写一个执行器算子?

如何从0深入PostgreSQL内核写一个执行器算子&#xff1f; 大家好&#xff0c;我叫光城&#xff0c;昨天分享了一个主题&#xff1a;如何从0深入PostgreSQL内核写一个执行器算子&#xff1f;今天来总结一下&#xff0c;本篇文章的直播回放可以在b站观看&#xff0c;点击原文或者…

[PS小技能学习]抠图和切图

详情见视频教程&#xff1a;PS小技巧--抠图与切图 今天我们来学习如何使用PS对表情包合辑进行抠图和裁剪保存 1、首先&#xff0c;将图片导入&#xff0c;双击图层新建一个图层 2、然后点击工具栏的魔棒工具&#xff0c;再点击顶部菜单栏的添加到选区 3、点击图片的空白区域即…

IMU状态预积分功能实现与测试

IMU状态预积分功能实现与测试 前言实现IMU状态预积分类测试程序验证预积分与直接积分的效果结果 前言 预积分&#xff1a;是一种十分常见的IMU数据处理方法。 与传统的IMU运动学积分不同&#xff0c;预积分可以将一段时间内的IMU测量数据累积&#xff0c;建立预积分测量&#…

两院院士泌尿外科专家吴阶平教授

吴阶平&#xff08;1917-2011&#xff09;&#xff0c;男&#xff0c;江苏常州人&#xff0c;1933年天津汇文中学毕业&#xff0c;保送到北平燕京大学医预科&#xff0c;1937年毕业于北平燕京大学获理学士学位&#xff0c;1942年毕业于北平协和医学院获医学博士学位&#xff0c…

银行卡归属地查询API接口快速对接

银行卡归属地查询API接口指的是通过银行卡号查询该银行卡详细信息&#xff0c;包括银行卡名称、卡种、卡品牌、发卡行、编号以及归属地等信息&#xff0c;支持一千多家银行返回归属地信息&#xff0c;那么银行卡归属地查询API接口如何快速对接呢&#xff1f; 首先找到有做银行…

SpringBoot集成Kafka开发

4.SpringBoot集成Kafka开发 4.1 创建项目 4.2 配置文件 application.yml spring:application:name: spring-boot-01-kafka-basekafka:bootstrap-servers: 192.168.2.118:90924.3 创建生产者 package com.zzc.producer;import jakarta.annotation.Resource; import org.spri…

Thread类及常见方法

目录 1.Thread类概念 2.Thread的常见构造方法 3.Thread的几个常见属性 4.启动一个线程—start( ) 5.中断一个线程 1.使用自定义的变量来作为标志位 2.使用interrupt() 3.观察标志位是否被清除 6.等待一个线程-join() 7.获取当前线程引用 8.休眠当前线程 1.Thread类概…

GitHub Copilot 简单使用

因为公司安全原因&#xff0c;并不允许在工作中使用GitHub Copilot&#xff0c;所以&#xff0c;一直没怎么使用。最近因为有一些其它任务&#xff0c;所以&#xff0c;试用了一下&#xff0c;感觉还是很不错的。&#xff08;主要是C和Python编程&#xff09; 一&#xff1a;常…