计算机算法设计与分析实验指导书,算法设计与分析-附录 实验指导

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼

“算法分析与设计”是一门面向设计的,处于计算机类相关学科核心地位的课程。无论是计算机系统、系统软件和解决计算机的各种应用课题都可归结为算法的设计。通过本课程的学习,学生将消化理论知识,加深对讲授内容的理解,尤其是一些算法的实现及其应用;并掌握计算机领域中许多常用的非数值计算的算法设计技术:递归算法、分治算法、贪心算法、动态规划算法、回溯算法、分支限界算法,增强独立编程和调试程序的能力;与此同时,读者将对算法的分析与设计有更深刻的认识,并掌握算法分析的方法。

上机实验一般应包括以下几个步骤。

(1)准备好上机所需的程序。手编程序应书写整齐,并经人工检查无误后才能上机。

(2)上机输入和调试自己所编写的程序。一人一组,独立上机调试。若上机时出现问题,最好独立解决。

(3)上机结束后,整理出实验报告。实验报告应包括:题目、程序清单、运行结果、对运行情况所做的分析等。

实验一 递归与分治算法

1.1 实验目的与要求

1. 进一步熟悉C/C++语言的集成开发环境;

2. 通过本实验加深对递归与分治策略的理解和运用。

1.2 实验课时

4学时(课内2学时+课外2学时)

1.3 实验原理

分治(Divide-and-Conquer)的思想:一个规模为n的复杂问题的求解,可以划分成若干个规模小于n的子问题,再将子问题的解合并成原问题的解。

需要注意的是,分治法使用递归的思想。划分后的每一个子问题与原问题的性质相同,可用相同的求解方法。最后,当子问题规模足够小时,可以直接求解,然后逆求原问题的解。

1.4 实验题目

1. 范例:汉诺塔(hanoi)问题

设有A、B、C共3根塔座,在塔座A上堆叠n个金盘,每个盘大小不同,只允许小盘在大盘之上,最底层的盘最大,如附图1所示。现在要求将A上的盘全都移到C上,在移的过程中要遵循以下原则:每次只能移动一个盘;圆盘可以插在A、B和C任一个塔座上;在任何时刻,大盘不能放在小盘的上面。附图1所示为初始状态。

hanoi问题递归求解思想:

我们把一个规模为n的hanoi问题:1到n号盘按照移动规则从A上借助B移到C上表示为H(A,B,C,n);原问题划分成如下三个子问题:

(1)将1到n-1号盘按照移动规则从A上借助C移到B上H(A,C,B,n-1);

(2)将n号盘从A上直接移到C上;

(3)将1到n-1号盘按照移动规则从B上借助A移到C上H(B,A,C,n-1);

经过三个子问题求解,原问题的也即求解完成。

hanoi问题递归求解代码:

void H(char A,charB,charC,int n)

{

if(n>0)

{

H(A,C,B,n-1);

printf("%d from %c to %c",n,A,C);

H(B,A,C,n-1);

}

}

2. 上机题目:格雷码构造问题

Gray码是一个长度为2n的序列。序列无相同元素,每个元素都是长度为n的串,相邻元素恰好只有一位不同。试设计一个算法对任意n构造相应的Gray码(分治、减治、变治皆可)。

对于给定的正整数n,格雷码为满足如下条件的一个编码序列。

(1)序列由2n个编码组成,每个编码都是长度为n的二进制位串。

(2)序列中无相同的编码。

(3)序列中位置相邻的两个编码恰有一位不同。

1.5 思考题

(1)递归的关键问题在哪里?

(2)递归与非递归之间如何实现程序的转换?

(3)分析二分查找和快速排序中使用的分治思想。

(4)分析二次取中法和锦标赛算法中的分治思想。

实验二 贪心算法

2.1 实验目的与要求

1. 理解贪心算法的基本思想;

2. 运用贪心算法解决实际问题,加深对贪心算法的理解和运用。

2.2 实验课时

4学时(课内2学时+课外2学时)

2.3 实验原理

贪心算法的思想:

(1)贪心算法(Greedy Approach)能得到问题的最优解,要证明我们所做的第一步选择一定包含着一个最优解,即存在一个最优解的第一步是从我们的贪心选择开始。

(2)在做出第一步贪心选择后,剩下的子问题应该是和原问题类似的规模较小的子问题,为此我们可以用数学归纳法来证明贪心选择能得到问题的最优解。

2.4 实验题目

1. 范例:单源最短路径

在无向图 G=(V,E) 中,假设每条边 E[i] 的长度为 w[i],找到由顶点 V到其余各点的最短路径。

