贪心算法 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) <==  1
                        rec(2,-1) <== 0
                        rec(5,-4) <== 0
                    rec(2,0)   <==1
                    rec(5,-3) <== 0
                rec(2,1)
                    rec(2,-1) <== 0
                    rec(5,-4) <== 0
                rec(5,-2)  <== 0
            rec(2,2)
                rec(2,0)  <== 1
                rec(5,-3) <== 0
            rec(5,-1) <== 0
        rec(2,5-2=3)
            rec(2,1)
                rec(2,-1)  <== 0
                rec(5,-4)  <== 0
            rec(5,-2)  <==0
        rec(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) <== 1
    rec(2,3) 
        rec(2,1)
            rec(2,-1) <==0
            rec(1,0)  <==1
        rec(1,1)
            rec(1,0) <==1
    rec(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 3

    public 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);
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

第八篇:隔离即力量:Python虚拟环境的终极指南

隔离即力量&#xff1a;Python虚拟环境的终极指南 1 引言 在编程的多元宇宙中&#xff0c;Python语言犹如一颗闪耀的星辰&#xff0c;其魅力不仅仅在于简洁的语法&#xff0c;更在于其庞大而繁荣的生态系统。然而&#xff0c;随着应用的增长和复杂性的提升&#xff0c;开发者们…

WinRAR经典压缩神器,高效管理您的文件烈火汉化版 v7.0.

01 软件介绍 WinRAR&#xff0c;作为一款历史悠久且广为人知的压缩文件管理工具&#xff0c;已经成为压缩软件行业的标杆产品。其提供的完整支持覆盖了RAR和ZIP文件格式&#xff0c;同时&#xff0c;该软件还拥有诸多强大的解压缩功能&#xff0c;包括但不限于固体压缩、分卷压…

链表面试题2

1&#xff0c;合并两个有序链表 我们先定义一个虚拟节点newH&#xff0c; 然后按照上图所走&#xff0c;但是当其中一个链表走空时&#xff0c;我们只需返回另一个链表即可 class Solution {public ListNode mergeTwoLists(ListNode headA, ListNode headB) {ListNode newhead…

【C++】滑动窗口:长度最小的子数组

1.题目 2.算法分析 这种题目&#xff0c;首先想到的是暴力穷举法&#xff1a; 用两层循环取遍该数组的所有子数组&#xff0c;然后找到那个最短的就可以了。 我们的滑动窗口就是对这种暴力穷举法进行优化&#xff1a; 主要是舍弃的思想&#xff0c;舍弃那些一定不可能是最终…

03_电子设计教程基础篇(软件推荐)

文章目录 前言一、通用工具软件1.输入法2.截图3.录屏4.桌面管理5.文件检索6.笔记整理7.翻译软件8.AI软件9.文件对比10.思维导图、流程框图、表格软件11.项目托管平台12.解压缩软件13.休闲娱乐软件 二、专业工具软件1.硬件工程师1.原理图、PCB设计2.原理图、PCB仿真3.PCB下单软件…

DNS、ICMP、NAT以及代理服务器

目录 1. DNS 1.1. DNS 背景 1.2. 域名简介 1.3. 域名解析过程 2. ICMP 2.1. ICMP 的功能 2.2. ICMP 的报文格式 2.3. ping 命令 2.4. traceroute 命令 3. NAT和代理服务器 3.1. NAT 技术 3.2. NAT IP转换过程 3.3. NAT 技术的缺陷 3.4. 代理服务器 3.4.1. 正向…

界面组件DevExpress Blazor UI v23.2 - 网格、工具栏功能全新升级

DevExpress Blazor UI组件使用了C#为Blazor Server和Blazor WebAssembly创建高影响力的用户体验&#xff0c;这个UI自建库提供了一套全面的原生Blazor UI组件&#xff08;包括Pivot Grid、调度程序、图表、数据编辑器和报表等&#xff09;。 DevExpress Blazor控件目前已经升级…

通信接口——时钟和信号

前言 所有接口只要抓住三个核心点就能分清&#xff1a;时钟同步和异步&#xff0c;时钟的来源&#xff0c;信号的传输方向。 一、时钟同步和异步 接口之间的交互方式存在多种形式&#xff0c;如果按照是否有公共时钟CLK的参与&#xff0c;可以分为同步传输和异步传输。 同步&…

【Gateway】网关集成Knife4j—swagger接口文档

文章目录 前言一、相关配置1.网关gateway配置①.网关增加配置 pom文件②.网关增加配置 SwaggerHandler③.网关增加配置 SwaggerResourceConfig④.网关增加配置 SwaggerConfig 2.网关过滤器 二、接口文档使用1.访问文档2.查看文档 总结 前言 在日常开发中是需要前后端联调的&am…

