第16章 贪心算法
来源:互联网 发布:南京软件企业排名 编辑:程序博客网 时间:2024/05/23 01:12
一:贪心算法的概念:
求解最优化问题的算法通常需要经过一系列的步骤,在每个步骤都面临多重选择,动态规划算法是通过比较这么多选择来得到一个最优的选择,而贪心算法不用比较而是直接选出当时看起来最佳的选择,通过做出局部最优的选择来得到全局最优解。设计贪心算法有如下三个步骤:
- 将最优化问题转化为这样的形式:对其做出一次选择后,只剩下一个子问题需要求解;
- 证明贪心选择是最优解的一部分;
- 证明做出贪心选择后,剩余子问题的最优解与贪心选择结合在一起能得到原问题的最优解。
贪心算法求解最优化问题实例:
本章节列举了活动选择问题,背包问题,赫夫曼编码,任务调度问题,找零问题。
活动选择问题:
在这个问题中,贪心选择是选择原问题中结束时间最早的那个活动。
//数组activityTime存储的活动从下标1开始而不是0开始,并且已按活动结束时间从早到晚排序了;//数组activityTime中pair存储的第一个元素是活动开始时间,第二个元素是活动结束时间;//数组activityCandidate存储的就是最终求解的活动。void greedyActivitySelector(const vector< pair<int,int> >& activityTime,vector<int>& activityCandidate){ activityCandidate.push_back(1); int k=1; for(int i=2;i!=activityTime.size();++i) { if(activityTime[k].second<=activityTime[i].first){ activityCandidate.push_back(i); k=i; } }}
背包问题:
背包问题分为能用贪心算法求解的分数背包问题和只能用动态规划算法求解0-1背包问题。
分数背包问题:
贪心选择是选择每磅价值最大的商品
//pair<int,int>类型第一个元素存储商品的价值,第二个元素存储商品的重量;bool compare1(const pair<int,int>& lhs,const pair<int,int>& rhs){ double tmp1=(double)(lhs.first)/lhs.second; double tmp2=(double)(rhs.first)/rhs.second; return tmp2<tmp1;}//commodity[0]不存储商品,从commodity[1]开始存储商品;double fractionalKnapsackProblem(int totalWeight,vector< pair<int,int> >& commodity,vector< pair< pair<int,int>,int> >& commodityChoice){ sort(commodity.begin(),commodity.end(),compare1);//根据商品单位重量价值,从大到小排序; int remainingWeight=totalWeight; double maxProfit=0; for(int i=1;i!=commodity.size()&&remainingWeight>0;++i) { if(commodity[i].second<=remainingWeight){ commodityChoice.push_back(make_pair(make_pair(commodity[i].first,commodity[i].second),commodity[i].second)); maxProfit+=commodity[i].first; remainingWeight-=commodity[i].second; } else{ commodityChoice.push_back(make_pair(make_pair(commodity[i].first,commodity[i].second),remainingWeight)); maxProfit+=(double)(commodity[i].first)/commodity[i].second*remainingWeight; remainingWeight=0; } } return maxProfit;}
0-1背包问题:
void zeroOneKnapsackProblem(int totalWeight,vector< pair<int,int> >& commodity,vector< vector<int> >& profit){ int commodityCount=commodity.size()-1; profit.resize(commodityCount+1); for(int i=0;i!=profit.size();++i) profit[i].resize(totalWeight+1); for(int i=0;i!=profit.size();++i) { profit[0][i]=0; profit[i][0]=0; } for(int i=1;i<=commodityCount;++i) for(int w=1;w<=totalWeight;++w) { if(commodity[i].second<=w) profit[i][w]=max(commodity[i].first+profit[i-1][w-commodity[i].second],profit[i-1][w]); else profit[i][w]=profit[i-1][w]; }}void zeroOneKnapsackProblem(int totalWeight,vector< pair<int,int> >& commodity){ vector< vector<int> > profit; zeroOneKnapsackProblem(totalWeight,commodity,profit); int commodityCount=commodity.size()-1; cout<<"the maximum profit: "<<profit[commodityCount][totalWeight]<<endl; cout<<"the corresponding commodity: "<<endl; int w=totalWeight; int i=commodityCount; while(w>0&&i>0){ if(commodity[i].second<=w){ if(commodity[i].first+profit[i-1][w-commodity[i].second]>=profit[i-1][w]){ cout<<"("<<commodity[i].first<<" , "<<commodity[i].second<<" )"<<endl; w-=commodity[i].second; i=i-1; } else{ i=i-1; } } else{ i=i-1; } }}
赫夫曼编码:
1:编码树中结点定义如下:
struct node{ char character; int freq; node* left; node* right; node(const char& c=char(),int f=0,node* l=0,node* r=0):character(c),freq(f),left(l),right(r){} };
2:为了实现编码树,在队列中存储的是结点指针,但在c++中结点指针不能比较,因此可以创立一个类来存储结点指针同时通过比较这个类来比较结点存储的频率大小。
struct nodePointer{ node* pointee; explicit nodePointer(node* p=0):pointee(p){} };//存储结点指针 struct compare{ bool operator()(const nodePointer& lhs,const nodePointer& rhs) { return lhs.pointee->freq>rhs.pointee->freq; } };//比较结点中频率的大小
3:队列的定义如下:
priority_queue<nodePointer,vector<nodePointer>,compare > characterSetPriorityQueue;
4:构造编码树:
void huffmanCode::buildCode(){ int size=characterSetPriorityQueue.size(); for(int i=1;i!=size;++i) { node* tmp1=characterSetPriorityQueue.top().pointee; characterSetPriorityQueue.pop(); node* tmp2=characterSetPriorityQueue.top().pointee; characterSetPriorityQueue.pop(); node* tmp=new node(); tmp->left=tmp1; tmp->right=tmp2; tmp->freq=tmp1->freq+tmp2->freq; characterSetPriorityQueue.push(nodePointer(tmp)); } root=characterSetPriorityQueue.top().pointee; characterSetPriorityQueue.pop();}
5:解码的过程如下:
现在有了二进制编码,要根据已经建立的编码树,将这些二进制编码转化为字符信息:
void huffmanCode::decode(const string& binaryCodeString){ buildCode(); node* currentNode=root; for(int i=0;i!=binaryCodeString.size();++i) { if(binaryCodeString[i]=='0') currentNode=currentNode->left; else if(binaryCodeString[i]=='1') currentNode=currentNode->right; else throw runtime_error("the binary code string is illegal!"); if(currentNode->left==0){ cout<<currentNode->character; currentNode=root; } }}
任务调度问题:
1:任务类定义如下:
struct task{ int label;//存储任务标号; int deadline;//存储任务要完成的截止时间; int punishment;//存储任务没有在截止时间完成所受的惩罚; task(int l=0,int d=0,int p=0):label(l),deadline(d),punishment(p){}};
2:在任务调度问题中,我们需要将惩罚值从大到小排序,将任务的截止时间从小到大排序。定义的比较函数如下:
//将截止时间从小到大排序;bool compareDeadline(const task& lhs,const task& rhs){ return lhs.deadline<rhs.deadline;}//将惩罚值从大到小排序;bool comparePunishment(const task& lhs,const task& rhs){ return rhs.punishment<lhs.punishment;}
3:我们需要判断一个任务是否属于书中所定义的独立集合。代码如下:
bool isIndependent(vector<int>& deadlineCount,int candidateTaskDeadline){ for(int i=candidateTaskDeadline;i!=deadlineCount.size();++i) deadlineCount[i]++; bool dependent=true; for(int i=candidateTaskDeadline;dependent&&i!=deadlineCount.size();++i) if(deadlineCount[i]>i) dependent=false; if(!dependent){ for(int i=candidateTaskDeadline;i!=deadlineCount.size();++i) deadlineCount[i]--; } return dependent;}
4:有了上面一系列的类和函数后,求解任务调度的主要函数如下:
//optimumTaskOrder数组中存储的就是最优的任务排序。int taskArrangment(vector<task>& taskSet,vector<task>& optimumTaskOrder){ sort(taskSet.begin(),taskSet.end(),comparePunishment);//将惩罚值从大到小排序; vector<task> maxIndependentSet; vector<task> remainingTaskSet; vector<int> deadlineCount(taskSet.size()+1,0); for(int i=0;i!=taskSet.size();++i) if(isIndependent(deadlineCount,taskSet[i].deadline)) maxIndependentSet.push_back(taskSet[i]); else remainingTaskSet.push_back(taskSet[i]); sort(maxIndependentSet.begin(),maxIndependentSet.end(),compareDeadline);//将最大独立集合中任务的截止时间从早到晚排序; for(int i=0;i!=maxIndependentSet.size();++i) optimumTaskOrder.push_back(maxIndependentSet[i]); int minPunishment=0; for(int i=0;i!=remainingTaskSet.size();++i) { optimumTaskOrder.push_back(remainingTaskSet[i]); minPunishment+=remainingTaskSet[i].punishment; } return minPunishment;}
找零问题:
在找零问题中如果面额设置合适可以用贪心算法求解,如果设置不合适就只能用动态规划算法求解。
1:贪心算法如下:
//denomination数组中存储了从大到小的面额。//solution[i]中存储了对应的面额denomination[i]所使用的数目void changeGreedyChoice(int totalChange,const vector<int>& denomination,vector<int>& solution){ solution.resize(denomination.size(),0); for(int i=0;i!=denomination.size();++i) { solution[i]=totalChange/denomination[i]; totalChange%=denomination[i]; if(totalChange==0) break; }}void printChangeGreedyChoice(int totalChange,const vector<int>& denomination){ vector<int> solution; changeGreedyChoice(totalChange,denomination,solution); for(int i=0;i!=solution.size();++i) if(solution[i]!=0) cout<<denomination[i]<<" : "<<solution[i]<<endl;}
2:动态规划算法如下:
//数组denomination中存储的硬币面额没有必要经过排序,可以任意;//solution[i]存储的是为了找i美分零钱所使用的第一个面额树。这个和贪心算法中数组solution的定义是不一样的。void changeDynamicProgramming(int totalChange,const vector<int>& denomination,vector<int>& solution){ solution.resize(totalChange+1,0); vector<int> minCoinCount(totalChange+1,0); for(int change=1;change<=totalChange;++change) { minCoinCount[change]=INT_MAX; for(int i=0;i!=denomination.size();++i) if(denomination[i]<=change){ int coinCount=1+minCoinCount[change-denomination[i]]; if(coinCount<=minCoinCount[change]){ minCoinCount[change]=coinCount; solution[change]=denomination[i]; } } }}void printChangeDynamicProgramming(int totalChange,const vector<int>& denomination){ vector<int> solution; changeDynamicProgramming(totalChange,denomination,solution); cout<<"denomination required below: "<<endl; while(totalChange!=0){ cout<<solution[totalChange]<<" "; totalChange-=solution[totalChange]; }}
0 0
- 第16章 贪心算法
- 第16章 贪心算法
- 第 16 章 贪心算法
- 算法导论代码 第16章 贪心算法
- 算法导论第16章 贪心算法之活动选择
- 算法导论习题解-第16章贪心算法
- 贪心算法(算法导论第16章)
- 算法导论第16章 贪心算法-活动选择问题
- 高效算法之贪心算法(第16章)
- 《算法导论》第16章 贪心算法 个人笔记
- 活动选择问题(算法导论第16章(贪心算法)
- 算法导论 第16章 活动选择问题的递归和迭代贪心算法
- 算法导论 第16章 贪心算法-活动选择问题C++实现
- 算法导论-第16章-贪心算法-16.1 活动选择问题
- 算法导论第16章 贪心算法-0-1背包问题—动态规划求解
- 《算法导论》笔记 第16章 16.2 贪心策略的基本内容
- 《算法导论》笔记 第16章 *16.4 贪心法的理论基础
- 算法导论 16章 贪心算法
- WebLogic Tomcat WebSphere JBOSS 区别1
- 1141: 百钱百鸡问题
- 笔试总结——数据库篇【持续更新】
- Android引入即用的便捷开发框架WelikeAndroid
- UVA 1395Slim Span 最小生成树
- 第16章 贪心算法
- ZOJ 3706 Break Standard Weight(暴力思维)
- hdu 【1007】Quoit Design
- 二叉树--创建,删除,交换;查找节点的层数,某层多少节点,叶子节点到根节点的路径,LCA
- C++ 学习之路(11):多态性与虚函数
- 单例模式
- Android 轻量级输入校验库:Fire Eye
- java编程实战之闰年的判断程序编写
- 虚拟机的使用