按路径长度递增次序产生最短路径算法。

首先把V分成两组:

(1)S:已求出最短路径的顶点的集合;

(2)V-S=T:尚未确定最短路径的顶点集合。

然后将T中顶点按最短路径递增的次序加入到S中,需保证:

(1)从源点V到S中各顶点的最短路径长度都不大于从V到T中任何顶点的最短路径长度;

(2)每个顶点对应一个距离值。S中顶点:从V到此顶点的最短路径长度;T中顶点:从V到此顶点的只包括S中顶点作中间顶点的最短路径长度。

依据:可以证明V到T中顶点Vk的最短路径,或是从V到Vk的直接路径的权值;或是从V经S中顶点到Vk的路径权值之和。(反证法可证)

单源最短路径代码:

void Dijkstra(AdjMGraph G,int v0,int *dist, int *path)

{

int n=G.Vertices.size;

inti,j,k ,pre ,min ;

int *s=(int *)malloc(sizeof(int)*n);

for(i=0;i

{

s[i]=0;

dist[i]=G->edge[v0][i];

if(i!=v0&&dist[i]

path[i]=v0;

else

path[i]=-1;

}

s[v0]=1; //标记v0已从集合T中加入到S

for(i=1;i

{

min=MaxWeight;

for(j=0;j

if (s[j]==0&&dist[j]< min )

{

min=dist[j];

k=j;

}

if(min == MaxWeight)

return;

S[k]=1; //标记k已从集合T中加入到S

for (j=0;j

if (s[j]==0&&dist[j]>dist[k]+ G->edge[k][j])

{

dist[j]=dist[k]+G->edge[k][j];

path[j]=k;

}

}

}

2. 上机题目:最小延迟调度问题

给定等待服务的客户集合A={1,2,…,n},预计对客户i的服务时长为ti>0,T=(t1,t2,…,tn),客户i希望的服务完成时刻为di>0,D=(d1,d2,…,dn);一个调度f:A→N,f(i)为客户i的开始时刻。如果对客户i的服务在di之前结束,那么对客户i的服务没有延迟,即如果在di之后结束,那么这个服务就被延迟了,延迟的时间等于该服务的实际完成时刻f(i)+ti减去预期结束时刻di。一个调度f的最大延迟是所有客户延迟时长的最大值maxi∈A{f(i)+ti-di}。附图2所示是不同调度下的最大延迟。使用贪心策略找出一个调度使得最大延迟达到最小。

2.5 思考题

(1)哈夫曼编码问题的编程如何实现?

(2)使用贪心策略求解背包问题。

(3)分析普里姆算法和克鲁斯卡尔算法中的贪心策略。

(4)思考如何证明贪心策略的正确性。

(5)使用贪心策略求解多机调度问题。

实验三 动态规划算法

3.1 实验目的与要求

1. 理解动态规划算法的基本思想;

2. 运用动态规划算法解决实际问题,加深对贪心算法的理解和运用。

3.2 实验课时

4学时(课内2学时+课外2学时)

3.3 实验原理

动态规划(Dynamic Programming)算法思想:把待求解问题分解成若干个子问题,先求解子问题,然后由这些子问题的解得到原问题的解。动态规划求解过的子问题的结果会被保留下来,不像递归那样每个子问题的求解都要从头开始反复求解。动态规划求解问题的关键在于获得各个阶段子问题的递推关系式:

(1)分析原问题的最优解性质,刻画其结构特征;

(2)递归定义最优值;

(3)自底向上(由后向前)的方式计算最优值;

(4)根据计算最优值时得到的信息,构造一个最优解。

3.4 实验题目

1. 范例:多阶段决策问题——最短路径

如附图3所示从A0点要铺设一条管道到达A6点,中间必须经过5个中间站,第一站可以在A1、B1两地中任选一个站点,其他站点类似,连接两地间管道的距离(造价)用如附图3所示中连线的数字表示,求A0到A6间的最短造价路径。

代码清单:

#define m1 8

#define m 7

#define n1 17

#include

#include

main()

{

inti,j,k,fw;

int n[m1];

int c[m1][m1][m1];

int s[m1][m1];

int f[m1][m1];

int b[n1];

//clrscr();

for (i=0;i<=m;i++)

{

n[i]=0;

for(j=0;j<=m;j++)

{

s[i][j]=0;

f[i][j]=0;

for(k=0;k<=m;k++)

c[i][j][k]=0;

}

}

printf("\n输入每个阶段的结点数目:");

for(i=1;i<=m;i++)

scanf("%d",&n[i]);

printf("\n输入每阶段的结点的编号:");

for(i=1;i<=m;i++)

for(j=1;j<=n[i];j++)

scanf("%d",&s[i][j]);

printf("\n输入i阶段结点s[i,j]与i+1阶段中结点s[i+1,k]之间的权值:");

for(i=1;i<=m-1;i++)

for(j=1;j<=n[i];j++)

for(k=1;k<=n[i+1];k++)

scanf("%d",&c[i][j][k]);

printf("\n反推法计算第一步的最优值:");

for(j=1;j<=n[m-1];j++)

{

f[m-1][j]=c[m-1][j][1];

b[s[m-1][j]]=s[m][1];

}

printf("\n计算第二步递归定义的其他最优值:");

for(i=m-2;i>=1;i--)

for(j=1;j<=n[i];j++)

{

f[i][j]=c[i][j][1]+f[i+1][1];

b[s[i][j]]=s[i+1][1];

if(n[i+1]!=1)

for(k=2;k<=n[i+1];k++)

{

fw=c[i][j][k]+f[i+1][k];

if(f[i][j]>fw)

{

f[i][j]=fw;

b[s[i][j]]=s[i+1][k];

}

}

}

i=1;

printf("\n%4d",i);

while(b[i]!=n1-1)

{

printf("%4d",b[i]);

i=b[i];

}

printf("%4d\n",b[i]);

printf("\nthemininum cost is %d:",f[1][1]);

getch();

}

/*5 3 1 3 6 100 100 8 7 6 6 8 100 3 5 100 100 3 3 100 8 4 2 2 100 100 1 2 100 3 3 3 5

5 2 6 6 4 3*/

2. 上机题目:最大子段和问题

给定n个整数(可以为负数)组成的序列(a1,a2,…,an),使用动态规划思想求该序列的子段和的最大值。注:当所有整数均为负整数时,其最大子段和为0。

例如,对于六元组(-2, 11, -4, 13, -5, -2),其最大字段和为:a2+ a3+ a4= 20。

除了动态规划,该问题可以使用顺序求和+比较(蛮力法)和分治法求解,思考其求解过程。

3.5 思考题

(1)深刻理解动态规划与递归求解问题的区别是什么?

(2)动态规划思想解题的步骤是什么?

(3)动态规划思想和贪心算法在求解问题时的区别是什么?

(4)使用动态规划算法求解最长公共子序列(LCS)问题。

(5)使用动态规划算法求解最长最大字段和问题。

实验四 回溯算法

4.1 实验目的与要求

1. 通过回溯算法的示例程序理解回溯算法的基本思想;

2. 运用回溯算法解决实际问题,进一步加深对回溯算法的理解和运用。

4.2 实验课时

4学时(课内2学时+课外2学时)。

4.3 实验原理

回溯算法(Backtrack)的基本做法是搜索,或是一种组织得井井有条的、能避免不必要搜索的穷举式搜索法。这种方法适用于解一些组合数相当大的问题。

回溯算法在问题的解空间树中,按深度优先策略,从根结点出发搜索解空间树。算法搜索至解空间树的任意一点时,先判断该结点是否包含问题的解:如果肯定不包含,则跳过对该结点为根的子树的搜索,逐层向其祖先结点回溯;否则,进入该子树,继续按深度优先策略搜索。

回溯算法的基本步骤:

(1)针对所给问题,定义问题的解空间;

(2)确定易于搜索的解空间结构;

(3)以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索。

常用剪枝函数:

(1)用约束函数在扩展结点处剪去不满足约束的子树;

(2)用限界函数剪去得不到最优解的子树。

4.4 实验题目

1. 范例:0-1背包问题

有n件物品和一个容量为c的背包。第i件物品的容量是w[i],价值是p[i]。求解将哪些物品装入背包可使价值总和最大。

代码清单:

#include

#define c 30

#define n 3

int bound(inti,intcw,intcp,int w[],int p[])

{

int cleft=c-cw;

int b=cp;

while(i<=n && w[i]<=cleft)

{

cleft-=w[i];

b+=p[i];

i++;

}

if(i<=n)

b+=p[i]/w[i]*cleft;

return b;

}

void back(inti,intcw,intcp,int *bestp,int w[],int p[],int x[])

{

if(i>n)

{

*bestp=cp;

return;

}

if(cw+w[i]<=c)

{

cw+=w[i];

cp+=p[i];

x[i]=1;

back(i+1,cw,cp,bestp,w,p,x);

cw-=w[i];

cp-=p[i];

}

if(bound(i+1,cw,cp,w,p)>*bestp)

{

x[i]=0;

back(i+1,cw,cp,bestp,w,p,x);

}

}

main()

{

intcw=0,cp=0,bestp=0,x[5];

int w[]={0,16,15,15},p[]={0,45,25,25};

inti;

clrscr();

back(1,cw,cp,&bestp,w,p,x);

printf(" %d\n",bestp);

for(i=1;i<=n;i++)

printf("%2d",x[i]);

getch();

}

2. 上机题目:排兵布阵问题

某游戏中,不同的兵种处于不同的地形上时,其攻击能力也一样,现有n个不同兵种的角色(1, 2, ..., n),需安排在某战区n个点上,角色i在j点上的攻击力为Aij,使用回溯法设计一个布阵方案,使总的攻击力最大。注:个人决定A矩阵的初始化工作。该问题求解算法的输入数据形如附图4所示。

4.5 思考题

(1)什么是启发式搜索问题?

(2)搜索算法的解空间树的如何定义?

(3)0-1背包问题的动态规划算法如何求解?

(4)n皇后问题使用回溯法如何求解?

(5)使用回溯法求解装载问题。

实验五 分支限界算法

5.1 实验目的与要求

1. 通过分支限界算法的示例程序进一步理解分支限界算法的基本思想;

2. 运用分支限界算法解决实际问题,进一步加深对分支限界算法的理解和运用。

5.2 实验课时

4学时(课内2学时+课外2学时)。

5.3 实验原理

分枝限界(Branch-and-Bound)算法是另一种系统地搜索解空间的方法,它与回溯算法的主要区别在于对E-结点的扩充方式。每个活结点有且仅有一次机会变成E-结点。当一个结点变为E-结点时,则生成从该结点移动一步即可到达的所有新结点。在生成的结点中,抛弃那些不可能导出(最优)可行解的结点,其余结点加入活结点表,然后从表中选择一个结点作为下一个E-结点。从活结点表中取出所选择的结点并进行扩充,直到找到解或活动表为空,扩充过程才结束。

有两种常用的方法可用来选择下一个E-结点(虽然也可能存在其他的方法):

(1)先进先出(F I F O)即从活结点表中取出结点的顺序与加入结点的顺序相同,因此活结点表的性质与队列相同。

(2)(优先队列)最小耗费(LC)或最大收益法在这种模式中,每个结点都有一个对应的耗费或收益。如果查找一个具有最小耗费的解,则活结点表可用最小堆来建立,下一个E-结点就是具有最小耗费的活结点;如果希望搜索一个具有最大收益的解,则可用最大堆来构造活结点表,下一个E-结点是具有最大收益的活结点。

5.4 实验题目

1. 范例:旅行商售货员(TSP)问题

某售货员要到若干城市去推销商品,已知各城市之间的路程(或旅费)。他要选定一条从驻地出发,经过每个城市一次,最后回到驻地的路线,使总的路程(或总旅费)最小。

实验提示:旅行商问题的解空间树是一个排列树。有两种实现的方法。第一种是只使用一个优先队列,队列中的每个元素中都包含到达根的路径。另一种是保留一个部分解空间树和一个优先队列,优先队列中的元素并不包含到达根的路径。以下为第一种方法。

由于我们要寻找的是最小耗费的旅行路径,因此可以使用最小耗费分支限界算法。在实现过程中,使用一个最小优先队列来记录活结点,队列中每个结点的类型为MinHeapNode。每个结点包括如下区域:x(从1到n的整数排列,其中x[0] = 1),s(一个整数,使得从排列树的根结点到当前结点的路径定义了旅行路径的前缀x[0:s],而剩余待访问的结点是x[s+ 1 : n- 1 ]),cc(旅行路径前缀,即解空间树中从根结点到当前节点的耗费),lcost(该节点子树中任意叶节点中的最小耗费),rcost(从顶点x[s: n- 1]出发的所有边的最小耗费之和)。当类型为MinHeapNode( T)的数据被转换成为类型T时,其结果即为lcost的值。分支限界算法的代码见程序。

程序首先生成一个容量为100的最小堆栈,用来表示活结点的最小优先队列。活结点按lcost值从最小堆中取出。接下来,计算有向图中从每个顶点出发的边中耗费最小的边所具有的耗费MinOut。如果某些顶点没有出边,则有向图中没有旅行路径,搜索终止。如果所有的顶点都有出边,则可以启动最小耗费分支限界搜索。根的孩子B作为第一个E-结点,在此结点上,所生成的旅行路径前缀只有一个顶点1,因此s=0, x[0]=1, x[1:n-1]是剩余的顶点(即顶点2,3,…,n)。旅行路径前缀1的开销为0,即cc= 0,并且,rcost=n&&i=1时MinOut。

在程序中,bestc给出了当前能找到的最少的耗费值。初始时,由于没有找到任何旅行路径,因此bestc的值被设为NoEdge。

旅行商问题的最小耗费分枝定界算法如下。

templateT AdjacencyWDigraph::BBTSP(int v[])

{ // 旅行商问题的最小耗费分枝定界算法

// 定义一个最多可容纳1000个活节点的最小堆栈

MinHeap>H(1000);

T *MinOut = new T [n+1];

// 计算MinOut= 离开顶点i的最小耗费边的耗费

T MinSum = 0; // 离开顶点i的最小耗费边的数目

for (inti = 1; i<= n; i++)

{

T Min = NoEdge;

for (int j = 1; j <= n; j++)

if (a[j] != NoEdge&& (a[j] < Min || Min == NoEdge))

Min = a[j];

if (Min == NoEdge)

return NoEdge; // 此路不通

MinOut = Min;

MinSum += Min;

}

// 把E-结点初始化为树根

MinHeapNode E;

E.x = new int [n];

for (i=0; i

E.x=i+1;

E.s=0; // 局部旅行路径为x [ 1 : 0 ]

E.cc=0; // 其耗费为0

E.rcost=MinSum;

T bestc=NoEdge; // 目前没有找到旅行路径

// 搜索排列树

while (E.s

{ // 不是叶子

if(E.s==n-2)

{ // 叶子节点的父结点

// 通过添加两条边来完成旅行

// 检查新的旅行路径是不是更好

if (a[E.x[n-2]][E.x[n-1]]!= NoEdge&& a[E.x[n-1]][1]!=NoEdge&& (E.cc +

a[E.x[n-2]][E.x[n-1]]+a[E.x[n-1]][1]

{ // 找到更优的旅行路径

bestc=E.cc + a[E.x[n-2]][E.x[n-1]]+a[E.x[n-1]][1];

E.cc=bestc;

E.lcost=bestc;

E.s++;

H.Insert( E ) ;

}

else

delete [] E.x;

}

else

{ // 产生孩子

for (int i=E.s+1; i

if (a[E.x[E.s]][E.x]!=NoEdge)

{ // 可行的孩子,限定了路径的耗费

T cc=E.cc+a[E.x[E.s]][E.x];

T rcost=E.rcost-MinOut[E.x[E.s]];

T b=cc+rcost; // 下限

if (b

{// 子树可能有更好的叶子

// 把根保存到最大堆栈中

MinHeapNode N;

N.x=new int [n];

for (int j=0; j

N.x[j]=E.x[j];

N.x[E.s+1]=E.x;

N.x=E.x[E.s+1];

N.cc=cc;

N.s=E.s+1;

N.lcost=b;

N.rcost=rcost;

H.Insert( N ) ;

}

} // 结束可行的孩子

delete [] E.x;

} // 对本结点的处理结束

try

{

H.DeleteMin(E);

} // 取下一个E-结点

catch(OutOfBounds)

{

break;

} // 没有未处理的结点

}

if (bestc==NoEdge)

return NoEdge; // 没有旅行路径将最优路径复制到v[1:n] 中

for (i=0; i

v[i+1]=E.x;

while (true)

{ // 释放最小堆中的所有结点

delete [] E.x;

try

{

H.DeleteMin(E);

}

catch (OutOfBounds)

{

break;

}

}

returnbestc;

}

2. 上机题目:运动员最佳配对问题

羽毛球队有男女运动员各n人。给定两个n×n矩阵P和Q;P[i][j]表示男运动员i和女运动员j配对组成混合双打的男运动员竞赛优势,Q[i][j]表示女运动员i和男运动员j配对组成混合双打的女运动员竞赛优势(注意:由于多种原因,P[i][j]未必等于Q[j][i]),男运动员i和女运动员j配对组成混合双打的男女运动员双方总体竞赛优势为P[i][j]* Q[j][i]。用分支限界法设计一个算法,计算男女运动员的最佳配对,即各组男女运动员双方总体竞赛优势的总和达到最大。

5.5 思考题

(1)批处理作业调度问题的分支限界算法如何求解?

(2)0-1背包问题的分支限界算法如何求解?

你可能感兴趣的:(计算机算法设计与分析实验指导书)