贪心算法
这是篇很老的博客了,是我刚学贪心的时候写的笔记。今天无意间翻到,就分享一下。
内容没写多少,主要是看例子,这几个题都比较经典,都是当时我参考大佬的博客自己研究了写的(都是运行了测试过的,但时间长了,我不保证都对)。
算法描述 贪心选择是采用从顶向下(问题拆分)、以迭代的方法做出相继选择(循环处理拆分出的部分),每做一次贪心选择就将所求问题简化为一个规模更小的子问题(找出这个部分的贪心最优解)。
贪心算法追求的不是问题整体上的最优解,只是迭代找到各部分贪心较优解(最关注的那一项),并将循环结果作为较为满意的解(贪心算法的每一次操作都对结果产生直接影响)。
找到最优解要穷举所有可能,因此会消耗大量时间。而贪心算法常以每一个迭代的情况为基础做最优选择,而不考虑整体情况(贪心算法不需要回溯),因此可以快速找到较优解。
算法过程
建立数学模型来描述问题;
把求解的问题分成若干个子问题;
对每一子问题求解,得到子问题的局部最优解;
把子问题的解局部最优解合成原来解问题的一个解。
1 2 3 4 5 6 fat = 问题 sub = 拆分(fat) for a_part in sub: res.append(贪心最优解(a_part)) print("问题的较优解" , res)
算法实例
背包问题
有一个背包,背包容量是M=150kg。有几个物品,。要求尽可能让装入背包中的物品总价值最大,但不能超过总容量。
(1)物品**不可以**分割成任意大小
这里以物体的价值与质量比作为标准,每次迭代都找比值较优的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 #include <iostream> #include <vector> #include <algorithm> #define MAX 7 #define WEIGHT_OF_BAG 150 using namespace std ;struct Node { char id; double weight; double value; double value_pre_weight; bool chosen; }; int main () { Node arr[MAX]; double weights[MAX] = {35 , 30 , 60 , 50 , 40 , 15 , 20 }; double values[MAX] = {10 , 40 , 30 , 50 , 35 , 40 , 30 }; for (int i = 0 ; i < MAX; ++i) { arr[i].id = i; arr[i].weight = weights[i]; arr[i].value = values[i]; arr[i].value_pre_weight = values[i]/weights[i]; arr[i].chosen = false ; } double total_weight = 0 , total_value = 0 ; vector <Node> chosen_list; while (total_weight < WEIGHT_OF_BAG) { double greatest = 0.0 ; int choice = 0 ; for (int i = 0 ; i < MAX; ++i) { if (arr[i].chosen == false && total_weight + arr[i].weight <= 150 && arr[i].value_pre_weight >= greatest) { greatest = arr[i].value_pre_weight; choice = i; } } arr[choice].chosen = true ; chosen_list.push_back(arr[choice]); total_weight += arr[choice].weight; total_value += arr[choice].value; greatest = arr[choice].value_pre_weight; } cout << "Chosen:\n" ; for (auto item : chosen_list) cout << "\tThe " << int (item.id) << endl ; cout << "Total weight:\t" << total_weight << "\nTotal value:\t" << total_value << endl ; return 0 ; }
(2)物品可以 分割成任意大小 这里以物体质量为标准 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <iostream> #include <cstdio> using namespace std ;const int N = 4 ;void bag (double m, double *v, double *w, double *x) ;int main () { double maxWeight = 50 ; double wts[] = { 10 , 30 , 20 , 5 }; double vls[] = {200 , 400 , 100 , 10 }; double res[N] = {0 }; bag(maxWeight, vls, wts, res); for (int i=0 ; i<N; ++i) printf ("--[%d]: %lf\n" , i, res[i]); return 0 ; } void bag (double m, double *v, double *w, double *x) { int i; for (i=0 ; i < N; ++i) { if (m - w[i] < 0 ) break ; x[i] = 1.0 ; m -= w[i]; } if (i < N) x[i] = m/w[i]; }
换零钱问题 假设1元、2元、5元、10元、20元、50元、100元的纸币分别有c0, c1, c2, c3, c4, c5, c6张。现在要用这些钱来支付K元,至少要用多少张纸币?用贪心算法的思想,很显然,每一步尽可能用面值大的纸币即可。在日常生活中我们自然而然也是这么做的。在程序中已经事先将Value按照从小到大的顺序排好。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 #include <iostream> #include <algorithm> #include <vector> using namespace std ;struct Chosen { int value; int count; }; const int N = 7 ;int Count[N] = {3 , 0 , 2 , 1 , 0 , 3 , 5 };int Value[N] = {1 , 2 , 5 , 10 , 20 , 50 , 100 };vector <Chosen> got;int solve (int money) { int num = 0 ; for (int i = N - 1 ; i >= 0 ; i--) { int c = min(money/Value[i], Count[i]); money = money - c * Value[i]; num += c; Chosen temp; temp.value = Value[i]; temp.count = c; got.push_back(temp); } if (money > 0 ) num = -1 ; return num; } int main () { int money; cout << "Enter money: " ; cin >> money; int res = solve(money); if (res != -1 ) { cout << "\n共" << res << " 张。" << endl ; for (auto i : got) printf ("%2d个%2d元\t" , i.count, i.value); cout << endl ; } else cout << "\nCan't solve!" << endl ; return 0 ; }
活动安排 有n个需要在同一天使用同一个教室的活动a1,a2,…,an,教室同一时刻只能由一个活动使用。每个活动ai都有一个开始时间si和结束时间fi 。一旦被选择后,活动ai就占据半开时间区间[si,fi)。如果[si,fi]和[sj,fj]互不重叠,ai和aj两个活动就可以被安排在这一天。该问题就是要安排这些活动使得尽量多的活动能不冲突的举行。例如下图所示的活动集合S,其中各项活动按照结束时间单调递增排序。
贪心策略应该是每次选取结束时间最早的活动。直观上也很好理解,按这种方法选择相容活动为未安排活动留下尽可能多的时间。这也是把各项活动按照结束时间单调递增排序的原因。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #include <cstdio> #include <iostream> #include <algorithm> using namespace std ;int N;struct Action { int start; int end; } act[100 ]; bool cmp (struct Action a, struct Action b) { return a.end < b.end; } int greedy () { int num = 0 , i = 0 ; for (int j = 1 ; j <= N; j++) { if (act[j].start >= act[i].end) { i = j; printf ("\t%2d : From %2d To %2d\n" , i, act[i].start, act[i].end); num++; } } return num; } int main () { int t; cout << "几组测试数据: " ; scanf ("%d" , &t); while (t--) { cout << " 共几个活动: " ; scanf ("%d" , &N); for (int i = 1 ; i <= N; i++) { cout << "活动" << i <<"的始末:" ; scanf ("%d%d" , &act[i].start, &act[i].end); } act[0 ].start = act[0 ].end = -1 ; sort(act + 1 , act + N + 1 , cmp); int res = greedy(); cout << "共可安排:" << res << endl ; } return 0 ; }
机器安排 n个作业组成的作业集,可由m台相同机器加工处理。要求给出一种作业调度方案,使所给的n个作业在尽可能短的时间内由m台机器加工处理完成。作业不能拆分成更小的子作业;每个作业均可在任何一台机器上加工处理。
首先将n个作业从大到小排序,然后依此顺序将作业分配给空闲的处理机。也就是说从剩下的作业中,选择需要处理时间最长的,然后依次选择处理时间次长的,直到所有的作业全部处理完毕,或者机器不能再处理其他作业为止。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include <iostream> #include <algorithm> #include <vector> using Work = int ;using namespace std ;int main () { int m=0 , n=0 ; vector <Work> arr; cout << "machines: " ; cin >> m; cout << "Works: " ; cin >> n; vector <Work> mch (m, 0 ) ; for (int i = 0 ; i < n; ++i) { Work temp; printf ("[%d].length: " , i); cin >> temp; arr.push_back(temp); } cout << "-----\n" ; sort(arr.begin(), arr.end(), [](auto x,auto y){return x > y;}); for (int i = 0 ; i < n; ) { int flag = 1 ; while (flag) { for (int j = 0 ; j < m; ++j) { if (mch[j] <= 0 ) { printf ("Use (%d) to process [length_%d];\n" , j, arr[i]); mch[j] = arr[i]; i++; flag = 0 ; } } int cnt = 0 ; for (auto &p : mch) p--; } } return 0 ; }