贪婪算法可以求解的经典问题包括货箱装船问题、背包问题、拓扑排序问题、二分覆盖问题、最短路径问题、最小代价生成树等问题。对于图相关的问题,这次没有学习,兴趣不大。
[概念]
1,最优化问题( optimization problem),每个最优化问题都包含一组限制条件( c o n s t r a i n t)和一个优化函数( optimization function),符合限制条件的问题求解方案称为可行解( feasible solution),使优化函数取得最佳值的可行解称为最优解(optimal solution)。
2,贪婪算法(greedy method):采用逐步构造最优解的方法。在每个阶段,都作出一个看上去最优的决策(在一定的标准下)。决策一旦作出,就不可再更改。作出贪婪决策的依据称为贪婪准则(greedy criterion)。
3,注意,贪婪算法的关键:1)每步贪婪;2)不回溯;3)选择的规则(criterion)非常重要,不正确的规则可能得不到结果、不优化或者不能作为贪婪的求解对象;4)必定有解,但是可能不会得到最优解。
[具体案例]
1,渴婴问题(Thirsty baby)
婴儿可得到n 种不同的饮料,每一种饮料赋予一个满意度值,而此婴儿需要t 盎司的饮料来解渴,那么,需要饮用n种不同的饮料各多少量才能满足婴儿解渴的需求呢?
->解:按照满意度大小排序,优先选择满意度高的。这个基本上就是一个通用的规则,其他的案例基本也适用:首先排序;然后优先选择最可能达到目标的解,而不管以后会怎么样。
2,装载问题(Loading problem)
有一艘大船准备用来装载货物。所有待装货物都装在货箱中且所有货箱的大小都一样,但货箱的重量都各不相同。设第i 个货箱的重量为wi(1≤i≤n),而货船的最大载重量为c,我们的目的是在货船上装入最多的货物。
3,找零钱(Change making)
一个小孩买了价值少于1美元的糖,并将1美元的钱交给售货员。售货员希望用数目最少的硬币找给小孩。假设提供了数目不限的面值为2 5美分、1 0美分、5美分、及1美分的硬币。售货员分步骤组成要找的零钱数,每次加入一个硬币。选择硬币时所采用的贪婪准则如下:每一次选择应使零钱数尽量增大。为保证解法的可行性(即:所给的零钱等于要找的零钱数),所选择的硬币不应使零钱总数超过最终所需的数目。
4,机器调度(Machine schedule)
现有n 件任务和无限多台的机器,任务可以在机器上得到处理。每件任务的开始时间为si,完成时间为fi ,si < fi 。[si , fi ] 为处理任务i 的时间范围。两个任务i,j 重指两个任务的时间范围区间有重叠,而并非是指i,j 的起点或终点重合。例如:区间[ 1,4 ]与区间[ 2,4 ]重叠,而与区间[ 4,7 ]不重叠。一个可行的任务分配是指在分配中没有两件重叠的任务分配给同一台机器。因此,在可行的分配中每台机器在任何时刻最多只处理一个任务。最优分配是指使用的机器最少的可行分配方案。
贪婪准则:根据欲分配任务的开始时间,若此时有旧的机器可用,则将任务分给旧的机器。
5,0/1背包问题(0/1 Knapsack problem)
在0 / 1背包问题中,需对容量为c 的背包进行装载。从n 个物品中选取装入背包的物品,每件物品i 的重量为wi ,价值为pi 。对于可行的背包装载,背包中物品的总重量不能超过背包的容量,最佳装载是指所装入的物品价值最高,即n ?i=1pi xi 取得最大值。约束条件为n ?i =1wi xi≤c 和xi?[ 0 , 1 ] ( 1≤i≤n)。
->解:按照pi/wi的比值进行排序,然后贪婪。但是背包问题是一个N/P难题,可以考虑使用k阶优化方法找到相对满意的解。
6,拓扑排序(Topological sorting)
任务的执行是连续进行的,我们可根据所建议的顺序来装配。在由任务建立的有向图中,边( i, j)表示在装配序列中任务i 在任务j 的前面,具有这种性质的序列称为拓扑序列(topological orders或topological sequences)。根据任务的有向图建立拓扑序列的过程称为拓扑排序(topological sorting)。
->解:贪婪准则来选择顶点:从剩下的顶点中,选择顶点w,使得w 不存在这样的入边( v,w),其中顶点v 不在已排好的序列结构中出现。
[硬币找零代码]
- #include <stdlib.h>
- const int SIZE = 15;
- int coins[SIZE] = {10, 25, 5, 5, 10, 25, 25, 5, 1,1,1, 5, 10,5,10};
- // compare used in qsort() for coins[]
- int _cdecl comp(const void* left, const void* right )
- {
- if (*(int*)left < *(int*)right )
- return 1;
- else if (*(int*)left == *(int*)right )
- return 0;
- else
- return -1;
- }
- // greedy for coin-change. returns the total used coins
- int change(int problem, int result[])
- {
- int i = 0;
- int j = 0;
- while ( (problem > 0) && ( i < SIZE ))
- {
- while ( i < SIZE ) // coins can not be re-used
- {
- if (problem >= coins[i])
- {
- result[j] = coins[i];
- problem -= result[j];
- j++;
- i++;
- break;
- }
- i++;
- }
- }
- if ( problem > 0)
- return 0;
- return j;
- }
- // for test
- int main(int argc, char* argv[])
- {
- // sort the input
- qsort(coins, sizeof(coins)/sizeof(int),sizeof(int), comp);
- for ( int i = 0; i < 10; i++)
- {
- int problem = 0;
- printf("Problem:>>>/n");
- scanf("%d", &problem);
- int result[SIZE] = {0};
- int numbers = change(problem, result);
- printf("Solution:>>>/n");
- if ( numbers > 0)
- {
- numbers--;
- for ( ; numbers >= 0; numbers-- )
- printf("%d,", result[numbers]);
- }
- else
- printf("no solution!");
- printf("/n");
- }
- return 0;
- }
[其他]
待扩展