贪心算法设计实验报告.docx
《贪心算法设计实验报告.docx》由会员分享,可在线阅读,更多相关《贪心算法设计实验报告.docx(19页珍藏版)》请在冰豆网上搜索。
![贪心算法设计实验报告.docx](https://file1.bdocx.com/fileroot1/2023-5/21/9ef26d84-2754-4c75-8cf9-18820ee6df98/9ef26d84-2754-4c75-8cf9-18820ee6df981.gif)
贪心算法设计实验报告
《算法设计技巧与分析》课程实验报告
实验名称
贪心算法的运用
实验序号
姓名
系院专业
班级
学号
实验日期
指导教师
成绩
一、实验目的
1.掌握贪心算法的基本概念和两个基本要素
2.熟练掌握贪心算法解决问题的基本步骤
3.学会利用贪心算法解决实际问题
二、实验内容与要求
1,实现贪心算法的经典运用-------Kruskal算法(最小生成树)
2,实现贪心算法的经典运用-------Prim算法(最小生成树)
三、实验设备
地点:
科技楼网络实验室602
硬件环境:
IntelPentiumProcessor1.8G,512M内存 ,windows操作系统
软件环境:
VC++6.0
五、实验步骤
1.用Kruskal算法实现最小生成树
算法描述:
假设WN=(V,{E})是一个含有n个顶点的连通网,则按照克鲁斯卡尔算法构造最小生成树的过程为:
先构造一个只含n个顶点,而边集为空的子图,若将该子图中各个顶点看成是各棵树上的根结点,则它是一个含有n棵树的一个森林。
之后,从网的边集E中选取一条权值最小的边,若该条边的两个顶点分属不同的树,则将其加入子图,也就是说,将这两个顶点分别所在的两棵树合成一棵树;反之,若该条边的两个顶点已落在同一棵树上,则不可取,而应该取下一条权值最小的边再试之。
依次类推,直至森林中只有一棵树,也即子图中含有n-1条边为止。
下面给出c语言代码实现及说明
本程序对树的存储主要是以边为存储对象,即边的结构体里面有这样几个参数:
1,边的权值。
2,边的一个顶点。
3,边的另一个顶点。
4,边是否属于生成树的一条边(即最小生成树边标志)。
由该程序的存储结构决定了该算法比较适用于边稀疏的情形,至于边稠密的情况会在下面的Prim算法中给出。
在Kruskal算法中有两个比较重要的部分1,对边按权重排序。
2,对一条边加入子树后是否会产生回路的判断即判断边的两个节点是否在同一个树中(集合里)
对于问题1:
可以有各种排序算法,读者可以自行选择自己喜欢的排序算法来替换代码中的排序算法。
(本处使用选择排序算法(效率较低),读者可以自己修改为快速排序或者是对排序)
下面主要讲解解决问题2,解决这个问题一般借用不相交集的思想,即在本程序中每次以同集合中所有点的编号最小的数来标识本集合。
(对于根节点即标号最小的节点则标记为0)例如对于上图实例,下面给出详细的不相交集的维护过程(给出前几步的详细说明)
a,初始状态(ABCDEFG分别在数组的第1,2,3,4,5,6,7)0号位空置(均为0)
0
A
B
C
D
E
F
G
0
0
0
0
0
0
0
0
b,选择第一条边A---D(将D的标记改为1)(因为AD最短)
0
A
B
C
D
E
F
G
0
0
0
0
1
0
0
0
对表进行维护(维护后仍同上表,因为还没有两个集合合并)
0
A
B
C
D
E
F
G
0
0
0
0
1
0
0
0
C,选择第二条边C----E(修改上表)
0
A
B
C
D
E
F
G
0
0
0
0
1
3
0
0
对上表进行维护(任同上表,因为还没有两个集合合并)
0
A
B
C
D
E
F
G
0
0
0
0
1
3
0
0
D,选择第三条边(D-----F)(根据条件DF两点不再同一集合,改边可选)
然后就合并DF两点所在的集合D的前去是1,即A标记为0,E的标记也为0,合并因为6>1所以表修改如下
0
A
B
C
D
E
F
G
0
0
0
0
1
3
1
0
以后几步均如上判断两点是否在一个集合从而判断改边是否可取,并维护上表
下面附上源代码
/**************************************************************************************************
Kruskal算法的实现
09网一殷赛0910322113
输入:
图G(用结构体数组来存储每条边,包含每条边的节点)
输出:
图G的最小生成树树
***************************************************************************************************/
#include
#include
typedefstructEdge
{
chardot_1;
chardot_2;
intweight;
intleap;
}Edge;
Edge*selectionsort(Edge*array,intn)//选择排序(对边按权重由高到低排序)
{
inti,j,min,temp;
for(i=0;i{
min=i;
for(j=i+1;jif(array[min].weight>array[j].weight)
min=j;
if(min!
=i)
{
temp=array[i].weight;
array[i].weight=array[min].weight;
array[min].weight=temp;
temp=array[i].dot_1;
array[i].dot_1=array[min].dot_1;
array[min].dot_1=temp;
temp=array[i].dot_2;
array[i].dot_2=array[min].dot_2;
array[min].dot_2=temp;
}
}
returnarray;
}
Edge*Kruskal(Edge*Graph,intnum_e,int**V,intnum_v)//克鲁斯卡尔算法实现
{
intm,n,test;
inti,j,t,k;
for(i=0;i{
for(j=1;j{
if(Graph[i].dot_1==V[0][j])
m=j;
if(Graph[i].dot_2==V[0][j])
n=j;
}
if(V[1][m]!
=V[1][n]&&m!
=V[1][n]&&n!
=V[1][m])//如果边的两个顶点不再一个集合则边是生成树的边(注意首节点的标记和集合里非首节点的标记不同)
{
Graph[i].leap=1;
if(V[1][n]==0)
{
if(nV[1][V[1][m]]=n;
else
V[1][n]=V[1][m];
}
else
{
if(V[1][m]==0)
{
if(mV[1][V[1][n]]=m;
else
V[1][m]=V[1][n];
}
else
{
if(V[1][m]>V[1][n])
V[1][V[1][m]]=V[1][n];
else
V[1][V[1][n]]=V[1][m];
}
}
//维护不相交集
k=1;//对每个节点都检查是否为标记合格节点
while(k{
if(V[1][k]!
=0)//只要标记不为0都进行整理,只不过有些节点的标记整理前后是一样的(即标记符合标准的节点)
{
t=V[1][k];
while(V[1][t]!
=0)
{
t=V[1][t];
}
V[1][k]=t;
}
k++;
}
}
if(V[1][m]==0&&V[1][n]==0)//如果边的两个顶点是两个集合的首节点则可以合并
{
Graph[i].leap=1;
if(m>n)
V[1][m]=n;
else
V[1][n]=m;
//维护不相交集
k=1;
while(k{
if(V[1][k]!
=0)
{
t=V[1][k];
while(V[1][t]!
=0)
{
t=V[1][t];
}
V[1][k]=t;
}
k++;
}
}
/*printf("不相交集的情况:
\n");
for(test=1;testprintf("%-4c",V[0][test]);
printf("\n");
for(test=1;testprintf("%-4d",V[1][test]);
printf("\n");*/
}
returnGraph;
}
voidmain()
{
inti,j,num_v,num_e,cost=0;
Edge*Graph=NULL;
int**V=NULL;
printf("请输入土中有多少个顶点!
\n");
scanf("%d",&num_v);
V=(int**)malloc(sizeof(int*)*2);
for(i=0;i<2;i++)
V[i]=(int*)malloc(sizeof(int)*(num_v+1));
for(i=0;i<2;i++)
for(j=0;jV[i][j]=0;
for(i=1;i{
printf("请输入第%d个顶点:
",i);
scanf("%c",&V[0][i]);
}
printf("请输入图中有多少条边!
\n");
scanf("%d",&num_e);
Graph=(Edge*)malloc(sizeof(Edge)*num_e);
for(i=0;i{
printf("请输入第%d条边的权值和两个顶点!
\n",i+1);
scanf("%d%c%c",&Graph[i].weight,&Graph[i].dot_1,&Graph[i].dot_2);
Graph[i].leap=0;
}
Graph=selectionsort(Graph,num_e);
//以上部分是存储图
//--------------------------------------------------------------------------------------------------
Graph=Kruskal(Graph,num_e,V,num_v);
printf("构成最小生成树的边和顶点分别是:
\n");
printf("顶点1------------顶点2-------------边\n");
for(i=0;i{
if(Graph[i].leap==1)
{
printf("%c------------%c-------------%d\n",Graph[i].dot_1,Graph[i].dot_2,Graph[i].weight);
cost=cost+Graph[i].weight;
}
}
printf("最小生成树的权值是:
%d\n",cost);
}
2.用Prim算法实现最小生成树
算法描述:
假设V是图中顶点的集合,E是图中边的集合,TE为最小生成树中的边的集合,则prim算法通过以下步骤可以得到最小生成树:
1:
初始化:
U={u0},TE={f}。
此步骤设立一个只有结点u0的结点集U和一个空的边集TE作为最小生成树的初始形态,在随后的算法执行中,这个形态会不断的发生变化,直到得到最小生成树为止。
2:
在所有u∈U,v∈V-U的边(u,v)∈E中,找一条权最小的边(u0,v0),将此边加进集合TE中,并将此边的非U中顶点加入U中。
此步骤的功能是在边集E中找一条边,要求这条边满足以下条件:
首先边的两个顶点要分别在顶点集合U和V-U中,其次边的权要最小。
找到这条边以后,把这条边放到边集TE中,并把这条边上不在U中的那个顶点加入到U中。
这一步骤在算法中应执行多次,每执行一次,集合TE和U都将发生变化,分别增加一条边和一个顶点,因此,TE和U是两个动态的集合,这一点在理解算法时要密切注意。
3:
如果U=V,则算法结束;否则重复步骤2。
可以把本步骤看成循环终止条件。
我们可以算出当U=V时,步骤2共执行了n-1次(设n为图中顶点的数目),TE中也增加了n-1条边,这n-1条边就是需要求出的最小生成树的边
(图任如上图)
下面给出具体c语言代码和详解
该代码的存储结构是邻接矩阵上三角用来存储最小生成树的边,树边用-1标记,下三角用来存储原图,方便输出最小生成树上图的最后邻接矩阵如下:
(具体实现和解释见代码)(INF代表无穷,即不相邻)
0
-1
INF
-1
INF
INF
INF
7
0
INF
INF
-1
INF
INF
INF
8
0
INF
-1
INF
INF
5
9
INF
0
INF
-1
INF
INF
7
5
15
0
INF
-1
INF
INF
INF
6
8
0
INF
INF
INF
INF
INF
9
11
0
/*********************************************************************************
09网一殷赛0910322113
Prim算法的实现
输入:
图G
输出:
图G的最小生成树
**********************************************************************************/
#include
#include
#defineINF32767
/*************************************************************************************
Prim算法最重要的部分在于从可以挑选的边中间挑选出最小值
并且对加入后会产生回路的边标记为不可选(此代码中将该边权值标记为INF即无穷大)
每选中一条边就会加入一个点(就必须对与该点连接的边进行维护,即多产生回路边标记为无穷)
找最小边则是从上三角中所有可选的行和列中选择
选中的树边则标记为-1(其权值从下三角读出)
**************************************************************************************/
voidPrim(int**Graph,intnum_v)//转化为对上三角的维护
{
inti,j,leap=0,temp;
intm,n,min;
int*a=(int*)malloc(sizeof(int)*num_v);
for(i=0;ia[i]=0;
a[0]=1;
while(leap!
=num_v-1)
{
min=INF;
for(i=0;i{
if(a[i]==1)
{
for(j=i+1;j{
if(min>Graph[i][j]&&Graph[i][j]>0)
{
min=Graph[i][j];
m=j;
n=i;
temp=0;
}
}
for(j=0;j
{
if(min>Graph[j][i]&&Graph[j][i]>0)
{
min=Graph[j][i];
m=j;
n=i;
temp=1;
}
}
}
}
if(temp==0)//为了区别出是在行还是在列中搜索到的元素
Graph[n][m]=-1;
else
Graph[m][n]=-1;
for(i=0;i{
if(a[i]==1&&Graph[i][m]>0)
{
Graph[i][m]=INF;
}
}
for(i=m+1;i{
if(a[i]==1&&Graph[m][i]>0)
{
Graph[m][i]=INF;
}
}
a[m]=1;
/*for(i=0;iprintf("%-4d",a[i]);
printf("\n");*/
/*for(i=0;i{
for(j=0;jprintf("%-4d\t",Graph[i][j]);
printf("\n");
}*/
leap++;
}
}
voidmain()
{
inti,j;
intnum_v,num_e;
int**Graph=NULL;
char*V=NULL;
charch_1,ch_2;
intweight;
intm,n;
printf("请输入图的顶点数:
");
scanf("%d",&num_v);
V=(char*)malloc(sizeof(char)*num_v);
Graph=(int**)malloc(sizeof(int*)*num_v);//动态生成二维数组用来存储图(邻接矩阵)
for(i=0;iGraph[i]=(int*)malloc(sizeof(int)*num_v);
for(i=0;ifor(j=0;jGraph[i][j]=INF;
for(i=0;iGraph[i][i]=0;
for(i=0;i{
printf("请输入第%d个顶点:
",i+1);
scanf("%c",&V[i]);
}
printf("请输入图的边数:
");
scanf("%d",&num_e);
for(i=0;i{
printf("请输入第%d条边的顶点和权值:
",i+1);
scanf("%c%c%d",&ch_1,&ch_2,&weight);
for(j=0;j{
if(V[j]==ch_1)
m=j;
if(V[j]==ch_2)
n=j;
}
Graph[m][n]=weight;
Graph[n][m]=weight;
}
//以上是对图用邻接矩阵存储
//------------------------------------------------------------------------------------
Prim(Graph,num_v);
printf("最小生成树如下:
\n");
printf("顶点----------------顶点-----------------权值\n");
weight=0;
for(i=0;ifor(j=i+1;j{
if(Graph[i][j]==-1)
{
printf("%-2c----------------%-2c-----------------%-2d\n",V[i],V[j],Graph[j][i]);
weight=weight+Graph[j][i];
}
}
printf("最小生成树的权重是:
%d\n",weight);
}
六、实验结果与分析
Kruskal算法适用于边稀疏的情形,而Prim算法适用于边稠密的情形