贪心法归纳

概念

贪心法就是遵循某种规则,不断贪心地选取当前最优策略的算法设计方法。

一般步骤:
1.建立数学模型来描述问题;
2.把求解的问题分成若干个子问题;
3.对每一子问题求解,得到子问题的局部最优解;
4.把子问题的解局部最优解合成原来解问题的一个解。

算法的实现框架:
从问题的某一初始解出发;
while (能朝给定总目标前进一步)
{
利用可行的决策,求出可行解的一个解元素;
}
由所有解元素组合成问题的一个可行解;

典型例题

1.货币选择问题

问题描述: 分别有1,5,10,50,100元,分别有5,2,2,3,5张纸币。问若要支付k元,则最少需要多少张纸币?
问题分析: 优先使用面值大的货币
代码实现:

#include <iostream>
#include <algorithm>
using namespace std;
 
const int N=7;   
int Money[N]={3,0,2,1,0,3,5};  
int Value[N]={1,2,5,10,20,50,100};  
 
int solve(int money){
	int num=0;
	for(int i=N-1;i>=0;i--){
		int c=min(money/Value[i],Money[i]);
		money=money-Value[i]*c;		
		num+=c;
	}
	if(money>0){
		num=-1;		
	}
	return num;
}
int main(){
	int money;
	cin>>money;
	int res=solve(money);
	if(res!=-1) cout<<res<<endl;
	else cout<<"no"<<endl;
	return 0;
}

2.区间调度问题

问题描述: 有n项工作,每项工作分别在Si开始,Ti结束。例如S={1,2,4,6,8},T={3,5,7,8,10}。对每项工作,你都可以选择与否,若选择参加,则必须至始至终参加全程参与,且参与工作的时间段不能有重叠。
问题分析: 我们把“在可选工作中,每次都选取结束时间最早的”策略作为贪心算法所遵循的规则。
代码实现:

#include<iostream>
#include<algorithm>

using namespace std;
//输入
const int n = 5;
int S[n]={1,2,4,6,8};
int T[n]={3,5,7,9,10};
struct actime{
    int start,finish;
}act[1002];
bool cmp(actime a,actime b){
    return a.finish<b.finish;
}
int solve()
{
	//这里注意要按照工作的结束时间进行排序
	for(int i = 0; i < n; i ++) {
 
		act[i].start = S[i];
		act[i].finish = T[i];
	}
	sort(act, act + n,cmp);
 
	int count = 0;//选取的结果
	int t = -1;	//最后所选工作的结束时间
	for(int i = 0; i < n; i ++) {
		if(t < act[i].start) {
			count ++;
			t = act[i].finish;
		}
	}
	return count;
}
 
int main() {
	int k=solve();
	cout << k<< endl;
	return 0;
}

3.最小生成树问题

问题描述: 求一个连通无向图的最小生成树的代价(图边权值为正整数)。
第一行是一个整数N(1<=N<=20),表示有多少个图需要计算。以下有N个图,第i图的第一行是一个整数M(1<=M<=50),表示图的顶点数,第i图的第2行至1+M行为一个M*M的二维矩阵,其元素ai,j表示图的i顶点和j顶点的连接情况,如果ai,j=0,表示i顶点和j顶点不相连;如果ai,j>0,表示i顶点和j顶点的连接权值。
问题分析:
(1)对边升序排序
在此采用链式结构,通过插入排序完成。每一结点存放一条边的左右端点序号、权值及后继结点指针
(2)边的加入是否构成环
一开始假定各顶点分别为一组,其组号为端点序号。选择某边后,看其两个端点是否在同一组中,即所在组号是否相同,如果是,表示构成了环,则舍去。 如果两个端点所在的组不同,则表示可以加入,则将该边两端的组合并成同一组。
代码实现:


```cpp
#include<iostream>
using namespace std;
struct node
{
 int l;
 int r;
 int len;
 node *next;
};
void insert(node *&h,node *p)   //指针插入排序
{
 node *q=h;
 while(q->next && q->next->len <= p->len)
 {
  q=q->next;
 }
 p->next=q->next;
 q->next=p;
}
int main()
{
// freopen("001.in","r",stdin);
 node *h,*p;
 int n,m,x,temp;
 int *a;
 int i,j;
 int sum;
 cin>>n;
 while(n--)
 {
  sum=0;
  cin>>m;
  a=new int[m+1];
  for (i=1;i<=m;i++)
  {
   a[i]=i;
  }
  h=new node;
  p=h;
  p->next=NULL;
  for (i=1;i<=m;i++)
   for (j=1;j<=m;j++)
   {
    cin>>x;
    if (i>j && x!=0)
    {
     p=new node;
     p->l=i;
     p->r=j;
     p->len=x;
     p->next=NULL;
     insert(h,p);   //调用插入排序
    }
   }
          p=h->next;
   while (p)
   {
    if (a[p->l]!=a[p->r])
    {

     sum+=p->len;
     temp=a[p->l];
     for(i=1;i<=m;i++)
      if (a[i]==temp)
      {
       a[i]=a[p->r];
      }
    }
   p=p->next;
   }
   cout<<sum<<endl;
 }
 return 0;
}

4.字典序最小问题

问题描述: 给定长度为N的字符串为S,要构造一个长度为N的字符串T。起初,T 是一个空串,随后反复进行下列任意操作。
(1)从S的头部删除一个字符,加到T的尾部;
(2)从S的尾部删除一个字符,加到T的尾部;
目标是要构造字典序尽可能小的字符串T。(字典序是指从前到后比较两个字符串大小的方法。首先比较第1个字符,如果不同则第1个字符较小的字符串更小,如果相同则继续比较第2个字符…如此继续,来比较整个字符串的大小)
问题分析: 从字典序的性质来看,不管字符串T的尾部有多大,只要前面够小就可以,所以我们可以采用贪心算法
(1)不断去D开头和尾部中较小的一个字符放到T的尾部
(2)针对开头和尾部字符相同的情况,我们希望能够尽早使用更小的字符,所以就要比较下一个字符的大小。下一个字符也有可能相同。
代码实现:

#include<iostream> 
using namespace std;
 
int N ;
string S;
 
void solve(){
	//剩余的字符串位S[left],S[left+1],.....,S[right] 
	int left = 0, right = N-1;
	bool isLeft = false;	//将从左起和从右起的字符串进行比较 
	while(left <= right){
		isLeft = false;
		for(int i = 0; left + i < right; ++i){
			if(S[left+i] < S[right-i]){
				isLeft = true;
				break;
			}
			else if(S[left+i] > S[right-i]){
				isLeft = false;
				break;
			}
		}
		if(isLeft) cout << S[left++];
		else      cout << S[right--];
	}
	cout <<endl;
}
 
int main()
{
	cout <<"请输入N的值:";
	cin>>N;
	cout<<"请输入字符串S:";
	cin>>S;
	solve();
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值