算法分析与设计课程报告.docx
《算法分析与设计课程报告.docx》由会员分享,可在线阅读,更多相关《算法分析与设计课程报告.docx(32页珍藏版)》请在冰豆网上搜索。
算法分析与设计课程报告
重庆邮电大学研究生堂下考试答卷
2015-2016学年第2学期
考试科目算法分析与设计
姓名
年级
学号
专业计算机科学与技术
2016年6月2日
算法分析与设计课程报告
摘要:
本文选取了两个不同的问题,查找特定元素和单源节点间最短路径问题,采用不同的的求解算法来对问题进行分析求解,从理论分析上给出了不同算法在解决该问题时的效率和开销,并通过程序仿真实验,对理论分析结果进行验证。
在解决查找问题时,采用了线性表直接查找,二叉排序树查找,平衡的二叉排序树查找三种方法。
在解决单源节点间最短路径问题时,采用了蛮力搜索和迪杰斯特拉算法两种方法。
通过对数据实例的仿真结果的分析与对比,对不同算法在解决同一个问题的实际性能与理论分析进行了比较验证。
关键词:
查找;单源节点最短路径;理论分析;实验仿真
1问题和算法的定义
该部分对报告所分析的两个问题,查找问题和单元节点间最短路径问题进行了介绍和定义,并对解决对应问题给出了所选算法的的介绍。
1.1查找问题
查找问题就是在给定的集合(或者是多重集,它允许多个元素具有相同的值)中找寻一个给定的值,我们称之为查找键。
有许多查找算法可供选择,其中既包括直截了当的顺序搜索,也包括效率极高但应用受限的折半查找,还有那些将原集合用另一种形式表示以方便查找的算法。
最后一类算法对于现实应用具有特别重要的价值,因为它们对于大型数据库的信息存取来说是不可或缺的。
对于查找来说,没有一种算法在任何情况下都是最优的。
有些算法速度比其他算法快,但需要较多的存储空间;有些算法速度非常快,但仅适用于有序的数组,诸如此类。
和排序算法不同,查找算法没有稳定性问题,但会发生其他问题。
具体来说,如果应用里的数据相对于查找次数频繁变化,查找问题就必须结合另外两种操作一起考虑:
在数据集合中添加和删除元素的操作。
在这种情况下,必须自习选择数据结构和算法,以便在各种操作的需求之间达到一个平衡。
而且,对于用于高效查找的特大型数据集合来说,如何组织其结构是一项不同寻常的挑战,而这对实际应用具有非常重要的意义。
报告中,针对查找问题采用了三种方法:
线性表直接查找,二叉排序树查找,平衡二叉排序树查找三种方法。
1)其中线性表(LinearList,LL)直接查找,通过将数据集合存储在数组中,每次从数组首地址开始一直搜索到数组末尾,直到查找到待查元素和到达数组末尾为止。
并针对该方法给出了一个优化的算法,对数组中的元素增加一个频度域,将查找频度最高的元素放在数组最前面,依次按频度对数组中元素进行排序。
2)二叉排序树(BinarySortTree,BST)查找,BST又称二叉搜索树,其定义为:
二叉排序树或者是一颗空树,或者是具有如下性质的二叉树:
①若左子树不空,则左子树上所有节点的值均小于它的根节点的值;②若右子树不空,则右子树上所有节点的值均大于它的根节点的值;③左、右子树也分别是二叉排序树;④没有键值相等的节点。
从BST的定义中可知,在BST中查找一个元素的过程,即从根节点开始,每次根据待查元素与根节点的比较,决定查找成功或者依左或右子树查找,直到找到待查元素或者都到叶子节点为止。
3)平衡二叉查找树(Adelson-VelskiiandLandis,AVL树),一棵AVL树是一棵二叉查找树,其中每个节点的平衡因子定义为该节点左子树和右子树的高度差,这个平衡因子要么是0,要么为+1或者-1(一棵空树的高度定义为-1)。
因此AVL树是一种特殊的二叉查找树,其左右分支深度比较平衡,这样就缩短了树的高度,从而降低了查找的次数。
在AVL树种查找元素的过程类似在二叉排序树中查找元素。
1.2单源节点间最短路径问题
在报告中,考虑单源点最短路径问题,对于一个加权连通图的一个称为起点的给定定点,求出它到所有其他定点之间的一系列最短路径。
需要说明的是,这里所关心的不是从一个起点出发访问所有其他定点的单条最短路径,这种问题的难度更大。
单起点最短路径问题要求的是一组路径,每条路径都从起点出发通向图中的一个不同顶点,其中某些路径可能具有公共边。
在求带权连通图中最短路径问题有两个著名的算法,Dijkstra算法和Floyd算法,其中Dijkstra算法是解决单源节点间最短路径问题,报告中采用的就是这种算法,而Floyd算法是求解图中每对节点间的最短路径问题。
报告中考虑的两种算法如下:
1)基于全排列的蛮力搜索算法,对连通图中除首节点和尾节点外,其他节点进行全排列,对其中每一个排列构成的路径首先进行合法性检测,然后通过笔记记录,找到最短路径。
2)Dijkstra算法,该算法的基本思想是按照路径长度的增长来通过迭代,依次求得v0节点到其他节点的路径长度,当经过n次迭代后,即可求出v0到其他n-1个节点之间的最短路径长度。
首先将节点分为两个集合S1和S2,其中S1初始只包含v0节点,S2初始为其他节点,然后算法按照以下规则来迭代:
①从集合S2中选择一个节点vi使其到v0节点之间的距离最短,并把vi加入到S1集合中;
②用vi作为中间节点来过渡,判断是否能通过vi节点作为中间节点,使得从v0到S2集合中其他的节点路径更短,如果通过vi作为中间节点的过渡得到的距离更短,即更新v0到S2中节点之间的距离权值;
③重复①~②的迭代过程,知道S2集合中不再有节点为止。
2问题的理论分析
本节通过理论推导分析,对报告中所提到的两个问题进行效率和开销上的分析,得出其理论上的时间复杂度。
2.1查找问题
设集合中的数据个数为n,则在采用线性表直接存储时,每次查找一个元素,在最好的情况下,时间复杂度是o
(1),最坏的情况下时间复杂度是o(n),因此平均查找长度是
(1)
在对线性表直接查找进行优化的算法中,增加了频度域,数组中数据按照数据查找的频度从高到低排列,在这种算法上,最好的情况下,时间复杂度是o
(1),最坏的情况下时间复杂度是o(n)。
数据的平均查找长度依赖于所查找的序列。
但可以确定的是,其查找长度低于优化前的查找长度
(2)
在分析二叉排序树的理论查找性能时,我们首先要清楚二叉排序树的形态,因为当二叉排序树的形态不同,其树的深度就会不同,因而查找的性能也会不同。
在极端的情况,二叉排序树退化为一个单分支的树,则其查找性能跟线性表直接查找相同。
在这里我们可以分析,对一棵随机生成的二叉排序树,其平均查找性能优于线性表直接查找。
为了确定二叉排序树查找性能的上界和下界,我们首先分析一棵完全平衡的二叉排序树,即既是一棵平衡二叉树,又是一棵完全二叉树。
假设每一个节点的查找频率相同,则完全平衡二叉查找树的平均查找长度为
(3)
(4)
因此随机生成的一棵二叉查找树的平均长度为
(5)
上述三种方法从时间复杂度上来说,查找一个数据的时间复杂度都是o(n),空间复杂度均需要o(n)大小的辅助存储空间。
2.2单源节点间最短路径问题
首先我们分析基于全排列的蛮力搜索算法,通过对所有的节点进行全排列,然后判断每一种路径组合是否合法,从合法的路径中选择一条最短的路径。
其时间复杂度为o(n!
)。
在采用迪杰斯特拉算法时候,其时间复杂度为
,采用临界矩阵存储时,其空间复杂度为
。
3实验仿真方案的设计
本部分,通过引入实例来构建仿真模型,对解决同一个问题,采用不同算法的效率进行仿真测试。
3.1查找问题
选取一个数据集,其中包含有15个待查数据的集合。
该集合中的元素通过随机函数在1到100的范围内生成。
然后通过大量生成1到100的随机数,检测生成的数据是否在所选数据集合中,来测试每一种算法在执行大量样本数据时候的性能。
具体实验对比的方案见下表
表1查找问题实验记录表
样本集合:
测试数据集合:
算法
运行
时间
用例
n=1000
n=10000
n=50000
n=100000
直接线性表查找
二叉排序树查找
AVL树查找
3.2单源节点间最短路径问题
首先定义一个图的节点数和带权边数为实验的变量,针对具有相同节点数的稀疏图和稠密图,分别采用基于全排列的蛮力搜索算法和Dijkstra算法,测试这两种算法在不同情况下搜索最短路径时候的时间开销。
测试用例记录表如下
表2单源节点间最短路径记录表
源点
终点
,节点数V,边数N
算法
运行
时间
用例
V=10,
N=10
V=10,
N=50,
V=20,
N=30
V=20,
N=60
基于全排列的蛮力搜索算法
Dijkstra算法
4实验仿真结果分析
实验在VC6.0平台上,采用的C语言编写程序,将实验中测试的数据存储在record.txt文本中,再将文本数据导入到matlab中进行可视化比较。
4.1查找问题
图4.1.1和图4.1.2是测试不同查找算法的运行所耗费的时间的实时输出截图。
其中图4.1.1是样本数量较少情况下的运行输出图,图3.1.2是样本数量较大时候的运行输出图。
每一次输出会连续输出四组数据,每一组数据包含两个数值,第一个数值表示的是查找所耗费总的时间,第二个数值是查找中总的查找长度。
图4.1.1
图4.1.2
并将运行中的数据记录在文件中,图4.1.3和图4.1.4均是在测试样本数量不同时的各算法查找性能的评价值。
每一行为一个样本的记录,包含九个数字,第一个数值是测试样本的数量,后面的八个数字,两个一组,共四组,第一个数字是查找所耗费的时间,第二个数字是查找总的长度,四组数字分别对应,直接连表查找,带频度的连表查找,二叉查找树和AVL树查找。
在图4.1.4中,有的部分数字是负数,那是因为查找的长度值超过了C语言中int类型的最大数值表示范围,产生了溢出。
图4.1.3
图4.1.4
在图4.1.5中,是四种算法的查找耗费时间的对比图,通过将上面记录的实验数据导入到Matlab中,生成的对比图。
从图中我们可以直观看到四种算法中,线性表直接查找所耗费的时间最长,带频度的线性变性能次之,相对比,二叉查找树和AVL树的查找性能要好得多,耗费的时间也大大的低于线性表。
随着样本数量的增加,线性表查找的时间耗费整体趋势呈正比例增长,而二叉排序树和二叉查找树的时间变化比较缓慢。
图4.1.5
图4.1.6和图4.1.7分别是对图4.1.5的局部进行放大所成的图。
从这两幅图中,可以直观的看到两种线性表和两种二叉树数据结构算法的性能对比。
图4.1.6
图4.1.7
在下面,我们将呈现各算法总的查找长度的性能对比图。
在图4.1.8中,我们可以看到,相对于直接线性表和带频度的线性表来说,二叉查找树和AVl树的总的查找长度要低得多。
而图中在接近900000样本时,所出现的查找长度变为负数,是因为存储查找长度的int类型变量发生了正向溢出。
图4.1.8
图4.1.9分别为对图4.1.8的局部放大,更清晰的呈现其性能的对比。
从图4.1.8中可以直观的看到,带频度的线性表能够在线性表直接查找的基础上改进查找性能,降低总的查找长度。
从图4.1.9中可以看到,AVL树的查找性能在平均的情况向,其查找所耗费的总的查找长度,大约为二叉查找树的3/4。
图4.1.9
图4.1.10
4.2单源节点间最短路径问题
在单源节点间最短路径问题的实验设计中,采用的样本规模由节点的数量表示,因为在本文中选取的两种算法,基于全排列蛮力求解的算法和迪杰斯特拉算法的时间复杂度均直接与图的节点个数有关。
在图4.2.1中,是一个程序运行的实时输出图,在图中输出的数据中,每一行的三个数据,第一个是图中节点的个数,第二个数据是迪杰斯特拉算法所耗费的时间,第三个数据是基于全排列的蛮力算法的时间开销。
可以看到当节点个数是12的时候,基于全排列的蛮力算法的时间开销已经很大了,达到了3972ms。
由于蛮力算法的时间复杂度太高,因此专门针对迪杰斯特拉算法进行了时间开销的测试,图4.2.2中就是专门测试迪杰斯特拉算法随样本规模的增大时间开销的变化。
图4.2.1
图4.2.2
在对数据实验的记录中,依然采用了将实验中运行得到的数据,记录在了txt文本中,以供后面导入到Matlab中更直观的作图,可视化的对比算法的性能。
图4.2.3是对两种算法的测试数据记录的文本文件,可以看到当图中的节点数量达到15个时,基于全排列的蛮力算法基本失效,其运行时间达到了7044687ms,而此时迪杰斯特拉算法的时间开销还特别小。
图4.2.4则是专门对迪杰斯特拉算法进行的实验测试,可以看出,迪杰斯特拉算法的性能还是比较优秀的,时间开销维持在毫秒级别。
图4.2.3
图4.2.4
下面进行的是将实验中记录下的数据从txt文本中导入到Matlab中所得到的可视化性能曲线图。
在图4.2.5中,显示的是两种算法,时间开销的对比图,可以看到,当样本数量达到了13开始,基于全排列的蛮力算法时间复杂度急剧上升,很快将不具有可用性,而迪杰斯特拉算法的时间复杂度一直都在在0附近。
为了更直观的得到迪杰斯特拉算法的时间复杂度的变化,单一的针对迪杰斯特拉算法进行了实验测试。
逐渐增大样本数据的规模,测试其性能,从图4.2.6中我们可以看出,迪杰斯特拉算法的时间开销整体趋势是呈二次函数的曲线上升,这也符合迪杰斯特拉算法的时间复杂度为
。
图4.2.5
图4.2.6
5总结
在本文中,选取了两个不同的问题,查找问题和单元节点间最短路径问题进行了分析和实验仿真测试。
这两个问题都是在现实中应用很多的问题。
再本文第二节对所选取的问题进行了理论上的分析,分析了不同算法在解决同一个问题时理论上的性能,并且给出了理论上的时间复杂度相关评价项的表达式。
在第三节对实验方案进行了设计,以合理的通过实验仿真对理论部分进行验证,并加深对算法设计和分析思想的理解。
在第四节的仿真结果分析中,可以看到,实验结果与理论分析结果是吻合的,反应出了各算法在实际运行中的时间开销与理论分析的时间复杂度是想契合的。
参考文献
[1]甘玲等.解析C语言程序设计[M].北京:
清华大学出版社,2008.2.
[2]陈杰等编著.MATLAB宝典[M].北京:
电子工业出版社,2011.11.
[3]严蔚敏、吴伟民等.数据结构(C语言版)[M].北京:
清华大学出版社,2009.3
[4](美)赖维丁著,潘彦译.算法设计与分析基础(第2版)[M].北京:
清华大学出版社,2007.1.
附录
源程序:
1查找问题
#include
#include
#include
typedefstructsearchTree
{
intdata;
structsearchTree*lchild;
structsearchTree*rchild;
}SET;
#defineNUM5000
//#defineTEST_NUM100
intarrayList_search();
intarray_search_withfrequency();
intconstruct_searchTree(SET**c,intdata);
intconstruct_AVLTree(inta[],intn,SET**c2);
intfirst_travel(SET*c);
intsearch_in_list(inta[],intn,intnum);
intsearch_in_listwithfrequency(inta[][2],intn,intnum);
intsearch_in_searchTree(SET*c,intdata);
intmain()
{
inta[NUM],b[NUM][2],i=0,j=0,flag=1,d[NUM],temp=0,*test_set,TEST_NUM=100;
intseach_long1=0,seach_long2=0,seach_long3=0,seach_long4=0;
SET*c=NULL,*c2=NULL;
clock_tbegin,duration,time1,time2,time3,time4;
FILE*fp;
while(TEST_NUM<10000000)
{
test_set=(int*)malloc(TEST_NUM*sizeof(int));
seach_long1=0,seach_long2=0,seach_long3=0,seach_long4=0;
for(i=0;i{
flag=1;
while(flag)
{
a[i]=rand()%5000+1;
flag=0;
for(j=0;j
{
if(a[j]==a[i])
{
flag=1;
break;
}
}
}
b[i][0]=a[i];
b[i][1]=1;
d[i]=a[i];
construct_searchTree(&c,a[i]);
}
for(i=0;ifor(j=0;j{
if(d[j]>d[j+1])
{
temp=d[j];
d[j]=d[j+1];
d[j+1]=temp;
}
}
construct_AVLTree(d,NUM,&c2);
for(i=0;i{
test_set[i]=rand()%500+1;
}
begin=clock();
for(i=0;i{
seach_long1=seach_long1+search_in_list(a,NUM,test_set[i]);
}
duration=clock();
time1=duration-begin;
begin=duration;
for(i=0;i{
seach_long2=seach_long2+search_in_listwithfrequency(b,NUM,test_set[i]);
}
duration=clock();
time2=duration-begin;
begin=duration;
for(i=0;i{
seach_long3=seach_long3+search_in_searchTree(c,test_set[i]);
}
duration=clock();
time3=duration-begin;
begin=duration;
for(i=0;i{
seach_long4=seach_long4+search_in_searchTree(c2,test_set[i]);
}
duration=clock();
time4=duration-begin;
if((fp=fopen("record.txt","a"))==NULL)
{
printf("Thefile'record.txt'wasnotopened\n");
return0;
}
else
printf("Thefile'record.txt'wasopened\n");
fprintf(fp,"%d%d%d\t%d%d\t%d%d\t%d%d\n",TEST_NUM,time1,seach_long1,time2,seach_long2,time3,seach_long3,time4,seach_long4);
if(fclose(fp))
printf("Thefile'record.txt'wasnotclosed\n");
printf("%d%d\n",time1,seach_long1);
printf("%d%d\n",time2,seach_long2);
printf("%d%d\n",time3,seach_long3);
printf("%d%d\n",time4,seach_long4);
free(test_set);
TEST_NUM+=5000;
}
}
intsearch_in_list(inta[],intn,intnum)
{
inti=0;
for(i=0;i{
if(a[i]==num)
returni+1;
}
returni;
}
intsearch_in_listwithfrequency(inta[][2],intn,intnum)
{
inti=0,temp1,temp2,k;
for(i=0;i{
if(a[i][0]==num)
{
a[i][1]++;
break;
}
}
if(i{
k=i+1;
while(i>1&&a[i][i]>a[i-1][1])
{
temp1=a[i-1][1];
temp2=a[i-1][0];
a[i-1][1]=a[i][1];
a[i-1][0]=a[i][0];
a[i][1]=temp1;
a[i][0]=temp2;
i--;
}
returnk;
}
returni;
}
intsearch_in_searchTree(SET*c,intdata)
{
inti=1;
SET*p;
p=c;
while(p!
=NULL&&p->data!
=data)
{
if(datadata)
{
p=p->lchild;
}
else
{
p=p->rchild;
}
i++;
}
returni;
}
intconstruct_searchTree(SET**c,intdata)
{
SET*p=NULL,**p2;
if((*c)==NULL)
{
(*c)=(SET*)malloc(sizeof(SET));
(*c)->data=data;
(*c)->lchild=NULL;
(*c)->rchild=NULL;
return1