贪心算法不是从整体最优考虑,是做出的选择只是在某种意义上的局部最优选择,得到最终结果也是整体最优。**
基本要素
1。贪心选择性质
通过一系列的局部最优达到整体最优
2。最优子结构性质
一个问题的最优解包含子问题的最优解
下面是几个贪心选择算法实现的实例(局部最优达到整体最优)
活动安排问题
该问题要求高效的安排一系列争用某一公共资源的活动。
问题描述:
设有n个活动的集合E={1,2,.....,n},其中每个活动都要求使用同一个资源(如演讲会场),而在同一时间内只有一个活动能使用这一资源。每个活动i都有一个要求使用该资源的起始时间Si和一个结束时间Fi,且Si<Fi。如果选择了活动i,则他在改时间区间[Si,Fi]内占用资源,若区间[Si,Fi] 和区间[Sj,Fj]不相交,则称活动i与活动j是相容的。活动安排问题是要求在所给的活动集合范围内选出最大的相容的活动子集合。
问题解析:
查找每个活动的结束时间,每一次选择时查找具有最早结束时间的相容的活动,先把n个活动按时间的结束时间非减序排列,这样,贪心选择是取当前活动集合中结束时间最早的活动就归结为取当前活动集合中排在最前面的活动。
贪心选择性质
首先将活动安排问题数学化,设有n个活动的集合 e= { 1 ,2 ,…,n },每个活动 i 都有一个要求使用该资源的起始时问si 和一个结束时问fi 。即k是所需最少资源的个数。设活动已排序,( a1 , a2 , … ,ak )是所需要的k个已安排了活动的资源。 ①当k = 1时,也就是所有的活动在一个资源里相容,a1 是满足贪心选择性质的最优解;②当k >= 2时,取b1=a1,bk=ak (即bk是安排了m个活动的一个资源,(n-m)个活动都安排在b1 到bk-1个资源里)。就是(n-m)个活动安排需要(k -1)个资源。则(b1,b2 ,…,bk-1 )是(n - m)个活动可行解。
另一方面,由{( a1 ,a2,…,ak)-ak}=(b1,b2,…,bk-1)知,(b1,b2,…,bk-1)也是满足贪心选择性质的最优解,所以,活动安排问题具有贪心选择性质
最优子结构性质
( a1,a2, …,ak )是n个活动的集合e= {1,2 ,…,n }所需资源的最优解。设a1中安排了m个相容的活动,那么也就是说(n-m)个活动完全安排需要k-1个资源。假设(n - m)个活动安排只需要k-2个资源或则更少的资源。也就是说n个活动安排只需要k-1个资源就可以安排完,则前后出现矛盾。一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。
算法如下:
//i为当前的活动扫描到的下标
//j为被选中的活动的下标
public static int greedyselector (int []s,int []f,boolean []a)
{
int n=s.length-1;
a[1]=true;
int j=1;
int count=1;//前面有1个活动
for(int i=2;i<=n;i++)//扫描
{
if(s[i]>f[j])
{
a[i]=true;
j=i;
count++;
}
else a[i]=false;
}
return count;
}
最优装载问题
问题描述:
有一批集装箱要装上一艘载重量为c的轮船。其中
集装箱i的重量为Wi,最优装载问题要求在装载体积不受限制的情况下,将尽可能多的集装箱装上轮船。
问题分析:
该问题可形式化描述为:
算法描述:
最优装载问题可用贪心算法求解。采用重量最轻者
先装的贪心选择策略,可产生最优装载问题的最优解。
具体算法描述如下:
public float loading(float c,float[] w,int[] x){
int n=w.length;
Element[] d=new Element[n];
for(int i=0;i<n;i++){
d[i]=new Element(w[i],i);
}
java.util.Arrays.sort(d);
for(int i=0;i<n;i++)
x[i]=0;
float op=0;
for(int i=0;i<n&&d[i].w<=c;i++){
op+=d[i].w;
c-=d[i].w;
x[d[i].i]=1;
}
return op;
}
static class Element implements Comparable{
float w;
int i;
public Element(float ww,int ii){
w=ww;
i=ii;
}
@Override
public int compareTo(Object x) {//按每个重量从小到大排列
float xx=((Element)x).w;
if(this.w<xx) return -1;
if(this.w==xx) return 0;
return 1;
}
贪心选择性质
按每次装入重量最小的作为贪心的选择,那么设重量从小到大(x1,x2,...,xn)是最优装载问题的一个最优解.设k=min{i|xi=1}.当k=1的时候(x1,x2,...,xn)是一个满足贪心性质的最优解.当k>1,令y=1,yk=0,yi=xi,i不等于k,那么yi与对应重量wi的乘积的和=w1-wk+wixi乘积的和,这个是小于等于本身wi*xi乘积的和的,小于容量c因此,(y1,y2,...,yn)也是最优装载问题的可行解.然而,xi的和与yi的和是相等的,也就是说,(y1,y2,...,yn)也是满组贪心性质的最优解.矛盾.
最优子结构性质
设(x1,x2,……xn)是最优装载问题的满足贪心选择性质的最优解,则易知,x1=1,(x2,x3,……xn)是轮船载重量为c-w1,待装船集装箱为{2,3,……n}时相应最优装载问题的最优解。因此,最优装载问题具有最优子结构性质。
单源最短路径—-Dijkstra算法
基本思想:
设置顶点集合S并不断的做贪心选择来扩展这个集合,一个顶点属于集合S当且仅当从源到该顶点的最短路径长度已知,初始时,S中仅含有源,设U是G的某一顶点,把源到u且中间只经过S中顶点的路称作从源到u的特殊路径,并用数组dist记录当前每个顶点所对应的最短特殊路径长度。Dijkstra算法每次从V-S中取出具有最短路径长度的顶点u,将u添加到S中,同时对数组dist数组进行修改,一旦S包含了所有V中顶点,dist就记录从源到所有其他顶点的最短路径长度。
算法如下:
其中输入带权有向图是G=(V,E),V={1,2,…,n},顶点v是源。c是一个二维数组,c[i][j]表示边(i,j)的权。当(i,j)不属于E时,c[i][j]是一个大数。dist[i]表示当前从源到顶点i的最短特殊路径长度。在Dijkstra算法中做贪心选择时,实际上是考虑当S添加u之后,可能出现一条到顶点的新的特殊路,如果这条新特殊路是先经过老的S到达顶点u,然后从u经过一条边直接到达顶点i,则这种路的最短长度是dist[u]+c[u][i]。如果dist[u]+c[u][i]<dist[i],则需要更新dist[i]的值。步骤如下:
(1) 初始时,S只包含起点s;U包含除s外的其他顶点,且U中顶点的距离为"起点s到该顶点的距离"[例如,U中顶点v的距离为(s,v)的长度,然后s和v不相邻,则v的距离为∞]。
(2) 从U中选出"距离最短的顶点k",并将顶点k加入到S中;同时,从U中移除顶点k。
(3) 更新U中各个顶点到起点s的距离。之所以更新U中顶点的距离,是由于上一步中确定了k是求出最短路径的顶点,从而可以利用k来更新其它顶点的距离;例如,(s,v)的距离可能大于(s,k)+(k,v)的距离。
(4) 重复步骤(2)和(3),直到遍历完所有顶点。
**下面是一个例子:**
初始状态:S是已计算出最短路径的顶点集合,U是未计算除最短路径的顶点的集合!
第1步:将顶点D加入到S中。
此时,S={D(0)}, U={A(∞),B(∞),C(3),E(4),F(∞),G(∞)}。 注:C(3)表示C到起点D的距离是3。
第2步:将顶点C加入到S中。
上一步操作之后,U中顶点C到起点D的距离最短;因此,将C加入到S中,同时更新U中顶点的距离。以顶点F为例,之前F到D的距离为∞;但是将C加入到S之后,F到D的距离为9=(F,C)+(C,D)。
此时,S={D(0),C(3)}, U={A(∞),B(23),E(4),F(9),G(∞)}。
第3步:将顶点E加入到S中。
上一步操作之后,U中顶点E到起点D的距离最短;因此,将E加入到S中,同时更新U中顶点的距离。还是以顶点F为例,之前F到D的距离为9;但是将E加入到S之后,F到D的距离为6=(F,E)+(E,D)。
此时,S={D(0),C(3),E(4)}, U={A(∞),B(23),F(6),G(12)}。
第4步:将顶点F加入到S中。
此时,S={D(0),C(3),E(4),F(6)}, U={A(22),B(13),G(12)}。
第5步:将顶点G加入到S中。
此时,S={D(0),C(3),E(4),F(6),G(12)}, U={A(22),B(13)}。
第6步:将顶点B加入到S中。
此时,S={D(0),C(3),E(4),F(6),G(12),B(13)}, U={A(22)}。
第7步:将顶点A加入到S中。
此时,S={D(0),C(3),E(4),F(6),G(12),B(13),A(22)}。
此时,起点D到各个顶点的最短距离就计算出来了:A(22) B(13) C(3) D(0) E(4) F(6) G(12)。
下面是老师上课讲解的
Dijkstra最短路径。
* 即,统计图中"顶点vs"到其它各个顶点的最短路径。
*
* 参数说明:
* vs -- 起始顶点(start vertex)。即计算"顶点vs"到其它顶点的最短路径。
* prev -- 前驱顶点数组。即,prev[i]的值是"顶点vs"到"顶点i"的最短路径所经历的全部顶点中,位于"顶点i"之前的那个顶点。
* dist -- 长度数组。即,dist[i]是"顶点vs"到"顶点i"的最短路径的长度。
*/
public void dijkstra(int vs, int[] prev, int[] dist) {
// flag[i]=true表示"顶点vs"到"顶点i"的最短路径已成功获取
int n=dist.length-1;
if(v<1||v>n) return -1;
boolean[] flag = new boolean[mVexs.length];
// 初始化
for (int i = 0; i < mVexs.length; i++) {
dist[i]=a[v][i];
flag[i] = false; // 顶点i的最短路径还没获取到。
if(dist[i]==Float.MAX_VALUE)
prev[i] = 0; // 顶点i的前驱顶点为0。
else
prev[i] = v; // 顶点i的前驱顶点为v。
}
// 对"顶点vs"自身进行初始化
flag[vs] = true;
dist[vs] = 0;
for (int i = 1; i <n; i++) {
float temp=Float.MAX_VALUE;
int u=v;
for (int j = 0; j <=n; j++) {
if (!s[j]== && (dist[j]<temp)) {
u = j;
temp=dist[i];
}
}
// 标记"顶点k"为已经获取到最短路径
flag[k] = true;
// 修正当前最短路径和前驱顶点
// 即,当已经"顶点k的最短路径"之后,更新"未获取最短路径的顶点的最短路径和前驱顶点"。
for (int j = 0; j < mVexs.length; j++) {
if (!s[j]== && (a[u][j]<Float.MAX_VALUE)) {
float newdist=dist[u]+a[u][j];
if(newdist<dist[])
{
//dist[j]减少
dist[j] = newdist;
prev[j] = u;
}
}
}
}