贪心算法大学的时候就已经学过也弄过,可能周末确实没想到写什么,就顺手学了当年学习的知识,贪心算法(也称为贪婪算法),贪心算法总是作出在当前看来最好的选择。贪心算法并不从整体最优考虑,它所作出的选择只是在某种意义上的局部最优选择。当然,希望贪心算法得到的最终结果也是整体最优的。虽然贪心算法不能对所有问题都得到整体最优解,但对许多问题它能产生整体最优解。
贪心要素
概念就是这样,如果需要详情可继续搜索获取更多信息,这个时候出现了一个问题,什么使用贪心算法?只需要满足两点即可,首先就是所求解的问题最优解可以一系列局部最优来达到解决,其次一个问题的最优解中是否包含一个子问题的最优解,也称为最优子结构,一般如果包含子问题的最优解可以通过动态算法或者贪心算法来求解。简单说就是贪心策略适用的前提是:局部最优策略能导致产生全局最优解。一般的解题框架:
从问题的某一初始解出发;
while (能朝给定总目标前进一步)
{
利用可行的决策,求出可行解的一个解元素;
}
由所有解元素组合成问题的一个可行解;详情可参考Demo.
贪心Demo
贪心Demo这哥们大一就出现在一个很经典的C语言题目中,背包问题,有一个背包,背包容量是M=150。有7个物品,物品可以分割成任意大小。要求尽可能让装入背包中的物品总价值最大,但不能超过总容量。(跟0-1背包不同,0-1需要使用到动态规划)
物品 A B C D E F G
重量 35 30 60 50 40 10 25
价值 10 40 30 50 35 40 30
解题思路:
约束条件是装入的物品总重量不超过背包容量:∑wi<=M( M=150)。
(1)根据贪心的策略,每次挑选价值最大的物品装入背包,得到的结果是否最优?
(2)每次挑选所占重量最小的物品装入是否能得到最优解?
(3)每次选取单位重量价值最大的物品,成为解本题的策略。
就是这个猜想一下然后需要证明的,能看博客的基本上也明白第三种是最优的答案,一般这种题晚上有C代码,C++代码,C#代码比较少,我没事写了写,将就看下,定义一个物品的类:
public class Product { public string Name { get; set; } public float Weight { get; set; } public float Value { get; set; } public float UnitValue { get; set; } }
控制台代码,代码比较简单:
float[] weight = new float[] { 35, 30, 60, 50, 40, 15, 20 }; float[] value = new float[] { 10, 40, 30, 50, 35, 40, 30 }; string[] name = new string[] { "A", "B", "C", "D", "E", "F", "G" }; List<Product> list = new List<Product>(); for (int i = 0; i < weight.Length; i++) { Product product = new Product(); product.Name = name[i]; product.Weight = weight[i]; product.Value = value[i]; product.UnitValue = value[i] / weight[i]; list.Add(product); } float sum = 0; foreach (Product item in list) { Console.Write(item.Name + "-" + item.UnitValue+"\t"); } List<Product> result = new List<Product>(); foreach (var product in list.OrderByDescending(item => item.UnitValue)) { sum += product.Weight; if (sum > 150) break; result.Add(product); } foreach (var product in result) { Console.Write(product.Name + "--" + product.Weight+"\t"); } Console.WriteLine(); Console.WriteLine("总价值:" + result.Sum(item => item.Value) + "\t总重量:" + result.Sum(item => item.Weight)); Console.ReadKey();
结果如下:
这样求出的最优结果是物品没有分割的情况的,有的是要求切割的,结果如何就看怎么算了;
背包算是过去的,还有一个是绕不过的会议,老师讲课的还经常拿上课的教师做例子,先看下题目:
设有N个活动时间集合,每个活动都要使用同一个资源,比如说会议场,而且同一时间内只能有一个活动使用,每个活动都有一个使用活动的开始si和结束时间fi,即他的使用区间为(si,fi),现在要求你分配活动占用时间表,即哪些活动占用该会议室,哪些不占用,使得他们不冲突,要求是尽可能多的使参加的活动最大化,即所占时间区间最大化~
看着费劲的话那就看张网络图片吧,i表示活动,S[i]开始时间,f[i]表示结束时间:
这个题目也很简单,需要想清楚的一点的就是如果两个活动需要相容,那么第二个活动的开始时间一定要大于等于第一个活动的结束时间,想清楚了这个就OK了;
int[] start = { 1, 3, 0, 5, 3, 5, 6, 8, 8, 2, 12 }; int[] end = { 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 }; List<int> list = new List<int>() { 0 }; int j = 0; for (int i = 1; i < start.Length; i++) { if (start[i] >= end[j]) { list.Add(i); j = i; } } for (int i = 0; i < list.Count(); i++) { Console.Write(list[i].ToString()+"\t"); } Console.ReadKey();
上面问题的答案是0,3,7,10;这个问题还有一个类似的兄弟问题就是如何求解同一条直线各个线段覆盖的长度,具体的就将上图的作为参考吧,详细数值看下面代码即可:
int[] start = { 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; int[] end = { 3, 5, 7, 6, 9, 8, 12, 10, 13, 15 }; int j = 0; int sum = end[0] - start[0]; for (int i = 1; i < start.Length; i++) { if (start[i] >= end[j]) { sum += end[i] - start[i]; j = i; } else { if (end[i] > end[j]) { sum += end[i] - end[j]; j = i; } } } Console.WriteLine("总的里程数:" + sum.ToString()); Console.ReadKey();
答案是13公里~小算怡情,大算伤身,强算灰灰湮灭,我就小小算算~各位,晚安~