算法设计与分析:贪心算法

贪心选择性:每一步贪心选出来的一定是原问题的最优解的一部分
最优子结构:每一步贪心选完后会留下子问题,子问题的最优解和贪心选出来的解可以凑成原问题的最优解

好文推荐:
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)\)

友情,恋爱,推理,因为有当初的约定才努力不懈。
原文地址:https://www.cnblogs.com/ziyanblog/p/15685273.html