中国数学建模编程交流贪婪算法2.docx

上传人:b****2 文档编号:17859272 上传时间:2023-08-04 格式:DOCX 页数:18 大小:25.98KB
下载 相关 举报
中国数学建模编程交流贪婪算法2.docx_第1页
第1页 / 共18页
中国数学建模编程交流贪婪算法2.docx_第2页
第2页 / 共18页
中国数学建模编程交流贪婪算法2.docx_第3页
第3页 / 共18页
中国数学建模编程交流贪婪算法2.docx_第4页
第4页 / 共18页
中国数学建模编程交流贪婪算法2.docx_第5页
第5页 / 共18页
中国数学建模编程交流贪婪算法2.docx_第6页
第6页 / 共18页
中国数学建模编程交流贪婪算法2.docx_第7页
第7页 / 共18页
中国数学建模编程交流贪婪算法2.docx_第8页
第8页 / 共18页
中国数学建模编程交流贪婪算法2.docx_第9页
第9页 / 共18页
中国数学建模编程交流贪婪算法2.docx_第10页
第10页 / 共18页
中国数学建模编程交流贪婪算法2.docx_第11页
第11页 / 共18页
中国数学建模编程交流贪婪算法2.docx_第12页
第12页 / 共18页
中国数学建模编程交流贪婪算法2.docx_第13页
第13页 / 共18页
中国数学建模编程交流贪婪算法2.docx_第14页
第14页 / 共18页
中国数学建模编程交流贪婪算法2.docx_第15页
第15页 / 共18页
中国数学建模编程交流贪婪算法2.docx_第16页
第16页 / 共18页
中国数学建模编程交流贪婪算法2.docx_第17页
第17页 / 共18页
中国数学建模编程交流贪婪算法2.docx_第18页
第18页 / 共18页
亲,该文档总共18页,全部预览完了,如果喜欢就下载吧!
下载资源
资源描述

中国数学建模编程交流贪婪算法2.docx

《中国数学建模编程交流贪婪算法2.docx》由会员分享,可在线阅读,更多相关《中国数学建模编程交流贪婪算法2.docx(18页珍藏版)》请在冰点文库上搜索。

中国数学建模编程交流贪婪算法2.docx

中国数学建模编程交流贪婪算法2

中国数学建模-编程交流-贪婪算法├数学思想

├编程交流

├学术杂谈

├EnglishFans

wh-ee重登录隐身用户控制面板搜索风格论坛状态论坛展区社区服务社区休闲网站首页退出

>>VC++,C,Perl,Asp...编程学习,算法介绍.我的收件箱(0)

中国数学建模→学术区→编程交流→贪婪算法

您是本帖的第890个阅读者

*贴子主题:

贪婪算法

b

等级:

职业侠客

文章:

470

积分:

956

门派:

黑客帝国

注册:

2003-8-28

第11楼

 

1.3.6最小耗费生成树

在例1-2及1-3中已考察过这个问题。

因为具有n

个顶点的无向网络G的每个生成树刚好具有n-1条边,所以问题是用某种方法选择n-1条边使它们形成G的最小生成树。

至少可以采用三种不同的贪婪策略来选择这n-1条边。

这三种求解最小生成树的贪婪算法策略是:

Kruskal算法,Prim算法和Sollin算法。

1.Kruskal算法

(1)算法思想

Kruskal算法每次选择n-

1条边,所使用的贪婪准则是:

从剩下的边中选择一条不会产生环路的具有最小耗费的边加入已选择的边的集合中。

注意到所选取的边若产生环路则不可能形成一棵生成树。

K

ruskal算法分e步,其中e是网络中边的数目。

按耗费递增的顺序来考虑这e

条边,每次考虑一条边。

当考虑某条边时,若将其加入到已选边的集合中会出现环路,则将其抛弃,否则,将它选入。

考察图1-12a中的网络。

初始时没有任何边被选择。

图13-12b显示了各节点的当前状态。

边(1,

6)是最先选入的边,它被加入到欲构建的生成树中,得到图13-12c。

下一步选择边(3,4)并将其加入树中(如图13-

12d所示)。

然后考虑边(2,7),将它加入树中并不会产生环路,于是便得到图13-12e。

下一步考虑边(

2,3)并将其加入树中(如图13-12

f所示)。

在其余还未考虑的边中,(7,4)具有最小耗费,因此先考虑它,将它加入正在创建的树中会产生环路,所以将其丢弃。

