1、贪心算法

贪心算法,是在每一次选择中,总是做出当前看来最好的选择,而不从整体的最优考虑,选择只是某种意义上局部的最优解。生活中很多问题需要对资源优化分配,达到资源利用率最大化。贪心算法虽然不能对所有的问题都求得整体最优解,但是对大部分的问题都能求得最优近似解,对部分问题也能得到最优解,例如单源最短路径最小生成树等。

● 语言描述与基本思想

贪心算法的语言描述为:贪心算法一步步进行,每次都对当前的局部最优解判断:若添加入部分解后仍是可行解就加入;否则舍弃该局部解,寻找下一个局部最优解,直到将所有数据全部枚举完成或可以确定达到目标
基本思想可以概括为:从问题的某一个初始解出发,向给定的目标推进,每次都做出当前最佳的贪心选择,不断将问题实例归纳为更小的相似子问题。

● 基本性质

贪心算法常用于解决最大值最小值的优化问题。贪心算法能求得最优解的问题一般具有两个重要性质

  1. 贪心选择性质:问题的整体最优解可以通过一系列局部最优的选择达到。这是贪心算法可行的第一个基本要素,也是贪心算法问题动态规划问题主要区别。贪心算法通常自顶向下,以迭代的方式做出相继的贪心选择。
  2. 最优子结构性质:问题的整体最优解包含子问题的最优解

● 贪心算法与动态规划对比

贪心算法较为简便,效率更高,但求出的解不一定是整体最优解;动态规划通过求子问题的解构造原问题的解,相对更为复杂,但通过若干局部解的比较,去掉了次优解,得到的必定是整体最优解。

● 基本步骤

贪心算法主要分3个步骤:

  1. 建立数学模型分析问题
  2. 选定合适的贪心选择标准
  3. 根据贪心选择标准完成算法

2、经典问题

● 找零问题

  1. 问题描述
    假设有面值为50元、20元、10元、5元、1元的货币,现需要找给顾客x元现金,求最少给出的货币总数。
  2. 问题分析
    在现实的合理货币标准下,找零问题是最简单的贪心算法例题之一。若货币标准由题目给定,可能需要动态规划求解。
    设y∈{50,20,10,5,1}使得找x-y元现金成为原问题的一个子问题,当wx为原问题的最优解时,wx-y=wx-1必然是原问题的最优解;否则原问题将有wx’=wx-y+1<wx的更优解。由上反证法可证明找零问题具有最优子结构性质
    在题目所给条件下,该问题也具有贪心选择性质
  3. 算法实现
//找零问题
//贪心算法求最小货币数,cash为货币数组,x为找零总数,res为各货币使用总数
int getMinCash(int x,int *cash,int *res)
{sort(cash);int ans = 0;int i = 0;while(x > 0){if(x >= cash[i]){x -= cash[i];++res[i];++ans;}else++i;}return ans;
}

● 活动安排问题

  1. 问题描述
    设n个活动的集合E={1,2,…,n},每个活动需要使用统一资源,而同一时间只能有一个活动使用这一资源。每个活动i都有一个开始时间si和一个结束时间fi,且必有si<fi。任意一个活动将在半开区间[si,fi)内占用资源。对于两个活动i和j,若区间[si,fi)和[sj,fj)不相交,称i和j是相容的;否则称i和j是不相容的或冲突的。求在一定时间内所能安排活动的最大数,也即求集合E的最大相容子集。
  2. 问题分析
    活动安排问题是贪心算法有效求解的例题之一。我们选择结束时间作为贪心选择的标准,每次都选择当前可容的结束时间最早的活动。这样做的目的是为剩余的未安排活动留下尽可能多的时间。该问题的最优子结构性质可由数学归纳法证明;贪心选择性质可由反证法证明。
  3. 算法实现
//活动安排问题
//获取最大相容活动个数和子集,s为开始时间集合,f为结束时间集合,n为活动总数,res为选择的活动
int getMaxActivity(int *s,int *f,bool* res,int n)
{int lastF = 0;int ans = 0;for(int i = 1;i <= n;i++){if(s[i] >= lastF){++ans;res[i] = true;lastF = f[i];}}return ans;
}

