目录
一、问题概述
🎶有n个集装箱要装上1艘载重量为c的轮船,其中第i个集装箱的重量为wi,要求确定在装载体积不受限制的情况下,将尽可能多的集装箱装上轮船,并找出一种装载方案。
二、问题分析
🎈对该问题分析,我们可以列出两种不同的贪心策略:
🎆选择具有最大重量的集装箱
🎇选择具有最小重量的集装箱
🧨我们通过列举一系列的待装载集装箱并将他们按照重量从大到小和从小到大排序,并依据排好的顺序依次选择集装箱,找到可以装载尽可能多的集装箱的贪心策略。
✨最终,我们可以确定第二种贪心策略(选择具有最小重量的集装箱)可以装载更多的集装箱,满足要求。
三、例题演示
【例】现有五个待装载的集装箱如下表所示,需要将他们装在在一个限重12kg的轮船中,用贪心法找出满足要求的装在方案。
i | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
Wi(kg) | 8 | 4 | 2 | 5 | 7 |
注:这种问题普遍都很简单,稍微复杂一点的是为每一个集装箱规定一个value值,要求在不超过限重的条件下,选择总价值value最大的方案,而要解决这种问题,就需要用到之后要讲的动态规划法了。
就这个问题而言,我们只需要排好序,选择重量最小的就可以解决了。
仍以Java为例:
/********Huowu类*********/
public class Huowu {
int Wi;
public Huowu() {
super();
}
public Huowu(int wi) {
super();
Wi = wi;
}
public int getWi() {
return Wi;
}
public void setWi(int wi) {
Wi = wi;
}
@Override
public String toString() {
return "Huowu [Wi=" + Wi + "]";
}
}
/********Main*********/
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
Huowu h1 = new Huowu(8);
Huowu h2 = new Huowu(4);
Huowu h3 = new Huowu(2);
Huowu h4 = new Huowu(5);
Huowu h5 = new Huowu(7);
ArrayList<Huowu> al = new ArrayList<Huowu>();
al.add(h1);
al.add(h2);
al.add(h3);
al.add(h4);
al.add(h5);
// 排序
for(int i=0;i<al.size();i++) {
for(int j=i;j<al.size();j++) {
if(al.get(i).Wi > al.get(j).Wi) {
Huowu hu = al.get(i);
al.set(i, al.get(j));
al.set(j, hu);
}
}
}
int i=0;
int num=0;
do {
num+=al.get(i).Wi;
i++;
}while(num+al.get(i).Wi<=12);
System.out.println("最优装载方案:");
for(int x=0;x<i;x++) {
System.out.println(al.get(x));
}
System.out.println("以上,共"+i+"个货物,重"+num+"kg");
}
}
🍕输出结果如下:
最优装载方案:
Huowu [Wi=2]
Huowu [Wi=4]
Huowu [Wi=5]
以上,共3个货物,重11kg
四、算法正确性证明
1.贪心选择性质
证明思路:(归纳法)先提出一个全局最优解,证明可以对该解加以修改(“剪枝”),将以贪心选择为基础的解加上,该解也为最优解。
该证明与会场安排问题的证明相似,所以这次我们不再依据例题,如果要看更加具体的证明解释可以点击这里,跳转到会场安排问题的算法正确性证明。
🚗证明:设最优解的第一个选择是集装箱j,而贪心算法选择的是集装箱i。根据贪心选择原则,Wi<Wj,即集装箱i的重量小于集装箱j的重量。现在,我们将集装箱j替换为集装箱i,由于Wi<Wj,所以替换后的总重量不会超过原来的总重量,即如果第一个选择集装箱j的解是最优解,那么第一个选择集装箱i的解也是最优解,故此可以证明存在从贪心选择开始的最优解。
2.最优子结构性质
证明思路:(反证法)现提出一个全局最优解,去掉其中的某一部分,剩下的解在局部也是最优的。
🚌证明:设全局最优解为OPT,去掉其中的某一部分X得到OPT',设在去掉X部分后所剩下的部分的局部最优解为OPT*,则OPT'<OPT*,之后我们加上先前去掉的X部分可得OPT'+X<OPT*+X ,而OPT'+X 等价于 OPT,即全局最优解OPT<OPT*+X,与假设相矛盾,即OPT'即为局部的最优解,进而证明满足最优子结构性质。
五、其他版本
🎊以上例题我们展示了Java版本代码,为辅助各位读者更好的学习,下面分别是C语言、C++以及Python实现上述算法。(语言的转换由ChatGPT完成,如发现错误,欢迎评论指正。)
🍔C语言版本:
#include <stdio.h>
typedef struct {
int Wi;
} Huowu;
int main() {
Huowu h[] = {{8}, {4}, {2}, {5}, {7}};
int n = sizeof(h) / sizeof(h[0]);
// Sorting
for (int i = 0; i < n; i++) {
for (int j = i; j < n; j++) {
if (h[i].Wi > h[j].Wi) {
Huowu temp = h[i];
h[i] = h[j];
h[j] = temp;
}
}
}
int i = 0, num = 0;
do {
num += h[i].Wi;
i++;
} while (num + h[i].Wi <= 12);
printf("最优装载方案:\n");
for (int x = 0; x < i; x++) {
printf("Huowu [Wi=%d]\n", h[x].Wi);
}
printf("以上,共%d个货物,重%dkg\n", i, num);
return 0;
}
🌭C++版本:
#include <iostream>
#include <vector>
#include <algorithm>
class Huowu {
public:
int Wi;
Huowu(int wi) : Wi(wi) {}
};
int main() {
std::vector<Huowu> al = {8, 4, 2, 5, 7};
// Sorting
std::sort(al.begin(), al.end(), [](const Huowu& a, const Huowu& b) {
return a.Wi < b.Wi;
});
int i = 0, num = 0;
do {
num += al[i].Wi;
i++;
} while (num + al[i].Wi <= 12);
std::cout << "最优装载方案:" << std::endl;
for (int x = 0; x < i; x++) {
std::cout << "Huowu [Wi=" << al[x].Wi << "]" << std::endl;
}
std::cout << "以上,共" << i << "个货物,重" << num << "kg" << std::endl;
return 0;
}
🍿Python版本:
class Huowu:
def __init__(self, Wi):
self.Wi = Wi
h = [Huowu(8), Huowu(4), Huowu(2), Huowu(5), Huowu(7)]
# Sorting
h.sort(key=lambda x: x.Wi)
#注:这里使用了lambda函数作为排序的关键字。
#Lambda函数“lambda x: x.Wi”表示对于每个Huowu对象x,使用其Wi属性的值作为排序的依据。
#除了这种方法,我们还可以使用operator.attrgetter函数,如:
#from operator import attrgetter
#h.sort(key=attrgetter('Wi'))
#在这里,我们使用了operator模块中的attrgetter('Wi')作为排序的关键字,
#它创建了一个可调用对象用于获取Wi属性,并将其用作排序的关键字。
i = 0
num = 0
while num + h[i].Wi <= 12:
num += h[i].Wi
i += 1
print("最优装载方案:")
for x in range(i):
print(f"Huowu [Wi={h[x].Wi}]")
print(f"以上,共{i}个货物,重{num}kg")