此后将边(

5,4)加入树中,得到的树如图13-12g所示。

下一步考虑边(7,5),由于会产生环路,将其丢弃。

最后考虑边(

6,5)并将其加入树中,产生了一棵生成树,其耗费为99。

图1-13给出了Kruskal算法的伪代码。

//在一个具有n个顶点的网络中找到一棵最小生成树

令T为所选边的集合,初始化T=

令E为网络中边的集合

while(E≠)&&(|T|≠n-1){

令(u,v)为E中代价最小的边

E=E-{(u,v)}//从E中删除边

if((u,v)加入T中不会产生环路)将(u,v)加入T

}

if(|T|==n-1)T是最小耗费生成树

else网络不是互连的,不能找到生成树

图13-13Kruskao算法的伪代码

(2)正确性证明

利用前述装载问题所用的转化技术可以证明图13-13的贪婪算法总能建立一棵最小耗费生成树。

需要证明以下两点:

1)

只要存在生成树,Kruskal算法总能产生一棵生成树;2)

产生的生成树具有最小耗费。

令G为任意加权无向图(即G是一个无向网络)。

从12.11.

3节可知当且仅当一个无向图连通时它有生成树。

而且在Kruskal

算法中被拒绝(丢弃)的边是那些会产生环路的边。

删除连通图环路中的一条边所形成的图仍是连通图,因此如果G在开始时是连通的,则T与E中的边总能形成一个连通图。

也就是若G开始时是连通的,算法不会终止于E=

和|T|

现在来证明所建立的生成树T具有最小耗费。

由于G具有有限棵生成树,所以它至少具有一棵最小生成树。

令U为这样的一棵最小生成树,

T与U都刚好有n-1条边。

如果T=U,则T就具有最小耗费,那么不必再证明下去。

因此假设T≠U,令k(k>0)

为在T中而不在U中的边的个数,当然k也是在U中而不在T中的边的数目。

通过把U变换为T来证明U与T具有相同的耗费,这种转化可在k

步内完成。

每一步使在T而不在U中的边的数目刚好减1。

而且U的耗费不会因为转化而改变。

经过k

步的转化得到的U将与原来的U具有相同的耗费,且转化后U中的边就是T中的边。

由此可知,T具有最小耗费。

每步转化包括从T中移一条边e

到U中,并从U中移出一条边f。

边e与f的选取按如下方式进行:

1)令e是在T中而不在U中的具有最小耗费的边。

由于k>0,这条边肯定存在。

2)当把e加入U时,则会形成唯一的一条环路。

令f为这条环路上不在T中的任意一条边。

由于T中不含环路,因此所形成的环路中至少有一条边不在T中。

从e与f的选择方法中可以看出,V=U+{e}-{f}是一棵生成树,且T中恰有k-

1条边不在V中出现。

现在来证明V的耗费与U的相同。

显然,V的耗费等于U的耗费加上边e的耗费再减去边f的耗费。

若e的耗费比f

的小,则生成树V的耗费比U的耗费小,这是不可能的。

如果e的耗费高于f,在Kruskal算法中f会在e

之前被考虑。

由于f不在T中,Kruskal算法在考虑f能否加入T时已将f丢弃,因此f和T中耗费小于或等于f

的边共同形成环路。

通过选择e,所有这些边均在U中,因此U肯定含有环路,但是实际上这不可能,因为U是一棵生成树。

e的代价高于f

的假设将会导致矛盾。

剩下的唯一的可能是e与f具有相同的耗费,由此可知V与U的耗费相同。

(3)数据结构的选择及复杂性分析

为了按耗费非递减的顺序选择边,可以建立最小堆并根据需要从堆中一条一条地取出各边。

当图中有e条边时,需花(e)的时间初始化堆及O

(loge)的时间来选取每一条边。

边的集合T与G中的顶点一起定义了一个由至多n

个连通子图构成的图。

用顶点集合来描述每个子图,这些顶点集合没有公共顶点。

为了确定边(u,v)是否会产生环路,仅需检查u,v

是否在同一个顶点集中(即处于同一子图)。

如果是,则会形成一个环路;如果不是,则不会产生环路。

因此对于顶点集使用两个Fin

d操作就足够了。

当一条边包含在T中时,2个子图将被合并成一个子图,即对两个集合执行Union操作。

