贪心算法程序
❶ 求一个算法(贪心算法)
贪心算法
一、算法思想
贪心法的基本思路:
——从问题的某一个初始解出发逐步逼近给定的目标,以尽可能快的地求得更好的解。当达到某算法中的某一步不能再继续前进时,算法停止。
该算法存在问题:
1. 不能保证求得的最后解是最佳的;
2. 不能用来求最大或最小解问题;
3. 只能求满足某些约束条件的可行解的范围。
实现该算法的过程:
从问题的某一初始解出发;
while 能朝给定总目标前进一步 do
求出可行解的一个解元素;
由所有解元素组合成问题的一个可行解;
二、例题分析
1、[背包问题]有一个背包,背包容量是M=150。有7个物品,物品可以分割成任意大小。
要求尽可能让装入背包中的物品总价值最大,但不能超过总容量。
物品 A B C D E F G
重量 35 30 60 50 40 10 25
价值 10 40 30 50 35 40 30
分析:
目标函数: ∑pi最大
约束条件是装入的物品总重量不超过背包容量:∑wi<=M( M=150)
(1)根据贪心的策略,每次挑选价值最大的物品装入背包,得到的结果是否最优?
(2)每次挑选所占重量最小的物品装入是否能得到最优解?
(3)每次选取单位重量价值最大的物品,成为解本题的策略。 ?
值得注意的是,贪心算法并不是完全不可以使用,贪心策略一旦经过证明成立后,它就是一种高效的算法。
贪心算法还是很常见的算法之一,这是由于它简单易行,构造贪心策略不是很困难。
可惜的是,它需要证明后才能真正运用到题目的算法中。
一般来说,贪心算法的证明围绕着:整个问题的最优解一定由在贪心策略中存在的子问题的最优解得来的。
对于例题中的3种贪心策略,都是无法成立(无法被证明)的,解释如下:
(1)贪心策略:选取价值最大者。反例:
W=30
物品:A B C
重量:28 12 12
价值:30 20 20
根据策略,首先选取物品A,接下来就无法再选取了,可是,选取B、C则更好。
(2)贪心策略:选取重量最小。它的反例与第一种策略的反例差不多。
(3)贪心策略:选取单位重量价值最大的物品。反例:
W=30
物品:A B C
重量:28 20 10
价值:28 20 10
根据策略,三种物品单位重量价值一样,程序无法依据现有策略作出判断,如果选择A,则答案错误。
所以需要说明的是,贪心算法可以与随机化算法一起使用,具体的例子就不再多举了。(因为这一类算法普及性不高,而且技术含量是非常高的,需要通过一些反例确定随机的对象是什么,随机程度如何,但也是不能保证完全正确,只能是极大的几率正确)
================================
三个经典的贪心算法
有人说贪心算法是最简单的算法,原因很简单:你我其实都很贪,根本不用学。有人说贪心算法是最复杂的算法,原因也很简单:这世上贪的人太多了,那轮到你我的份?
不论难度如何,贪心算法都是一个很重要的算法,我在网上N多Online Judge中的题目中,总结了三类较为常见,也十分经典的贪心算法,发布在这儿Just For Fun。
(注:由于没有现成的名字可用,这三种类型贪心算法的名字都是我自己取的,如果你听着别扭,请见谅。)
No 1.线段覆盖(linescover)
题目大意:
在一维空间中告诉你N条线段的起始坐标与终止坐标,要求求出这些线段一共覆盖了多大的长度。
解题思路:
将线段按其坐标进行排序(排序的具体方法:按起始坐标排,起始坐标相同的按终止坐标排,都是小在前大在后),使之依次递增,并按顺序分别编号为X(i),X(i).a代表其起始坐标,X(i).b代表其终止坐标。
然后按排好的顺序依次处理:定义一个变量last记录考虑到当前线段之时被线段覆盖的最大的坐标值,再定义一个变量length记录当前线段覆盖的长度。对于后面的线段,我们把它看成由两个部分组成,即把它分成last之前的线段和last之后的线段。(如果线段全部处在last之后,其last之前的部分不存在。)由于我们排过序,我们可以肯定当前考虑的线段X(i)其处在last之前的部分不会对length造成影响(因为X(i-1).b=last,X(i).a>=X(i-1).a,即X(i)在last之前的部分所处位置肯定被线段X(i-1)覆盖过),所以会对length产生影响的即是X(i)处在last之后的部分。
所以我们可以依次对每条线段做如下处理:(初始化length为零,last为负无穷)
length+=X(i).b-last (X(i).a<=last 且 X(i).b>=last)
length+=X(i).b-X(i).a (X(i).a>last)
last=X(i).b;
最后length就为我们所需要的答案。
No 2.最优数对(bestpair)
题目大意:
按递增的顺序告诉你N个正整数和一个实数P,要求求出求出该数列中的比例最接近P的两个数(保证绝对没有两个数使得其比值为P)。
解题思路:
定义两个指针i和j,先初始化i=j=1,然后进行如下操作:
当code[j]/code[i]>p时,inc(j);
当code[j]/code[i]<p时,inc(i)。
记录其中产生的最优值即为答案。
No 3.连续数之和最大值(maxsum)
题目大意:
给出一个长度为N的数列(数列中至少有一个正数),要求求出其中的连续数之和的最大值。(也可以加入a和b来限制连续数的长度不小于a且不大于b)。
解题思路:
先说不加限制的那种,定义一个统计变量tot,然后用循环进行如下操作:inc(tot,item) 其中如果出现tot<0的情况,则将tot赋值为0。在循环过程之中tot出现的最大值即为答案。
如果加入了限制条件的话,问题就变得难一些了(这句真的不是废话)。为此我们先定义数组sum[i]来表示code[1]到code[i]之和(这样的话code[a]~code[b]的和我们就可以用sum[b]-sum[a-1]来表示了。)。
再维护一个数组hash[i]来表示满足条件的sum[a-1]的下标,并使之按递增顺序排列,这样当前以第i的数为终止的数列的最大值肯定就是sum[i]-sum[hash[1]]。
现在我们来讨论hash数组之中的数据需要满足的条件和如何维护的具体问题:
当考虑到以第i个数为结尾时,hash[i]所表示的下标需要满足的第一个条件就是题目规定的长度限制,我们需要实时的加入满足长度规定的下标,删除不符合要求的下标。其次,与不加限制条件时相同,若sum[i]-sum[hash[1]]的值小于零,则清空数组hash。
维护时可以这样,当考虑到第i个数时,我们就将下标i-a+1加入到hash中,因为hash中原来已经排好序,因此我们我们可以用插入排序来维护hash的递增性,然后我们考察hash[1],若hash[1]<i-b+1,则证明其已超出长度限制,我们就将其删除,接着再考虑更新后的hash[1],如此重复直至找到一个满足条件的hash[1]为止。
我们可以用链表来表示hash,这样就可以减少数据加入和删除时频繁数据移动的时间消耗。
记录下sum[i]-sum[hash[1]]的最大值即为答案。
❷ 贪心算法 活动安排问题
这道题的贪心算法比较容易理解,我就不多说明了,只是提到一下算法思路1、建立数学模型描述问题。我在这里将时间理解成一条直线,上面有若干个点,可能是某些活动的起始时间点,或终止时间点。在具体一下,如果编程来实现的话,将时间抽象成链表数组,数组下标代表其实时间,该下标对应的链表代表在这个时间起始的活动都有哪些,具体参照程序注释。2、问题分解。为了安排更多的活动,那么每次选取占用时间最少的活动就好。那么从一开始就选取结束时间最早的,然后寻找在这个时间点上起始的活动,以此类推就可以找出贪心解。程序代码:#include<stdio.h>
struct inode //自定义的结构体
{
int end; //表示结束时间
inode *next; //指向下一个节点的指针
};int main()
{
inode start[10001],*pt;
int a,b,i,num=0; //num负责计数,i控制循环,a,b输入时候使用
for(i=0;i<10001;i++) //初始化
{
start[i].next=NULL;
}
while(scanf("%d %d",&a,&b)) //输入并建立数据结构
{
if(a==0&&b==0) break;
pt=new inode; //创建新的节点,然后将该节点插入相应的位置
pt->end=b;
pt->next=start[a].next;
start[a].next=pt;
}
i=0;
while(i<10001) //进行贪心算法,i表示当前时间
{
if(start[i].next==NULL)
{
i++; //该时间无活动开始
}
else
{
int temp=10001; //临时变量,存储该链表中最早的终止时间
for(pt=start[i].next;pt!=NULL;pt=pt->next)
{
if(pt->end<temp)
{
temp=pt->end;
}
}
i=temp; //将当前时间设置成前一子问题的终止时间
num++;
}
}
printf("%d\n",num); //打印结果
return 0;
}代码并不一定是最快速的,但是可以求出贪心解,如果你做的是ACM编程题目,不保证能AC注释我尽力写了,希望对你有帮助。
❸ 求c++程序(应该是贪心算法)急!!在线等!!!
使用a数组保存前i个元素的累加和,当a[j]-a[i]<=f时就表示可以购买i+1~j张连票。#include<iostream>
using namespace std;
int main()
{ int i,j,n,f,m=1,s,t,a[30]={0};
cin>>n>>f;
for(i=1;i<=n;i++)
{cin>>a[i];
a[i]+=a[i-1]; //a[i]:1~i张票价的总和
}
for(i=0;i<n;i++)
for(j=i;j<=n;j++)
if(a[j]-a[i]<=f&&j-i>m) //可以购买i+1~j张连票
m=j-i;
cout<<m<<'
';
return 0;
}
❹ 最少购物费用问题求解(贪心算法)
type struct
{ int code;
int quantity;
}elem
elem buy [b]//b所购商品的种类数
type struct
{elem data ;
float gprice;
}group;
group offer [m][s]//m表示优惠策略的组数,s表示每组中的商品数
float price[n] ;//n表示商品的种类数
Mincost(data buy [], group offer [m][],int s,int b int result[])
{ int p,i,j,k, remain[b],flag=1;float cost=0.0,min=0.0;
for(i=1;i<=s;i++)result[i]=0};
for(i=1;i<=b;i++) {min+=buy[i].quantity*price[buy[i].code]; //计算优惠前花费
remain[i]= buy[i].quantity;}
while(flag){
flag=0;
for(p=1;p<=m;p++)
{ if(Match(p)) {//判断本次购买与各个组合是否匹配
k=1;
for(i=1;i<=s;i++) {
while(buy[k].code<offer[p][i].data.code)k++;
if(buy[k].code==offer[p][i].data.code) {
remain[k] =buy[k].quantity-offer[p][i].data.quantity;//保存剩余数量
if(remain[k] >=2) flag=1;
k++; } }
cost=gprice[p];
for(i=1;i<=b;i++) cost+=remain[i] *price[buy[i].code];//优惠后花费
if(cost<min){cost=min; result[p]=1;}
}//endfor(p)
for(i=1;i<=b;i++)buy[i].quantity=remain[i];
}}
int Match(int k)
{
int i,j=1;
for(i=1;i<=s;i++) {
while(buy[j].code<offer[k][i].code&&j<=b)j++;
if(j>b)break;
if(buy[j].code==offer[k][i].code)
if( buy[j].quantity>=offer[k][i].quantity)
j++;
else break;
}
if(i>s)return 1;
else return 0;
}
这可是咱老师的答案
❺ 关于编程的贪心法
定义
所谓贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解。 贪心算法不是对所有问题都能得到整体最优解,但对范围相当广泛的许多问题他能产生整体最优解或者是整体最优解的近似解。
[编辑本段]贪心算法的基本思路
1.建立数学模型来描述问题。 2.把求解的问题分成若干个子问题。 3.对每一子问题求解,得到子问题的局部最优解。 4.把子问题的解局部最优解合成原来解问题的一个解。 实现该算法的过程: 从问题的某一初始解出发; while 能朝给定总目标前进一步 do 求出可行解的一个解元素; 由所有解元素组合成问题的一个可行解。 下面是一个可以试用贪心算法解的题目,贪心解的确不错,可惜不是最优解。
[编辑本段]例题分析
[背包问题]有一个背包,背包容量是M=150。有7个物品,物品不可以分割成任意大小。 要求尽可能让装入背包中的物品总价值最大,但不能超过总容量。 物品 A B C D E F G 重量 35 30 60 50 40 10 25 价值 10 40 30 50 35 40 30 分析: 目标函数: ∑pi最大 约束条件是装入的物品总重量不超过背包容量:∑wi<=M( M=150) (1)根据贪心的策略,每次挑选价值最大的物品装入背包,得到的结果是否最优? (2)每次挑选所占重量最小的物品装入是否能得到最优解? (3)每次选取单位重量价值最大的物品,成为解本题的策略。 值得注意的是,贪心算法并不是完全不可以使用,贪心策略一旦经过证明成立后,它就是一种高效的算法。 贪心算法还是很常见的算法之一,这是由于它简单易行,构造贪心策略不是很困难。 可惜的是,它需要证明后才能真正运用到题目的算法中。 一般来说,贪心算法的证明围绕着:整个问题的最优解一定由在贪心策略中存在的子问题的最优解得来的。 对于例题中的3种贪心策略,都是无法成立(无法被证明)的,解释如下: (1)贪心策略:选取价值最大者。 反例: W=30 物品:A B C 重量:28 12 12 价值:30 20 20 根据策略,首先选取物品A,接下来就无法再选取了,可是,选取B、C则更好。 (2)贪心策略:选取重量最小。它的反例与第一种策略的反例差不多。 (3)贪心策略:选取单位重量价值最大的物品。 反例: W=30 物品:A B C 重量:28 20 10 价值:28 20 10 根据策略,三种物品单位重量价值一样,程序无法依据现有策略作出判断,如果选择A,则答案错误。 【注意:如果物品可以分割为任意大小,那么策略3可得最优解】 对于选取单位重量价值最大的物品这个策略,可以再加一条优化的规则:对于单位重量价值一样的,则优先选择重量小的!这样,上面的反例就解决了。 但是,如果题目是如下所示,这个策略就也不行了。 W=40 物品:A B C 重量:28 20 15 价值:28 20 15 附:本题是个NP问题,用贪心法并不一定可以求得最优解,以后了解了动态规划算法后本题就有了新的解法。
[编辑本段]备注
贪心算法当然也有正确的时候。求最小生成树的Prim算法和Kruskal算法都是漂亮的贪心算法。 所以需要说明的是,贪心算法可以与随机化算法一起使用,具体的例子就不再多举了。(因为这一类算法普及性不高,而且技术含量是非常高的,需要通过一些反例确定随机的对象是什么,随机程度如何,但也是不能保证完全正确,只能是极大的几率正确)
[编辑本段]附贪心算法成功案例之一
马踏棋盘的贪心算法 123041-23 XX 【问题描述】 马的遍历问题。在8×8方格的棋盘上,从任意指定方格出发,为马寻找一条走遍棋盘每一格并且只经过一次的一条最短路径。 【初步设计】 首先这是一个搜索问题,运用深度优先搜索进行求解。算法如下: 1、 输入初始位置坐标x,y; 2、 步骤 c: 如果c> 64输出一个解,返回上一步骤c-- (x,y) ← c 计算(x,y)的八个方位的子结点,选出那此可行的子结点 循环遍历所有可行子结点,步骤c++重复2 显然(2)是一个递归调用的过程,大致如下: void dfs(int x,int y,int count) { int i,tx,ty; if(count> N*N) { output_solution();//输入一个解 return; }
❻ 程序员算法基础——贪心算法
贪心是人类自带的能力,贪心算法是在贪心决策上进行统筹规划的统称。
比如一道常见的算法笔试题---- 跳一跳 :
我们自然而然能产生一种解法:尽可能的往右跳,看最后是否能到达。
本文即是对这种贪心决策的介绍。
狭义的贪心算法指的是解最优化问题的一种特殊方法,解决过程中总是做出当下最好的选纤启择,因为具有最优子结构的特点,局部最优解可以得到全局最优解;这种贪心算法是动态规划的一种特例。 能用贪心解决的问题,也可以用动态规划解决。
而广义的贪心指的是一种通用的贪心策略,基于当前局面而进行贪心决策。以 跳一跳 的题目为例:
我们发现的题目的核心在于 向右能到达的最远距离 ,我们用maxRight来表示;
此时有一种贪心的策略:从第1个盒子开始向右遍历,对于每个经过的盒子,不断更新maxRight的值。
贪毁局如心的思考过程类似动态规划,依旧是两步: 大事化小 , 小事化了 。
大事化小:
一个较大的腊山问题,通过找到与子问题的重叠,把复杂的问题划分为多个小问题;
小事化了:
从小问题找到决策的核心,确定一种得到最优解的策略,比如跳一跳中的 向右能到达的最远距离 ;
在证明局部的最优解是否可以推出全局最优解的时候,常会用到数学的证明方式。
如果是动态规划:
要凑出m元,必须先凑出m-1、m-2、m-5、m-10元,我们用dp[i]表示凑出i元的最少纸币数;
有 dp[i]=min(dp[i-1], dp[i-2], dp[i-5], dp[i-10]) + 1 ;
容易知道 dp[1]=dp[2]=dp[5]=dp[10]=1 ;
根据以上递推方程和初始化信息,可以容易推出dp[1~m]的所有值。
似乎有些不对? 平时我们找零钱有这么复杂吗?
从贪心算法角度出发,当m>10且我们有10元纸币,我们优先使用10元纸币,然后再是5元、2元、1元纸币。
从日常生活的经验知道,这么做是正确的,但是为什么?
假如我们把题目变成这样,原来的策略还能生效吗?
接下来我们来分析这种策略:
已知对于m元纸币,1,2,5元纸币使用了a,b,c张,我们有a+2b+5c=m;
假设存在一种情况,1、2、5元纸币使用数是x,y,z张,使用了更少的5元纸币(z<c),且纸币张数更少(x+y+z<a+b+c),即是用更少5元纸币得到最优解。
我们令k=5*(c-z),k元纸币需要floor(k/2)张2元纸币,k%2张1元纸币;(因为如果有2张1元纸币,可以使用1张2元纸币来替代,故而1元纸币只能是0张或者1张)
容易知道,减少(c-z)张5元纸币,需要增加floor(5*(c-z)/2)张2元纸币和(5*(c-z))%2张纸币,而这使得x+y+z必然大于a+b+c。
由此我们知道不可能存在使用更少5元纸币的更优解。
所以优先使用大额纸币是一种正确的贪心选择。
对于1、5、7元纸币,比如说要凑出10元,如果优先使用7元纸币,则张数是4;(1+1+1+7)
但如果只使用5元纸币,则张数是2;(5+5)
在这种情况下,优先使用大额纸币是不正确的贪心选择。(但用动态规划仍能得到最优解)
如果是动态规划:
前i秒的完成的任务数,可以由前面1~i-1秒的任务完成数推过来。
我们用 dp[i]表示前i秒能完成的任务数 ;
在计算前i秒能完成的任务数时,对于第j个任务,我们有两种决策:
1、不执行这个任务,那么dp[i]没有变化;
2、执行这个任务,那么必须腾出来(Sj, Tj)这段时间,那么 dp[i] = max(dp[i], dp[ S[j] ] ) + 1 ;
比如说对于任务j如果是第5秒开始第10秒结束,如果i>=10,那么有 dp[i]=max(dp[i], dp[5] + 1); (相当于把第5秒到第i秒的时间分配给任务j)
再考虑贪心的策略,现实生活中人们是如何安排这种多任务的事情?我换一种描述方式:
我们自然而然会想到一个策略: 先把结束时间早的兼职给做了!
为什么?
因为先做完这个结束时间早的,能留出更多的时间做其他兼职。
我们天生具备了这种优化决策的能力。
这是一道 LeetCode题目 。
这个题目不能直接用动态规划去解,比如用dp[i]表示前i个人需要的最少糖果数。
因为(前i个人的最少糖果数)这种状态表示会收到第i+1个人的影响,如果a[i]>a[i+1],那么第i个人应该比第i+1个人多。
即是 这种状态表示不具备无后效性。
如果是我们分配糖果,我们应该怎么分配?
答案是: 从分数最低的开始。
按照分数排序,从最低开始分,每次判断是否比左右的分数高。
假设每个人分c[i]个糖果,那么对于第i个人有 c[i]=max(c[i-1],c[c+1])+1 ; (c[i]默认为0,如果在计算i的时候,c[i-1]为0,表示i-1的分数比i高)
但是,这样解决的时间复杂度为 O(NLogN) ,主要瓶颈是在排序。
如果提交,会得到 Time Limit Exceeded 的提示。
我们需要对贪心的策略进行优化:
我们把左右两种情况分开看。
如果只考虑比左边的人分数高时,容易得到策略:
从左到右遍历,如果a[i]>a[i-1],则有c[i]=c[i-1]+1;否则c[i]=1。
再考虑比右边的人分数高时,此时我们要从数组的最右边,向左开始遍历:
如果a[i]>a[i+1], 则有c[i]=c[i+1]+1;否则c[i]不变;
这样讲过两次遍历,我们可以得到一个分配方案,并且时间复杂度是 O(N) 。
题目给出关键信息:1、两个人过河,耗时为较长的时间;
还有隐藏的信息:2、两个人过河后,需要有一个人把船开回去;
要保证总时间尽可能小,这里有两个关键原则: 应该使得两个人时间差尽可能小(减少浪费),同时船回去的时间也尽可能小(减少等待)。
先不考虑空船回来的情况,如果有无限多的船,那么应该怎么分配?
答案: 每次从剩下的人选择耗时最长的人,再选择与他耗时最接近的人。
再考虑只有一条船的情况,假设有A/B/C三个人,并且耗时A<B<C。
那么最快的方案是:A+B去, A回;A+C去;总耗时是A+B+C。(因为A是最快的,让其他人来回时间只会更长, 减少等待的原则 )
如果有A/B/C/D四个人,且耗时A<B<C<D,这时有两种方案:
1、最快的来回送人方式,A+B去;A回;A+C去,A回;A+D去; 总耗时是B+C+D+2A (减少等待原则)
2、最快和次快一起送人方式,A+B先去,A回;C+D去,B回;A+B去;总耗时是 3B+D+A (减少浪费原则)
对比方案1、2的选择,我们发现差别仅在A+C和2B;
为何方案1、2差别里没有D?
因为D最终一定要过河,且耗时一定为D。
如果有A/B/C/D/E 5个人,且耗时A<B<C<D<E,这时如何抉择?
仍是从最慢的E看。(参考我们无限多船的情况)
方案1,减少等待;先送E过去,然后接着考虑四个人的情况;
方案2,减少浪费;先送E/D过去,然后接着考虑A/B/C三个人的情况;(4人的时候的方案2)
到5个人的时候,我们已经明显发了一个特点:问题是重复,且可以由子问题去解决。
根据5个人的情况,我们可以推出状态转移方程 dp[i] = min(dp[i - 1] + a[i] + a[1], dp[i - 2] + a[2] + a[1] + a[i] + a[2]);
再根据我们考虑的1、2、3、4个人的情况,我们分别可以算出dp[i]的初始化值:
dp[1] = a[1];
dp[2] = a[2];
dp[3] = a[2]+a[1]+a[3];
dp[4] = min(dp[3] + a[4] + a[1], dp[2]+a[2]+a[1]+a[4]+a[2]);
由上述的状态转移方程和初始化值,我们可以推出dp[n]的值。
贪心的学习过程,就是对自己的思考进行优化。
是把握已有信息,进行最优化决策。
这里还有一些收集的 贪心练习题 ,可以实践练习。
这里 还有在线分享,欢迎报名。