贪心算法
贪心算法是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关 。简单来说就是每一步总是做出在当前看来最好的选择,也就是说贪心算法并不从整体最优考虑,它所作出的选择只是在某种意义上的局部最优选择。
解决贪心问题的思路
1.建立数学模型来描述问题;
2.把求解的问题分成若干个子问题;
3.对每一子问题求解,得到子问题的局部最优解;
4.把子问题的解局部最优解合成原来解问题的一个解。
贪心策略适用的前提是:局部最优策略能导致产生全局最优解。
实际上,贪心算法适用的情况很少。一般,对一个问题分析是否适用于贪心算法,可以先选择该问题下的几个实际数据进行分析,就可做出判断,因为用贪心算法只能通过解局部最优解的策略来达到全局最优解,因此,一定要注意判断问题是否适合采用贪心算法策略,找到的解是否一定是问题的最优解。贪心算法并不是完全不可以使用,贪心策略一旦经过证明成立后,它就是一种高效的算法。
典型例题
1、单源最短路径:迪杰斯特拉算法
算法流程总结:
(1)初始化:顶点集S为空,从V0出发到图上其余各顶点Vi可能到达的最短路径长度初值:D[i]=arc[locate(G,V0)][i](Vi ϵ V), 也就是D[0]=0,其他为+∞。
(2)选择节点Vj,加入集合S,U=V-S,使得:D[j]=Min{D[i]|Vi ϵ U}。其中Vj就是当前求的从v0出发的最短路径的终点
(3)修改从V0出发到集合U上的任一顶点Vk可达的最短路径长度,若:D[j]+arc[j][k]<D[k],则使得D[k]=D[j]+arc[j][k]
(4)重复(2)和(3),直到U为空。求的V0 到其余顶点的最短路径是依路径长度递增的序列。
代码如下:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
struct node{
int a[100][100];
int v;//顶点数目
};
struct node a;
int dist[100];//到源点的距离
int flag[100];//标记是否已添加
int pre[100];//指明当前点的路径的上一个点
int max1=99999;
int zuixiao()
{
int min1=99999;
int minv;
for(int i=0;i<a.v;i++)
{
if(flag[i]==0&&dist[i]<min1)
{
min1=dist[i];
minv=i;
}
}
if(min1<max1)
return minv;
else return -1;
}
bool dijkstra(int x)
{
for(int i=0;i<a.v;i++)
{
flag[i]=0;
dist[i]=a.a[x][i];
if(dist[i]<max1)
pre[i]=x;
else pre[i]=-1;
}
flag[x]=1;
dist[x]=0;
while(1)
{
int y=zuixiao();
if(y==-1)
break;
flag[y]=1;
for(int i=0;i<a.v;i++)
{
if(flag[i]==0&&a.a[y][i]<max1)
{
if(a.a[y][i]<0)
return false;
if(dist[y]+a.a[y][i]<dist[i])
{
dist[i]=dist[y]+a.a[y][i];
pre[i]=y;
}
}
}
}
return true;
}
int main()
{
cout<<"请输入点的个数"<<endl;
int n;
cin>>n;
a.v=n;
cout<<"请输入图的距离矩阵"<<endl;
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
cin>>a.a[i][j];
}
}
int x;
cout<<"请选择源点"<<endl;
cin>>x;
dijkstra(x);
for(int i=0;i<n;i++)
{
cout<<dist[i]<<" ";
}
}
2、多元哈夫曼编码问题:
Problem Description
在一个操场的四周摆放着n堆石子。现要将石子有次序地合并成一堆。规定每次至少选2 堆最多选k堆石子合并成新的一堆,合并的费用为新的一堆的石子数。试设计一个算法,计算出将n堆石子合并成一堆的最大总费用和最小总费用。
对于给定n堆石子,计算合并成一堆的最大总费用和最小总费用。
Input
输入数据的第1 行有2 个正整数n和k(n≤100000,k≤10000),表示有n堆石子,每次至少选2 堆最多选k堆石子合并。第2 行有n个数(每个数均不超过 100),分别表示每堆石子的个数。
Output
将计算出的最大总费用和最小总费用输出,两个整数之间用空格分开。
Sample Input
7 3
45 13 12 16 9 5 22
Sample Output
593 199
代码如下:
//多元哈夫曼编码
//当构造WPL最小的树时,构造k路完全哈夫曼树
//的WPL最小,即如果点不够就用0节点补全,
//若要使构造树的WPL最大,就使用二路哈夫曼树,
//要选择值最大的两个数进行合并
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
struct node {
int x;
};
struct tmp1{
bool operator ()(struct node a,struct node b)
{
return a.x>b.x;//小顶堆
}
};
struct tmp2{
bool operator ()(struct node a,struct node b)
{
return a.x<b.x;//大顶堆
}
};
int main()
{
int n;
int y;
cin>>n>>y;
int u=(n-1)%(y-1);
int u1;
if(u==0)u1=0;
else
u1=y-u-1;//u1是添加的空归并段个数
struct node a[n+u1];
for(int i=0;i<n;i++)
{
cin>>a[i].x;
}
for(int i=n;i<n+u1;i++)
a[i].x=0;
priority_queue<struct node,vector<struct node>,tmp1 > q;//小顶堆
priority_queue<struct node,vector<struct node>,tmp2 > p;//大顶堆
int count1=0;
int count2=0;
for(int i=0;i<n+u1;i++)
{
q.push(a[i]);
}
while(q.size()>0)
{
int ans=0;
struct node b[y];
for(int i=0;i<y;i++)
{
b[i]=q.top();
q.pop();
ans+=b[i].x;
}
struct node c;
c.x=ans;
if(q.size()!=0)
q.push(c);
count1+=ans;
}
cout<<count1<<endl;;
for(int i=0;i<n;i++)
{
p.push(a[i]);
}
while(p.size()>0)
{
int ans=0;
struct node b[2];
for(int i=0;i<2;i++)
{
b[i]=p.top();
p.pop();
ans+=b[i].x;
}
struct node c;
c.x=ans;
if(p.size()!=0)
p.push(c);
count2+=ans;
}
cout<<count2<<endl;
}