集合的Find和U

nion操作可利用8.10.2节的树(以及加权规则和路径压缩)来高效地执行。

Find操作的次数最多为2e,Un

ion操作的次数最多为n-1(若网络是连通的,则刚好是n-1次)。

加上树的初始化时间,算法中这部分的复杂性只比O(n+e)

稍大一点。

对集合T所执行的唯一操作是向T中添加一条新边。

T可用数组t来实现。

添加操作在数组

的一端进行,因为最多可在T中加入n-1条边,因此对T的操作总时间为O(n)。

总结上述各个部分的执行时间,可得图13-13算法的渐进复杂性为O(n+eloge)。

(4)实现

利用上述数据结构,图1-13可用C++代码来实现。

首先定义EdgeNode类(见程序13-6

),它是最小堆的元素及生成树数组t的数据类型

 

----------------------------------------------

plot(100+t+15*cos(3.05*t),t=0..200,coords=polar,axes=none,scaling=constrained);

 

2004-5-2719:

42:

37

b

等级:

职业侠客

文章:

470

积分:

956

门派:

黑客帝国

注册:

2003-8-28

第12楼

 

程序13-6Kruskal算法所需要的数据类型

template

classEdgeNode{

public:

operatorT()const{returnweight;}

private:

Tweight;//边的高度

intu,v;//边的端点

};

为了更简单地使用8.10.2节的查找和合并策略,定义了UnionFind类,它的构造函数是程序8-1

6的初始化函数,Union是程序8-16的加权合并函数,Find是程序8-17的路径压缩搜索函数。

为了编写与网络描述无关的代码,还定义了一个新的类UNetWork,它包含了应用于无向网络的所有函数。

这个类与Und

irected类的差别在于Undirected类中的函数不要求加权边,而UNetWor

k要求边必须带有权值。

UNetWork中的成员需要利用Network类中定义的诸如Begin和Ne

xtVerte

x的遍历函数。

不过,新的遍历函数不仅需要返回下一个邻接的顶点,而且要返回到达这个顶点的边的权值。

这些遍历函数以及有向和无向加权网络的其他函数一起构成了W

Network类(见程序13-7)。

程序13-7WNetwork类

template

classWNetwork:

virtualpublicNetwork

{

public:

virtualvoidFirst(inti,int&j,T&c)=0;

virtualvoidNext(inti,int&j,T&c)=0;

};

象Begin和NextVertex一样,可在AdjacencyWDigrap

h及LinkedWDigraph类中加入函数First与Next。

现在Adjace

ncyWDigraph及LinkedWDigraph类都需要从WNetWor

k中派生而来。

由于AdjacencyWGraph类和LinkedWGrap

h类需要访问UNetwork的成员,所以这两个类还必须从UNetWork中派生而来。

UNetWo

rk:

:

Kruskal的代码见程序13-8,它要求将Edges()定义为NetWork

类的虚成员,并且把UNetWork定义为EdgeNode的友元)。

如果没有生成树,函数返回fals

e,否则返回true。

注意当返回true时,在数组t中返回最小耗费生成树。

程序13-8Kruskal算法的C++代码

template

boolUNetwork:

:

Kruskal(EdgeNodet[])

{//使用Kruskal算法寻找最小耗费生成树

//如果不连通则返回false

//如果连通,则在t[0:

n-2]中返回最小生成树

intn=Vertices();

inte=Edges();

//设置网络边的数组

InitializePos();//图遍历器

EdgeNode*E=newEdgeNode[e+1];

intk=0;//E的游标

for(inti=1;i<=n;i++){//使所有边附属于i

intj;

Tc;

First(i,j,c);

while(j){//j邻接自i

if(i

E[++k].weight=c;

E[k].u=i;

E[k].v=j;}

Next(i,j,c);

}

}

//把边放入最小堆

MinHeap>H

(1);

H.Initialize(E,e,e);

UnionFindU(n);//合并/搜索结构

//根据耗费的次序来抽取边

k=0;//此时作为t的游标

while(e&&k

//生成树未完成,尚有剩余边

EdgeNodex;

H.DeleteMin(x);//最小耗费边

e--;

inta=U.Find(x.u);

intb=U.Find(x.v);

if(a!

=b){//选择边

t[k++]=x;

U.Union(a,b);}

}

DeactivatePos();

H.Deactivate();

return(k==n-1);

}

