算法设计与分析:贪心算法
贪心选择性:每一步贪心选出来的一定是原问题的最优解的一部分
最优子结构:每一步贪心选完后会留下子问题,子问题的最优解和贪心选出来的解可以凑成原问题的最优解
好文推荐:
https://www.cnblogs.com/whsu/p/13906447.html
https://blog.csdn.net/ZhifanSk/article/details/105217963
磁带最优存储问题
问题描述
有n 个程序{1,2,…, n }要存放在长度为L的磁带上。程序i存放在磁带上的长度是Li, 1<= i<= n。这n 个程序的读取概率分别是p1,p2,…,pn,且pi+p2+…+pn = 1。如果将这n个程序按 i1,i2,…,in 的次序存放,则读取程序tr 所需的时间tr=c(Pi1Li2+Pi2Li2+…+PirLir)。这n 个程序的平均读取时间为t1+t2+…+tn。磁带最优存储问题要求确定这n个程序在磁带上的一个存储次序,使平均读取时间达到最小。
由文件 input. txt给出输入数据。第1行是正整数n,表示文件个数。接下来的n行中,每行有2个正整数a和b,分别表示程序存放在磁带上的长度和读取概率。实际上第k个程序的读取概率为ak/∑ai,对所有输入均假定c=1。
将计算的最小平均读取时间输出到文件 output. txt
输入文件示例 输出文件示例
5 85.6193
71 872
46 452
9 265
73 120
35 85
算法描述
根据tr的计算公式,假设n个程序按 i1,i2,…,in 的次序存放,
平均读取时间 = n*P1*L1+(n-1)*P2*L2+(n-2)*P3*L3+…+Pn*Ln
由此可知要使平均读取时间最小,即需要使Pi*Li最小的程序放在最前面,它在计算式子中被算的次数最多。
贪心求解过程:
- 计算每个程序的长度和读取概率的乘积pi*li
- 对序列按照pi*li升序排序
- 求出n个程序的最小平均读取时间
贪心选择性质:磁带问题有一个以贪心选择开始的最优解
- 命题:磁带问题有一个最优解以贪心选择开始,即包含最小的l*p,即t0。
- E数组是将磁带上的程序以l*p非递减排列得到的最优解,E = {t0,t1,t2……tn-1} 。
证明:对于问题p=t0+t2+..+tn-1,因为问题的解包含了所有的tr,如果只存在一个最优解E,那么序列的顺序是特定的, 最优解E一定包含了t0,问题转换为证明最优解唯一。
- 假设除E= {t0,t1,…ti……tj…tn-1} 之外还存在一个最优解E’= {t0,t1,…tj……ti…tn-1},那么访问的时间TE’<=TE,做差TE’-TE=(tj-ti)+(tj-ti)(j-i-1)+{(ti-tj)+(tj-ti)(j-i)}= (tj-ti)(2j-2*i)+1,由于tj>=ti,所以TE’>=TE
①若TE’=TE,二者等价于一个最优解
②若TE’>TE,与假设矛盾,所以说问题的最优解唯一
因为只存在一个最优解E,那么序列的顺序是特定的,最优解E一定包含了t0 - 结论:磁带问题有一个以贪心选择开始的最优解,即以t0开始
关键代码
Scanner scanner = new Scanner(System.in);
int N = scanner.nextInt();
int[] l = new int[N];
int[] p = new int[N];
double[] w = new double[N];
int sum = 0;
for (int i = 0; i < N; i++) {
l[i] = scanner.nextInt();
p[i] = scanner.nextInt();
sum += p[i];
}
for (int i = 0; i < N; i++) {
w[i] = (double) p[i] * l[i] / sum;
}
Arrays.sort(w);
double res = 0;
for (int i = 0; i < N; i++) {
res += (N - i) * w[i];
}
System.out.println(res);
结果分析
输入:
5
71 872
46 452
9 265
73 120
35 85
输出:85.61928651059085
输入:
6
71 800
46 402
19 365
37 160
53 95
33 100
输出:95.34027055150884
算法时间复杂度:\(O(nlogn)\)
n表示程序的个数,算法的主要时间花费在排序上
算法空间复杂度:\(O(n)\)
该贪心算法需要一个长度为n的数组存储每个程序的长度和读取概率的乘积pi*li
汽车加油问题
问题描述
一辆汽车加满油后可行驶n公里。旅途中有若干个加油站。设计一个有效算法,指出应在哪些加油站停靠加油,使沿途加油次数最少。并证明算法能产生一个最优解。
对于给定的n和k个加油站位置,计算最少加油次数。
输入数据的第一行有2 个正整数n和k(n≤5000,k≤1000),表示汽车加满油后可行驶n公里,且旅途中有k个加油站。接下来的1 行中,有k+1 个整数,表示第k个加油站与第k-1 个加油站之间的距离。第0 个加油站表示出发地,汽车已加满油。第k+1 个加油站表示目的地。
将计算出的最少加油次数输出。如果无法到达目的地,则输出“No Solution!”
输入文件示例 输出文件示例
7 7 4
1 2 3 4 5 1 6 6
算法描述
最少加油次数,意味着每次加油都尽可能跑的远一些,直至跑不动了(跑不到下一个加油站),才在本加油站加油。这是一个贪心策略。
贪心选择性质:设在加满油后可行驶的N千米这段路程上任取两个加油站A,B,且A相较于B距离起始点更近,则若在B加油不能到达终点,那么在A加油也一定不能到达终点。假设A与B的间距为x,则在B点加油可行驶的路程比在A点加油可行驶的路程要长x千米。所以根据贪心选择策略,为使得加油次数最少就会尽可能选择更远的加油站去加油。因此,加油次数最少满足贪心选择性质,该算法能产生一个最优解。
关键代码
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int k = scanner.nextInt();
int[] dis = new int[k + 1];
for (int i = 0; i < dis.length; i++) {
dis[i] = scanner.nextInt();
if (dis[i] > n) {
System.out.println("No Solution");
return;
}
}
int res = 0;
int oil = n;
int i = 0;
while (i < dis.length) {
//i是目标
if (oil - dis[i] < 0) {
//到第不了第i个加油站,需要立马加油
res++;
oil = n;
continue;
}
oil -= dis[i];
i++;
}
System.out.println(res);
结果分析
输入:
7 7
2 3 6 6 6 6 6 6
输出:6
输入:
7 7
1 2 3 4 5 1 6 6
输出:4
输入:
7 7
7 7 7 7 7 7 7 7
输出:7
算法时间复杂度:\(O(n)\) ,n表示加油站的个数-1
算法空间复杂度:\(O(1)\)