Liunx磁盘管理(上)

Liunx磁盘管理&#xff08;中&#xff09;-CSDN博客 目录 一.硬盘类型 机械硬盘&#xff08;HDD&#xff09; 固态硬盘&#xff08;SSD&#xff09; 二.插拔方式 1. 热插拔&#xff08;Hot Swapping&#xff09; 2. 冷插拔&#xff08;Cold Swapping&#xff09; 3. 模块…

C++仿函数周边及包装器

我最近开了几个专栏&#xff0c;诚信互三&#xff01; > |||《算法专栏》&#xff1a;&#xff1a;刷题教程来自网站《代码随想录》。||| > |||《C专栏》&#xff1a;&#xff1a;记录我学习C的经历&#xff0c;看完你一定会有收获。||| > |||《Linux专栏》&#xff1…

FileCodeBox-Lite:轻量级文件分享解决方案

在数字时代&#xff0c;文件分享是一个常见的需求&#xff0c;无论是个人用户还是企业团队。FileCodeBox-Lite提供了一个简单、高效且安全的文件分享解决方案。以下是对FileCodeBox-Lite项目的详细介绍。 项目简介 FileCodeBox-Lite是一个轻量级的文件分享系统&#xff0c;…

机器学习-06-聚类算法总结

聚类总结 1.聚类 机器学习 任务 聚类 无label的 分类 label是离散的 回归 label是连续的 2.聚类算法-kmeans 划分聚类 思想&#xff1a; D中选取k个作为初始质心 repeat 计算所有点与质心的距离&#xff0c;分到近的质心簇 更新簇之间的质心 until 质心不改 不足&#xff…

AI新篇章:全面解读ChatGPT3.5与GPT4.0的革命性融合

MidTool&#xff08;kk.zlrxjh.top&#xff09;&#xff0c;一个集成了多种先进人工智能技术的助手&#xff0c;融合了ChatGPT3.5、GPT4.0、DALLE 3和Midjourney等多个智能服务&#xff0c;提供多功能体验。下面是对这些技术的简要概述&#xff1a; **ChatGPT3.5**&#xff1a;…

dockerfile 搭建lamp 实验模拟

一 实验目的 二 实验 环境 1, 实验环境 192.168.217.88一台机器安装docker 并做mysql nginx php 三台容器 2&#xff0c; 大致框架 3&#xff0c; php php:Nginx服务器不能处理动态页面&#xff0c;需要由 Nginx 把动态请求交给 php-fpm 进程进行解析 php有三…

LeetCode 131 —— 分割回文串

阅读目录 1. 题目2. 解题思路3. 代码实现 1. 题目 2. 解题思路 首先&#xff0c;按照 LeetCode 5——最长回文子串 中的思路&#xff0c;我们先求出 d p dp dp&#xff0c;这样我们就知道了所有的子串是否是回文子串。 然后&#xff0c;我们进行一个 dfs 搜索&#xff0c;起…

Linux用户权限管理与文件权限设定

一、相关概念 1、用户与角色分类 超级用户&#xff1a;拥有对系统的最高管理权限&#xff0c;默认是root用户。 普通用户&#xff1a;只能对自己目录下的文件进行访问和修改&#xff0c;具有登录系统的权限&#xff0c;例如www用户、ftp用户等。 虚拟用户&#xff1a;也叫“…

JavaScript+B/S版云LIS系统源码ASP.NET CORE 3.1 MVC云LIS系统如何实现样本追踪的预警功能?医院云LIS检验系统源码

JavaScriptB/S版云LIS系统源码ASP.NET CORE 3.1 MVC云LIS系统如何实现样本追踪的预警功能&#xff1f;医院云LIS检验系统源码 实验室信息管理系统&#xff08;Trasen Laboratory Information Management System&#xff09;是一套专业的医疗实验室信息管理软件&#xff0c;包含…

【C++】深入理解string类

一、熟悉string类 1.1 string类的由来&#xff1a; C语音中的字符串需要我们自己管理底层空间&#xff0c;容易内存泄露。而C是面向对象语音&#xff0c;所以它把字符串封装成一个string类。 C中对于string的定义为&#xff1a;typedef basic_string string; 也就是说C中的str…

Linux 进程间通信之匿名管道

&#x1f493;博主CSDN主页:麻辣韭菜&#x1f493;   ⏩专栏分类&#xff1a;Linux知识分享⏪   &#x1f69a;代码仓库:Linux代码练习&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习更多Linux知识   &#x1f51d; 目录 前言 一. 进程间通信介绍 1.进程间通…