2.Prim算法

与Kruskal算法类似,Pri

m算法通过每次选择多条边来创建最小生成树。

选择下一条边的贪婪准则是:

从剩余的边中,选择一条耗费最小的边,并且它的加入应使所有入选的边仍是一棵树。

最终,在所有步骤中选择的边形成一棵树。

相反,在Kruskal

算法中所有入选的边集合最终形成一个森林。

Prim算法从具有一个单一顶点的树T开始,这个顶点可以是原图中任意一个顶点。

然后往T中加入一条代价最小的边(u,

v)使TÈ{(u,v)}仍是一棵树,这种加边的步骤反复循环直到T中包含n-1条边。

注意对于边(u,v),u、v

中正好有一个顶点位于T中。

Prim算法的伪代码如图1-1

4所示。

在伪代码中也包含了所输入的图不是连通图的可能,在这种情况下没有生成树。

图1-15显示了对图1-12a使用Pri

m算法的过程。

把图1-14的伪代码细化为C++程序及其正确性的证明留作练习(练习31)。

//假设网络中至少具有一个顶点

设T为所选择的边的集合,初始化T=

设TV为已在树中的顶点的集合,置TV={1}

令E为网络中边的集合

while(E<>)&&(|T|<>n-1){

令(u,v)为最小代价边,其中uTV,vTV

if(没有这种边)break

E=E-{(u,v)}//从E中删除此边

在T中加入边(u,v)

}

if(|T|==n-1)T是一棵最小生成树

else网络是不连通的,没有最小生成树

图13-14Prim最小生成树算法

如果根据每个不在TV中的顶点v选择一个顶点near(v),使得near(v)ÎTV且cost(v,n

ear(v))的值是所有这样的near(v)节点中最小的,则实现Prim算法的时间复杂性为O(n2

)。

下一条添加到T中的边是这样的边:

其cost(v,near(v))最小,且vTV。

3.Sollin算法

Solli

n算法每步选择若干条边。

在每步开始时,所选择的边及图中的n个顶点形成一个生成树的森林。

在每一步中为森林中的每棵树选择一条边,这条边刚好有一个顶点在树中且边的代价最小。

将所选择的边加入要创建的生成树中。

注意一个森林中的两棵树可选择同一条边,因此必须多次复制同一条边。

当有多条边具有相同的耗费时,两棵树可选择与它们相连的不同的边,在这种情况下,必须丢弃其中的一条边。

开始时,所选择的边的集合为空。

若某一步结束时仅剩下一棵树或没有剩余的边可供选择时算法终止。

图1-6给出了初始状态为图1-12a时,使用Sollin算法的步骤。

初始入选边数为0时的情形如图13-12a

时,森林中的每棵树均是单个顶点。

顶点1,2,.,7所选择的边分别是(1.6),(2,7),(3,4),(4,3),(5,4),

(6,1),(7,2),其中不同的边是(1,6),(2,7),(3,4)和(5,4

),将这些边加入入选边的集合后所得到的结果如图13-16a所示。

下一步具有顶点集{1,6}的树选择边(6,5

),剩下的两棵树选择边(2,3),加入这两条边后已形成一棵生成树,构建好的生成树见图13-6b。

Solli

n算法的C++程序实现及其正确性证明留作练习(练习32)。

 

----------------------------------------------

plot(100+t+15*cos(3.05*t),t=0..200,coords=polar,axes=none,scaling=constrained);

 

2004-5-2719:

42:

54

b

等级:

职业侠客

文章:

470

积分:

956

门派:

黑客帝国

注册:

2003-8-28

第13楼

 

练习

8.针对装载问题,扩充贪婪算法,考虑有两条船的情况,算法总能产生最优解吗?

9.已知n个任务的执行序列。

假设任务i需要ti个时间单位。

若任务完成的顺序为1,2,.,n,则任务i完成的时间为ci=i

åj=1tj。

任务的平均完成时间(AvergeCompletionTime,ACT)

为1–nnåi=1ci。

1)考虑有四个任务的情况,每个任务所需时间分别是(4,2,8,1)。

若任务的顺序为1,2,3,4,则ACT是多少?

2)若任务顺序为2,1,4,3,则ACT是多少?

3)创建具有最小ACT的任务序列的贪婪算法分n步来构造该任务序列,在每一步中,从剩下的任务里选择时间最小的任务。

