贪心算法 Greedy Algorithm

news2024/12/23 8:08:36

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

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

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

相关文章

MySQL 运维篇

回顾基本语句&#xff1a; 数据定义语言(DDL) 这类语言用于定义和修改数据库的结构&#xff0c;包括创建、删除和修改数据库、 表、视图和索引等对象。 主要的语句关键字包括 CREATE 、 DROP 、 ALTER 、 RENAME 、 TRUNCATE 等。 create database 数据库 &#xff1b; cr…

在vue3项目中设置错误路径的统一跳转

在我们前端开发过程中&#xff0c;如果&#xff0c;访问到了错误的或者不存在的路径&#xff0c;那么会直接出现空白。在一个完整的项目中&#xff0c;应该给用户一些友好的提示&#xff0c;显示他访问到了错误的页面。 我们可以专门的创建一个错误界面的.vue文件&#xff0c;…

.360勒索病毒的威胁:如何恢复您的数据?

引言&#xff1a; 近年来&#xff0c;网络安全威胁层出不穷&#xff0c;其中.360勒索病毒以其独特的攻击方式和广泛的传播能力&#xff0c;成为了众多企业和个人面临的重大挑战。本文将对.360勒索病毒进行深入剖析&#xff0c;并探讨应对此类病毒的有效策略&#xff0c;以帮助…

编译器优化之内存对齐

编译器优化之内存对齐 前言 在工作中&#xff0c;做性能优化&#xff0c;无意间看到反汇编中有nop指令&#xff0c;大致能猜测是内存对齐相关优化&#xff0c;但不清楚相关优化选项&#xff0c;编来了兴趣&#xff0c;对编译器的内存对齐优化进行一次系统的学习和总结 由于我编…

手撕netty源码(四)- ServerBootstrap是如何监听事件的

文章目录 前言一、OP_ACCEPT事件注册1.1 bind 完成之后监听OP_ACCEPT1.2 register0注册完成之后监听OP_ACCEPT 二、事件处理在这里插入图片描述 三、总结 前言 文档中的图片如果不清晰可以直接在线看processOn processOn文档跳转 接上一篇&#xff1a;手撕netty源码&#xff0…

OceanBase开发者大会实录-陈文光:AI时代需要怎样的数据处理技术?

本文来自2024 OceanBase开发者大会&#xff0c;清华大学教授、蚂蚁技术研究院院长陈文光的演讲实录—《AI 时代的数据处理技术》。完整视频回看&#xff0c;请点击这里&#xff1e;> 大家好&#xff0c;我是清华大学、蚂蚁技术研究院陈文光&#xff0c;今天为大家带来《AI 时…

MathType如何使用LaTex代码编辑公式?MathType使用LaTex代码编辑公式教程 mathtype高仿latex

MathType专为解决数学公式输入问题打造&#xff0c;内置有自定义函数识别、国际性字符输入、拖放表达式、标签命名等丰富的功能&#xff0c;下面就来看看如何使用LaTex代码编辑公式吧。 MathType使用LaTex代码编辑公式教程 第一步&#xff1a;首先打开软件&#xff0c;并准备…

WebStorm2024版 将项目上传到gitee

目录 一、准备 WebStorm gitee 二、上传代码到Gitee 三、过程中遇到的问题 报错&#xff1a;You may want to first integrate the remote changes (e.g., git pull ...) before pushing again. 报错&#xff1a;fatal: refusing to merge unrelated histories 报错&a…

鲲鹏华为云--OBS

文章目录 1.创建桶2.上传对象3.下载对象4.分享对象5. 删除对象6.删除桶 1.创建桶 创建桶 2.上传对象 点击创建的桶–“上传对象” 拖拽本地文件或文件夹至“上传对象”区域框内添加待上传的文件。 也可以通过单击“上传对象”区域框内的“添加文件”&#xff0c;选择本地…

【SQL每日一练】统计复旦用户8月练题情况

文章目录 题目一、分析二、题解1.使用case...when..then2.使用if 题目 现在运营想要了解复旦大学的每个用户在8月份练习的总题目数和回答正确的题目数情况&#xff0c;请取出相应明细数据&#xff0c;对于在8月份没有练习过的用户&#xff0c;答题数结果返回0. 示例代码&am…

