1算法思想
贪心
1.1含义
通过一系列局部选择得到问题的解,每个选择都是当前状态下最优的选择。
1.2 性质
1) 贪心选择性质: 全局最优解可以通过局部最优解的选择来达到
2) 最优子结构性质: 问题最优解包含其子问题的最优解
如何证明贪心选择性质:
需要证明每一步的贪心选择最终导致问题的最优解。
证明步骤:
1)考察问题的一个整体最优解,并证明可以通过贪心选择开始来修改这个最优解。
2)做出贪心选择后,原问题转化为规模更小的类似子问题
3)用数学归纳法证明,每一步的贪心选择可得到问题的整体最优解。
1.3 适合
贪心算法适合求解最大/小/优等最系列问题,不过需要证明贪心算法得到的的确是最优解。
1.4通用解法
贪心算法的关键是贪心策略选择。 贪心算法: 依据某种贪心策略(通常是基于某个属性或某几个属性先排序)对数据进行处理 对上述已经处理后的数据进行遍历处理 |
1.5经典例题讲解
活动安排问题
n 个活动E={1,2,..,n} ,都要求使用同一公共资源(如演讲
。 会场等)。且在同一时间仅一个活动可使用该资源。
i属于[s i , f i ), s i 间 为起始时间, f i 为结束时间。 s i < f i
活动i 和j 相容: s i >= f j 或 或 s j > = f i
分析:
贪心策略:
选择具有最早结束时间的相容活动加入,使剩余
的可安排时间最大,以安排尽可能多的活动。
贪心选择性质:
证明:
n 个活动E={1,2,..,n} ,按结束时间递增排序: :f 1 <=f 2 <= .. <=
f n 。 。
1. 总存在一个最优解以贪心选择开始即包含活动1 1 。
E 按结束时间递增排序,活动1 1 具有最早结束时间。
设A A, 为最优解,AE, A也 按结束时间递增排序。
设A A 中第一活动为k,k=1时 时, , 显然成立。
k>1时 时, ,设 设B=A-{k}并上{1} ,由于f 1 <=f k, 且A中活动互为相容,则B中活动也
互为相容。而B与A中活动个数相同,且A为最优解,则B为最优解。
最优子结构性质:
证明:
选择活动1 以后,问题变为子问题E’: 与活动1 相容的活动
。 安排问题。
设A 为包含活动1 的最优解,则A’=A-{1}为 为E’ 的一个最优解。
假如存在E’ 的一个解B’ ,|B’|>|A’|,则 则|B’ 并上{1}|>A, 与A 为最
优解矛盾。
代码
typedef struct Show{ bool operator < (const Show& show) const//常成员修饰符不能丢,否则报大量stl错误 { return uEndTime < show.uEndTime; } unsigned uBegTime; unsigned uEndTime; bool isDel;//删除标记,初始均设为false,一旦确定上一个节目后,下一个节目从剩余节目中选择,如果showA.uEndTime > showB.uBegTime,就设这个节目的isDel=true; }Show;
int getMoreShow(Show* shows, int num){ quickSort(show,0,num-1); // 贪心策略,按照节目结束时间排序 unsigned curTime = 0; int iCount = 0; for(int j = 0 ; j < iNum ;j++) // 对上述已经处理后的数据进行遍历处理 { if(curTime <= show[j].uBegTime) { curTime = show[j].uEndTime; iCount++; } } return count; } } |
1.6 贪心算法与动态规划算法的区别
贪心算法和动态规划算法都要求问题具有最优子结构性质(即问题最优解包含其子问题的最优解)。
有的题目可以用动态规划算法但是不能使用贪心算法求解是因为问题的某种条件(例如:
100, 70, 30, 10共4种面值的钱币,问140最少可以有多少张钱币组成,结果并不是:
100,30,10,而是2个70,70破坏了贪心选择性质)导致贪心算法失效。
2 贪心系列
类别-编号 |
题目 |
遁去的1 |
1 |
FatMouse’ Trade M磅猫粮,N个房间,第i个房间放着J[i]的老鼠粮食,需要F[i]磅猫粮给猫来看守,如果给予猫粮:F[i]*a%,则可获得J[i]*a%的老鼠粮食 输入: 第一行:非负整数 M N,接下来又N行,每一行含有1个非负整数 J[i] F[i] 最后一组测试用例为:-1 -1 所有的整数 <= 1000 对每一行输出: 打印出单独的一行,一个经过计算的实数(保留3位小数),老鼠能获得的最大的粮食(应选择最不值钱的鼠粮食) 输入: 5(磅猫粮) 3(3行) 7 2 4 3 5 2 20(磅猫粮) 3(3行) 25(J[1],第1个房间放着25磅老鼠粮食) 18(需要给猫18磅猫粮) 18/25=0.72 24 15 15/24=5/8=0.625(先买它) 15*(24/15)=24 15 10 10/15=2/3=0.66 (再买它)5*(15/10)=7.5 综合31.5 -1 -1(结束) 输出: 13.333 31.500 |
计算机考研—机试指南 https://blog.youkuaiyun.com/qingyuanluofeng/article/details/47160037 思路:最贪心思路,选择最不值钱的,能买到的鼠粮最多->鼠粮/猫粮最大的,通过 18元能买25斤食物, 所以1斤需要18/25,后除以前,最大的即为价值最高的,1元能买25/18(越大越不值钱) 1元能买 24/15=1.6斤,选择前/后最大的先买 关键: 1 打印3位小数 printf("%.3d",x); 2 整型数做除法要乘以1.0,否则为整数,(1.0)*iMouseFood/iCatFood 3 要设定猫粮减为空时,要跳出循环,当前猫粮不够这个房间时,同时要设置猫粮使用完后为空
代码 int main(int argc,char* argv[]) { int iCatFood,iNum; double dResult[100]; int iCount = 0;//用于存放计算后的结果
//第三个元素是鼠粮/猫粮的值,值越大说明鼠粮越不值钱,能买到的鼠粮越多 while(EOF!=scanf("%d %d",&iCatFood,&iNum)) { if(-1==iCatFood && -1==iNum) //第三次输入这个就无法判断了? { break; } int i; //int iArr[100][3];//建立二维数组,共有100行,每行3个元素,第一个元素是鼠粮,第二个元素是猫粮, Food food[100]; for(i = 0;i < iNum ; i++) { scanf("%d %d",&food[i].iMouseFood,&food[i].iCatFood); if(food[i].iMouseFood < 0 || food[i].iCatFood < 0)//校验输入数据的合法性 { return -1; } else { food[i].iValue = 1.0*food[i].iMouseFood/food[i].iCatFood;//易错 } } //对鼠粮/猫粮的值其进行快速排序,注意易错 quickSort(food,0,iNum-1); //判断当前房间需要的猫粮是否足够,计算结果 double dRes = 0.0; //从后向前读,因为先找iValue大的 //for(i = 0; i < iNum ; i++) for(i = iNum - 1; i >= 0 ;i--) { //设定推出循环的条件为,猫粮为空 if(iCatFood <= 0) { break; } //如果当前猫粮没有超过该房间最大值 if(iCatFood < food[i].iCatFood ) { //dRes += iCatFood*(food[i].iMouseFood/food[i].iCatFood); //注意保证浮点数,要乘以1.0 dRes += 1.0*iCatFood*(1.0*food[i].iMouseFood/food[i].iCatFood);//易错,这里用两个整型数做除法要乘以1.0 //易错,忘记置iCatFood为0了 iCatFood = 0; } //如果当前猫粮超过该房间最大值 else { dRes += 1.0*food[i].iMouseFood; iCatFood -= food[i].iCatFood; } } //存放结果到数组中 dResult[iCount++] = dRes; } //iCount--; for(int i = 0 ;i < iCount;i++) { printf("%.3f\n",dResult[i]); } system("pause"); getchar(); return 0; } |
2 |
今年暑假不AC 尽可能看多的电视节目,时间为整点
输入: 第一行一个整数n为喜欢看电视的节目总数,下面有n行数据,每一行数据包含2个数据Ti_s,Ti_e表示第i个节目的开始和结束时间 n=0表示输入结束,不做处理 输出:能看到电视节目的个数 输入: 12 1 3 3 4 0 7 3 8 15 19 15 20 10 15 8 18 6 12 5 10 4 14 2 9 0 |
计算机考研—机试指南 https://blog.youkuaiyun.com/qingyuanluofeng/article/details/47160063 排序: 下标:0 1 2 3 4 5 6 7 8 9 10 11 1 3, 3 4, 0 7, 3 8, 2 9, 5 10, 6 12, 4 14, 10 15,` 8 18, 15 19, 15 20 选择 选择 选择 选择 选择 选择 选择 选择 选择 选择(第一趟),确定1 3,3 4 j=0 选择 选择 选择 选择 选择 选择 选择 选择 选择(第二趟) ,确定1 3, 3 4,5 10 j=1 选择 选择 选择 选择 选择 选择(第三趟),确定1 3, 3 4,5 10,10 15 j=5 选择 选择 选择 选择 选择 (第四趟),确定1 3, 3 4,5 10,10 15,15 19 j=8 j=10 输出: 5 思路:尽可能选择时间跨度范围小的,并且一个节目的结束时间要小于另一个节目的开始时间。思路错误 应该优先选择结束时间最早的,最优解是结束时间最早。假设第一个节目A[s1,e1],有比A更早结束的节目B,那么用 B替换A不影响A后面节目顺序,因此这也是最优解。 所以当第x-1个节目选择结束后,选择剩余节目中最早结束的节目 贪心算法: 1要证明 2用反证 关键: 1 通过设置标记法,来做过滤,已经过滤掉的,下次循环不做处理 2 设置下一个节目下标时,初始值设为iMinIndex=iNum,以防止最后一次一直拿不到iMinIndex时,程序能够退出循环 3 bool operator < (const Show& show) const//常成员修饰符不能丢,否则报大量stl错误 方法2: 在遍历的过程中,只需搜索一遍,起初设置curTime = 0,然后另curTime<=show[i].endTime,然后不断更新curTime即可
代码: typedef struct Show{ bool operator < (const Show& show) const//常成员修饰符不能丢,否则报大量stl错误 { return uEndTime < show.uEndTime; } unsigned uBegTime; unsigned uEndTime; bool isDel;//删除标记,初始均设为false,一旦确定上一个节目后,下一个节目从剩余节目中选择,如果showA.uEndTime > showB.uBegTime,就设这个节目的isDel=true; }Show;
int main() { int iNum; Show show[MaxSize]; while(EOF!=scanf("%d",&iNum) && iNum > 0) { for(int i = 0;i < iNum ; i++) { scanf("%d %d",&show[i].uBegTime,&show[i].uEndTime); if(show[i].uBegTime < 0 || show[i].uBegTime > 24 || show[i].uEndTime < 0 || show[i].uEndTime > 24) { printf("The time you input is not valid!\n"); } } quickSort(show,0,iNum-1); unsigned curTime = 0; int iCount = 0; for(int j = 0 ; j < iNum ;j++) { if(curTime <= show[j].uBegTime) { curTime = show[j].uEndTime; iCount++; } } printf("%d\n",iCount); } system("pause"); getchar(); return 0; } |
3 |
买商品 一个顾客买了价值为x元的商品:并将y元的钱教给售货员。售货员希望用张数最少的钱币找给顾客。找的钱最多需要以下6种币值:50,20,10,5,2,1 |
算法设计与分析 https://blog.youkuaiyun.com/qingyuanluofeng/article/details/47189101 关键: 设定一个数组b[6] = {50,20,10,5,2,1} 采用累除法,依次从数组b的高位到低位进行累加,如果当前钱币的值除了之后 有剩余,那么累加找钱币的个数,更新剩余需要找的钱,用下一个币值重复上述过程 输入: 1(买东西的钱) 100(付给的钱) 输出: 6 【1(50) + 2(20) + + 1(5)+ 2(2) = 6】
代码: int leastNumOfBackMoney(int iNeedBackMoney,int* pMoneyTypeArr,int iMoneyTypeNum) { if(iNeedBackMoney <= 0) { return -1; } int iRet = 0; for(int i = 0 ; i < iMoneyTypeNum; i++) { int iNum = iNeedBackMoney / pMoneyTypeArr[i]; iNeedBackMoney -= iNum * pMoneyTypeArr[i]; iRet += iNum; if(iNeedBackMoney == 0) { break; } } return iRet; } |
4 |
高效率地安排见面会 每一个面试是一个整数的闭区间[B[i],E[i]],表示开始时间和结束时间,有N个面试要进行,求最少的面试点 |
编程之美 |