对于1

),利用这种策略获得的任务顺序为4,2,1,3,这种顺序的ACT是多少?

4)写一个C++程序实现3)中的贪婪策略,程序的复杂性应为O(nlogn),试证明之。

5)证明利用3)中的贪婪算法获得的任务顺序具有最小的ACT。

10.若有两个工人执行练习9中的n个任务,需将任务分配给他们,同时他们具有自己的任务执行顺序。

任务完成时间及AC

T的定义同练习9。

使AC

T最小化的一种可行的贪婪算法是:

两个工人轮流选择任务,每次从剩余的任务中选择时间最小的任务。

每个人按照自己所选任务的顺序执行任务。

对于练习9中的例子,假定工人1首先选择任务4,然后工人2选择任务2,工人1选择任务1,最后工人2选择任务3。

1)利用C++程序实现这种策略,其时间复杂性为多少?

2)上述的贪婪策略总能获得最小的ACT吗?

证明结论。

11.1)考虑有m个人可以执行任务,扩充练习10中的贪婪算法。

2)算法能保证获得最优解吗?

证明结论。

3)用C++程序实现此算法,其复杂性是多少?

12.考虑例4-4的堆栈折叠问题。

1)设计一个贪婪算法,将堆栈折叠为最小数目的子堆栈,使得每个子堆栈的高度均不超过H。

2)算法总能保证得到数目最少的子堆栈吗?

证明结论。

3)用C++代码实现1)的算法。

4)代码的时间复杂性是多少?

13.编写C++程序实现0/1背包问题,使用如下启发式方法:

按价值密度非递减的顺序打包。

14.根据k=1的性能受限启发式方法编写一个C++程序来实现0/1背包问题。

15.对于k=1的情况证明用性能受限的启发式方法求解0/1背包问题会发生边界错误。

16.根据k=2的性能受限启发式方法编写一个C++程序来实现0/1背包问题。

17.考虑0≤xi≤1而不是xiÎ{0,1

}的连续背包问题。

一种可行的贪婪策略是:

按价值密度非递减的顺序检查物品,若剩余容量能容下正在考察的物品,将其装入;否则,往背包中装入此物品的一部分。

1)对于n=3,w=[100,10,10],p=[20,15,15]及c=105

上述装入方法获得的结果是什么?

2)证明这种贪婪算法总能获得最优解。

3)用一个C++程序实现此算法。

18.例13-1的渴婴问题是练习17中连续背包问题的一般化,将练习1

7的贪婪算法用于渴婴问题,算法能保证总能得到最优解吗?

证明结论。

19.1)证明当且仅当二分图没有覆盖时,图13-7的算法找不到覆盖。

2)给出一个具有覆盖的二分图,使得图13-7的算法找不到最小覆盖。

20.当第一步选择了顶点1时,给出图13-7的工作过程。

21.

对于二分图覆盖问题设计另外一种贪婪启发式方法,可使用如下贪婪准则:

如果B中的某一个顶点仅被A中一个顶点覆盖,选择A中这个顶点;否则,从A中选择一个顶点,使得它所覆盖的未被覆盖的顶点数目最多。

1)给出这种贪婪算法的伪代码。

2)编写一个C++函数作为Undirected类的成员来实现上述贪婪算法。

3)函数的复杂性是多少?

4)验证代码的正确性。

22.令G为无向图,S为G中顶点的子集,当且仅当S中的任意两个顶点都有一条边相连时,S为完备子图(cliqu

e),完备子图的大小即S中的顶点数目。

最大完备子图(maximum

clique)即具有最大项点数目的完备子图。

在图中寻找最大完备子图的问题(即最大完备子图问题)是一个NP-复杂问题。

1)给出最大完备子图问题的一种可行的贪婪算法及其伪代码。

2)给出一个能用1)中的启发式算法求解最大完备子图的图例,以及不能用该算法求解的一个图例。

3)将1)中的启发式算法实现为Undirected:

:

Clique(intC,intm)

共享成员,其中最大完备子图的大小返回到m中,最大完备子图的顶点返回到C中。

4)代码的复杂性是多少?

23.令G为一无向图,S为G中顶点的子集,当且仅当S中任意两个顶点都无边相连时,S为无关集(independent

set)。

最大无关集即是顶点数目最多的无关集。

在一幅图中寻找最大无关集是一个NP-复杂问

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 自然科学 > 物理

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2