时间复杂度分析:对于已经给定非降序序列,主要时间消耗在遍历序列上,故时间复杂度为O(n);若给定序列无序,还需要进行排序,则时间复杂度由排序的时间复杂度决定,通常为O(nlog n)。

● 删数问题

  1. 问题描述
    对给定的数字串x1x2…xn,删除其中的k个数字,使得剩余数字按原次序组成的新数字最大。
  2. 问题分析
    对于一个长整数,其高位数字越大,数字也就越大。若存在xi与xi+1满足xi<xi+1,则删除xi必然能使数字变大;当序列已经呈非增序时,删除末尾的元素。由于越高位的数字对数字的大小影响也越大,由此可以从左端开始遍历,每次遇到第一个删除的数字就是当前的局部最优解。而每次贪心选择后,问题都化为n-1的子问题,因此该问题具有贪心选择性质
    设An为n问题的最优解,而An-1是n-1问题的最优解。若An-1不是n-1问题的最优解,设该最优解为Bn-1,满足Bn-1>An-1,那么补上删除的xi后,必有An = An-1 + xi · 10n-i < Bn-1 + xi · 10n-i = Bn,即存在Bn>An满足Bn为更优解。由上反证法可得,该问题具有最优子结构性质
  3. 算法实现
//删数问题
//x为数字序列,k为需要删除的数字个数,n为序列长度
void getMaxAfterDelK(int* x,int k,int n)
{int i;int t = 0;while(t < k){for(i = 1;i <= n - 1;i++){if(x[i] < x[i+1]){int tmp = x[i];for(int j = i;j <= n - t - 1;j++)   //被删元素后的元素前移x[j] = x[j+1];x[n-t] = tmp;++t;break;}}if(i == n)  break;}
}

时间复杂度分析:删数问题的贪心算法主要时间消耗在遍历数组和移动数组上,共循环k次,所以时间复杂度为O(kn)。

● 背包问题

  1. 问题描述
    给定n种物品和一个容量为C的背包,物品i的重量为wi,价值为vi,如何选择装入背包的物品,使得背包中物品总价值最大?
  2. 问题分析
    在前面学习动态规划时提到过,背包问题分为两种:0-1背包问题可拆分背包问题(简称背包问题)。对于0-1背包问题,由于背包剩余空间可能降低物品的单位重量价值,因此不适用贪心算法,而适用动态规划。而这里将讨论物品物品可拆分的背包问题
    由于背包的容量是有限的,因此物品的重量和价值都会对结果产生影响。因此我们以物品的单位重量价值作为贪心选择的标准。每次选择单位重量价值最高的物品加入背包。
    每次贪心选择放入R%的物品i后,我们的问题就变成C - R% · wi的背包容量与剩余物品集合的背包问题了。设原问题的最优解为A,子问题的最优解为A’。设子问题有更优解满足B‘ > A’,那么加上背包内的价值R% · vi后,可以得到A = A’ + R% · vi < B’ + R% · vi = B,即B为原问题的更优解。如上反证法可得该问题具有最优子结构性质
    设k为原问题或原问题的一个子问题。对于k的第一个选择,若该选择为贪心选择,则满足贪心选择性质;若该选择不为贪心选择,可以将其替换为贪心选择,而不影响之后的选择,使得得到的新解优于原解。因此,该问题具有贪心选择性质
  3. 算法实现
//背包问题
//物品结构体
struct Object{double w;double v;double v_w;int i;
};//objects为物品集合,c为背包容量,n为集合长度,res储存物品的选取重量
double getMaxBagValue(Object* objects,double c,int n,double* res)    //贪心算法求背包能放的最高价值
{sort(objects+1,objects+n+1,[](Object a,Object b)->bool{return a.v_w > b.v_w;});   //对物品按单位重量排序int i = 1;double value = 0;while(c >= objects[i].w){      //能完全装入的完全装入c -= objects[i].w;value += objects[i].v;res[objects[i].i] += objects[i].w;++i;}if(c > 0){                     //不能完全装入的,将剩余空间填满double r = c / objects[i].w;value += r * objects[i].v;res[objects[i].i] += c;}return value;
}

时间复杂度分析:背包问题的贪心算法时间主要消耗在对物品序列排序,其时间复杂度通常为O(nlog n);若给出的序列以满足按单位重量价值非增序,则时间消耗主要在遍历物品序列上,其时间复杂度为O(n)。

● 单源最短路径

  1. 问题描述
    给定一个带权有向图G={V,E},每条边的权值为非负实数。从图上的一点v0()出发,求到达任意另一点vi最短路径
  2. 问题分析
    最短路径问题显然具有最优子结构性质:最短路径必然能分成多个子问题的最短路径,否则该路径可以以最短路径替代,以得到更优的路径。
    最短路径问题的贪心选择性质证明如下:
    我们取当前的最短路径为贪心选择标准,若最短路径不具有贪心选择性质,则必然存在点vx位于贪心路径之外。那么v0到vi的最短路径d(0,i)可以表示为:
    d(0,i) = d(0,x) + d(x,i)
    设v0到vi的贪心选择路径为dist(0,i),则一定有:
    d(0,i) < dist(0,i)
    由于各边的权值均为非负,那么应有:
    d(0,x) ≤ d(0,i)
    再由上面的第二个式子可以得到:
    d(0,x) < dist(0,i)
    这个式子表示顶点v0到vx的距离小于v0到vi的贪心选择距离。根据贪心选择的定义,vx应当在贪心选择路径中。
    由上反证法可证明最短路径问题的贪心选择性质

Dijkstra算法是典型的最短路径算法。它将顶点分为两个集合:已得到最短路径的顶点集合S未确定最短路径的顶点集合V-S,并设一个数组D保存v0到V-S内顶点的当前最短路径
算法步骤如下:
● 初始状态下,S中只有v0,D中记录v0到其它各顶点出弧的权值;若不邻接,则记为无穷大;
● 从D中选择一条最短、且邻接点不在S中的路径,并将邻接点加入S中,该最短路径就是v0到该点的最短路径
● 遍历该邻接点的所有出弧,计算出弧的权值源到邻接点最短路径的和,并与D中记录的源到弧头的最短路径比较。如果和小于原记录,则更新数组D。
● 重复步骤2和3,直到所有的顶点都加入S,或目标顶点vi 加入S。

  1. 算法实现
//Dijkstra算法求最短路径
//图的结构体
struct Graph{int **martix;  //邻接矩阵int v;            //顶点个数
};//G为图,dist为最短路径数组,prev为前置顶点集合,v0为出发点
void dijkstra(Graph G,int* dist,int *prev,int v0)
{bool visited[G.v] = {false};visited[v0] = true;//初始化for(int i = 0;i < G.v;i++){if(G.martix[v0][i] > 0 && i != v0){dist[i] = G.martix[v0][i];prev[i] = v0;}else{dist[i] = 0x7fffffff;prev[i] = -1;}}dist[v0] = 0;prev[v0] = v0;//循环n-1次for(int i = 0;i < G.v;i++){if(i == v0)     continue;int min_num = 0x7fffffff;int v;                  //下一个加入的点for(int j = 0;j < G.v;j++){     //遍历查询最短的路径if(visited[j] == false && dist[j] < min_num){min_num = dist[j];v = j;}}visited[v] = true;for(int j = 0;j < G.v;j++){     //更新distif(visited[j] == false && G.martix[v][j] > 0 && min_num + G.martix[v][j] < dist[j]){dist[j] = min_num + G.martix[v][j];prev[j] = v;}}}
}//构造最优路径
void showPath(int* prev,int v)
{if(v == prev[v])cout << 'v' << v;else{cout << 'v' << v << "<-";showPath(prev,prev[v]);}
}

算法分析与设计:贪心算法相关推荐

  1. 算法分析与设计 —— 贪心算法「活动安排」「背包问题」「哈夫曼编码」

  2. 最少圆覆盖通信覆盖问题-算法分析设计-贪心算法-java实现

    问题描述 假设海岸线是一条无限延伸的直线,陆地在海岸线的一侧,海洋在另外一侧.每个小岛相当于海洋侧的一个点.坐落在海岸线上的基站只能覆盖半径为d的范围.应用直角坐标系,将海岸线作为x轴,设海洋侧在x轴 ...

  3. 算法分析与设计——贪心法实验报告

       算法导论  课程设计 成 绩 题    目:    贪心法 学院班级:        1613013         学    号:      16130130216       姓    名: ...

  4. 大学课程 | 《算法分析与设计》笔记总结

    文章目录 第一章 算法引论 1.1 算法与程序 1.2 表达算法的抽象机制 1.3 描述算法 1.4 算法复杂性分析 第二章 递归与分治策略 2.1 递归的概念 2.2 分治法的基本思想 2.3 二分 ...

  5. 疯子的算法总结(四)贪心算法

    一.贪心算法 解决最优化问题的算法一般包含一系列的步骤,每一步都有若干的选择.对于很多最优化问题,只需要采用简单的贪心算法就可以解决,而不需要采用动态规划方法.贪心算法使所做的局部选择看起来都是当前最 ...

  6. 《算法导论》第16章 贪心算法 个人笔记

    第16章 贪心算法 16.1 活动选择问题 问题:假设有一个n个活动的集合S=a1,a2,...,anS={a_1,a_2,...,a_n},这些活动使用同一个资源,而这个资源在某个时刻只能供一个活动 ...

  7. python算法技巧——贪心算法练习及掌握

    目录 1. 设计findcontentchildren(greedy, size)来判断出饼干可以满足多少小孩: 2. 设计carpooling(trips, capacity)判断是否一个车能接送所 ...

  8. java排队算法_贪心算法-排队问题-JAVA

    自己最近在学一些算法,试着把网上的贪心算法的例题编出来,JAVA版. [题目描述] 在一个医院B 超室,有n个人要做不同身体部位的B超,已知每个人需要处理的时间为ti,(0 输入数据:第1行一个正整数 ...

  9. 【算法学习】贪心算法

    参考算导第三版第16章 贪心算法 文章目录 1. 活动选择问题 1.1 活动选择问题的最优子结构 1.2 贪心选择 1.3 递归贪心算法 1.4 迭代贪心算法 2. 贪心算法原理 2.1 贪心选择性质 ...

  10. 算法导论-上课笔记7:贪心算法

    文章目录 0 前言 1 活动选择问题 1.1 活动选择问题的最优子结构 1.2 贪心选择 1.3 递归贪心算法 1.4 迭代贪心算法 2 贪心算法原理 2.1 贪心选择性质 2.2 最优子结构 2.3 ...

最新文章

  1. 微信小程序image bindload事件失效不触发
  2. jQuery新版本加载json注意事项。
  3. i219 2012驱动_2012年I / O之后
  4. Java秒杀系统实战系列~基于Redis的原子操作优化秒杀逻辑
  5. 中国移动宣布已开通5G基站近5万个,在50个城市提供5G服务
  6. K3S kubernetes-限制节点可启动的pod数量
  7. 苹果MAC系统常用软件 (BY 冷家锋)
  8. js实现椭圆轨迹_Js 椭圆轨迹运动动画 代码分享
  9. Word另存为pdf时提示“由于出现意外错误,导出失败”的解决方案
  10. 深度:那些梦碎乐视的造车高人!
  11. 汽车CAN通信解析(一)
  12. Python Turtle画奥运标志
  13. JS对象(对象的创建、属性的增删改查、属性的检测和枚举)
  14. 但愿人长久,千里共婵娟---众智云
  15. 每日英语:China's Red Cross Tries to Rebuild After Self-Inflicted Disaster
  16. CSS:三种背景(斑马线,棋盘,格子)
  17. 在linux下 用户的密码错误,linux中root用户密码错误如何解决
  18. kivy中on_press,on_release事件用代码设置函数的问题
  19. 摄像头 UIImagePickerController拍照和视频录制
  20. 解决模糊查询问题 element UI 从服务器搜索数据,输入关键字进行查找

热门文章

  1. 三调数据库标注插件v1.3
  2. 这 173 家牛逼的互联网国企!值得你加入
  3. InVEST实践与进阶及在生态系统服务供需、固碳、城市热岛、论文写作
  4. IDA Pro使用学习研究笔记(一)——IDA View
  5. 仅供自用,大学三年收藏夹
  6. 【预测模型】基于狼群算法优化BP神经网络实现预测matlab源码
  7. 反编译android sdk,反编译apk,修改sdk文件,重新签名
  8. 单片机实验板 c语言 打包下载,《AVR单片机开发板 实验板 C语言 视频教程 》
  9. 使用Origin绘制折线图(入门)
  10. 新浪、腾讯微博开放平台非标准oauth解析