手撕C语言题典——合并两个有序数组(顺序表)

搭配食用更佳哦~~ 数据结构之顺顺顺——顺序表-CSDN博客 数据结构之顺序表的基本操作-CSDN博客 继续来做一下关于顺序表的经典算法题叭~ 前言 88. 合并两个有序数组 - 力扣&#xff08;LeetCode&#xff09; 合并数组也是力扣上关于顺序表的一道简单题&#xff0c;继续来加深…

由于找不到mfc140u.dll,无法继续执行的多种解决方法

在我们日常与计算机的密切互动中&#xff0c;或许不少用户都曾遇到过这样一个棘手的问题&#xff1a;系统突然弹出一个提示窗口&#xff0c;告知我们“找不到mfc140u.dll文件”。这个文件是Microsoft Foundation Class&#xff08;MFC&#xff09;库的一部分&#xff0c;用于支…

ASP.NET实验室预约系统的设计

摘 要 实验室预约系统的设计主要是基于B/S模型&#xff0c;在Windows系统下&#xff0c;运用ASP.NET平台和SQLServer2000数据库实现实验室预约功能。该设计主要实现了实验室的预约和管理功能。预约功能包括老师对实验室信息、实验项目和实验预约情况的查询以及对实验室的预约…

『MySQL 实战 45 讲』18 - 为什么这些SQL语句逻辑相同,性能却差异巨大

为什么这些SQL语句逻辑相同&#xff0c;性能却差异巨大 条件字段函数操作 创建一个 sql 表。该表为交易记录表&#xff0c;包含交易流水号&#xff08;tradeid&#xff09;、交易员 id&#xff08;operator&#xff09;、交易时间&#xff08;t_modified&#xff09;等字段 …

Python版本管理工具-pyenv

Pyenv是一个Python版本管理工具。 Pyenv允许用户在同一台机器上安装多个版本的Python&#xff0c;并能够轻松切换使用这些版本。 一、安装 Mac下直接使用Homebrew安装 # 更新 Homebrew 的软件列表 brew update # 安装pyenv brew install pyenv# 验证是否安装成功 pyenv -v# …

【水文】LLM 成文测试|Agent AI智能体的未来:技术进步与创新

参与活动&#xff1a;#Agent AI智能体的未来# Agent AI智能体的未来征文活动介绍 随着Agent AI智能体的智能化水平不断提高&#xff0c;它们在未来社会中的角色、发展路径以及可能带来的挑战也引起了广泛关注。快来分享一下你的看法吧~ 活动时间 4月29日-5月13日 内容要求 1、文…

《深入解析Windows操作系统》第5章节学习笔记

1、每个Windows进程都是由一个执行体进程EPROCESS结构来表示的&#xff0c;EPROCESS和相关数据结构位于系统空间&#xff0c;但是进程环境控制块PEB是个例外&#xff0c;它位于进程空间地址中&#xff08;因为它包含了一些需要由用户模式代码来修改的信息&#xff09;。对于每一…

jvm面试题30问

什么是JVM的跨平台&#xff1f; 什么是JVM的语言无关性&#xff1f; 什么是JVM的解释执行 什么是JIT? JIT&#xff1a;在Java编程语言和环境中&#xff0c;即时编译器&#xff08;JIT compiler&#xff0c;just-in-time compiler&#xff09;是一个把Java的字节码&#xff08;…

基于springboot实现英语知识应用网站系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现英语知识应用网站系统演示 摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了英语知识应用网站的开发全过程。通过分析英语知识应用网站管理的不足&#xff0c;创建了一个计算机管理英语知识应…

Mysql中索引的概念

索引相关概念 基础概念&#xff1a; 在MySQL中&#xff0c;索引是一种数据结构&#xff0c;用于加快数据库查询的速度和性能。索引可以帮助MySQL快速定位和访问表中的特定数据&#xff0c;就像书籍的索引一样&#xff0c;通过存储指向数据行的指针&#xff0c;可以快速…