贪心算法是一种基于贪心思想的算法设计方法,它通过每一步的最优选择来达到全局最优解。贪心算法的应用非常广泛,比如在图论、动态规划、网络流等领域都有着重要的应用。在接下来的博客中,我们将讲述贪心算法的基本思想、应用场景和实现方法,以及如何评估贪心算法的正确性和效率。
当我们在生活中做决策时,经常会采用贪心思想,即每一步都选择当前最优的方案,以期望最终达到全局最优解
接下来我们用可爱的哆啦A梦举例来初步了解贪心算法
有一天,小哆啦去买铜锣烧,看到商店里的铜锣烧都被装在袋子里成袋的销售,每袋100元
但是哆啦A梦身上只有200元,只够买两袋铜锣烧,细心的小哆啦发现每个袋子里装的铜锣烧数量并不是相同的,有多有少,但是价格都是相同的每袋一百元,那么哆啦A梦怎么才能吃到最多的铜锣烧呢?
要想吃到最多的铜锣烧,就需要小哆啦从超市里这么多铜锣烧当中选出和最大的两袋,我们把问题简化一下,只需要选择出铜锣烧数量最多和第二多的两袋即可。
但是如果把所有袋子都排序再选择前两个的话会花费很多时间,我们来看看哆啦A梦是怎么做的
首先,哆啦A梦拿起了离自己最近的两袋铜锣烧,发现一个袋子里有10个,一个袋子里有15个,接着哆啦A梦又拿起了第三个袋子发现里面有12个。
小哆啦想了一下,第三个袋子并没有第二个袋子中的铜锣烧多,但是比第一个袋子多,所以哆啦A梦决定放弃第一个袋子,这样他就能吃到12+15=27个铜锣烧。
哆啦A梦拿起第四个袋子,发现有17个,比之前的袋子里都要多,由于所以哆啦A梦只需要拿到最多的两个袋子就可以了,所以他放弃了有12个铜锣烧的第三个袋子,拿起了第四个袋子,这样他可以吃到15+17=32个铜锣烧
以此类推,每拿起一个袋子都和自己手里的比较一下,如果比自己手里的其中一个数量多就替换掉,如果没有手里的多就继续往后看,每次都选择当前条件下最多的两个袋子,当哆啦A梦把所有的袋子都看过一遍之后,他手上拿的一定是最多的两袋铜锣烧,这便是贪心算法的核心思想:即每一步都选择当前最优的方案,并希望通过这种局部最优解来达到全局最优解
下面我们定义一个包含两个整型数的数组来表示哆啦A梦选择的两个袋子,用一个长一些的数组来表示超市的铜锣烧:
public class TLS {
public int[] doraemon=new int[2];//哆啦A梦选择的两个袋子
public static int[] shop={10,15,12,17,6,46,33,11,23,7,8,6,4,10,23,43,20,34};
//超市的铜锣烧
public int[] choose(int[] shop){
int Max=0;//目前铜锣烧数量最多的袋子
int secMax=0;//目前铜锣烧数量第二多的袋子
for (int i : shop) {
if (i > Max) {//如果比两个都多
secMax = Max;
Max = i;
} else if (i > secMax) {//如果只比第二个多
secMax = i;
}
}
doraemon[0]=Max;
doraemon[1]=secMax;
return doraemon;
}
public static void main(String[] args) {
System.out.println(Arrays.toString(new TLS().choose(shop)));
}
}
我们定义了两个数组,doraemon表示小哆啦选择的铜锣烧,shop表示超市中的铜锣烧,在choose方法中我们每遍历到一个元素都进行一次比较
我们可以看到只需遍历一次就可以选出铜锣烧数量最多的两个袋子,这便是贪心算法的基本雏形
再举个例子,哆啦A梦结账时发现超市打折,两袋铜锣烧只需要133元,需要找回67元,哆啦A梦想要拿最少的纸币或者硬币来拿回找回的67元,那么他会怎么做呢
按照小哆啦贪心的算法,只需要每次选择面额最大的纸币/硬币,凑够67元就是最优解,那么结果真的是这样吗?我们再写一个算法按照他的逻辑来实现一下:
public class Change {
public static void main(String[] args) {
int[] coins = {50, 20, 10, 5, 1}; // 纸币和硬币的面额
int amount = 67; // 需要找回的钱数
int[] result = new int[coins.length]; // 存储每种面额的纸币和硬币的数量
int total = 0; // 找回的钱数
for (int i = 0; i < coins.length; i++) {
while (total + coins[i] <= amount) {
result[i]++;
total += coins[i];
}
}
System.out.println("找零方案为:");
for (int i = 0; i < coins.length; i++) {
if (result[i] > 0) {
System.out.println(coins[i] + "元面额的纸币或硬币:" + result[i] + "张");
}
}
}
}
在这个算法中,我们假设每种硬币或纸币的数量不限,需要找回 67元的零钱。程序中使用了一个 result
数组来存储每种面额的纸币和硬币的数量,使用了一个 total
变量来记录已经找回的钱数。在每一次循环中,程序会选择当前最大面额的纸币或硬币,直到找回的钱数等于 67 元为止。最后,程序会输出找零的方案。
接下来我们运行一下:
我们可以看到,按照贪心算法的策略,找回 67 元的最优方案一共需要的纸币数为1+1+1+2=5张
需要注意的是,贪心算法并不一定能够得到全局最优解,因为每一步的选择都是基于当前的局部最优解,而不考虑未来的影响。因此,在应用贪心算法时,需要对算法的正确性进行评估,并且需要注意算法的局限性。
比如,当纸币和硬币的面额不是一组相互整除的数时,贪心算法就不一定能得到最优解。
例如,如果coins数组中的面额为{3, 5, 7},amount为8时,贪心算法会选择一张面额为7的纸币和一张面额为1的纸币,共计两张纸币。但是最优解应该是选择两张面额为3的纸币,共计两张纸币。
所以说贪心算法并不是万能的,对于一些问题,贪心算法可能无法得到最优解,此时需要考虑其他的算法。