算法效率与程序优化Word文档格式.docx

上传人:b****7 文档编号:22075857 上传时间:2023-02-02 格式:DOCX 页数:52 大小:36.15KB
下载 相关 举报
算法效率与程序优化Word文档格式.docx_第1页
第1页 / 共52页
算法效率与程序优化Word文档格式.docx_第2页
第2页 / 共52页
算法效率与程序优化Word文档格式.docx_第3页
第3页 / 共52页
算法效率与程序优化Word文档格式.docx_第4页
第4页 / 共52页
算法效率与程序优化Word文档格式.docx_第5页
第5页 / 共52页
点击查看更多>>
下载资源
资源描述

算法效率与程序优化Word文档格式.docx

《算法效率与程序优化Word文档格式.docx》由会员分享,可在线阅读,更多相关《算法效率与程序优化Word文档格式.docx(52页珍藏版)》请在冰豆网上搜索。

算法效率与程序优化Word文档格式.docx

(1)赋值运算

净运行时间0.8ms,与基本运行时间202.3ms相比,可忽略不计,故以后将赋值运算作为基本运行时间的一部分,不予考虑。

(2)加法运算

产生随机数:

x=rand();

y=rand();

循环内加法:

t=x+y;

下面的各种简单运算只是更改运算符即可,不再写出代码。

净运行时间41.45ms,即:

在1s的时限中,共可进行(1000-202.3)/41.45*10^8=1.9*10^9次加法运算,即:

只通过循环、加法和赋值的运算次数不超过1.9*10^9.。

而应用+=运算,与普通加法时间几乎相同,所以+=只是一种方便书写的方法,没有实质效果。

同样的,各种自运算并不能提高效率。

(3)减法运算

净运行时间42.95ms,与加法运算基本相同。

可见,在计算机内部实现中,把减法变成加法的求补码过程是较快的,而按位相加的过程占据了较多的时间,借用化学中的一句术语,可以称为整个运算的“速控步”。

当然,这个“速控步”的运行速度受计算机本身制约,我们无法优化。

在下面的算法设计中,还会遇到整个算法的“速控步”,针对这类情况,我们要对占用时间最多的步骤进行细心优化,减少常数级运算次数。

(4)乘法运算

净运行时间58.25ms,明显比加减法要慢,但不像某些人想象的那样慢,至少速度大于加减法的1/2。

所以在实际编程时,没有必要把两个或三个加法变成乘法,其实不如元素乘常数来得快。

不必“谈乘色变”,实际乘法作为CPU的一种基本运算,速度还是很快的。

以上四种运算速度都很快,比循环所需时间少很多,在普通的算法中,每个最内层循环约含有4-5个加、减、乘运算,故整个算法的运行时间约为循环本身所需时间的2倍。

(5)除法运算

净运行时间1210.2ms,是四种常规运算中最慢的一种,耗时是加法的28倍,是乘法的21.5倍,确实如常人所说“慢几十倍”,一秒的时限内只能运行8.26*10^7次,不足一亿次,远大于循环时间。

所以,在计算时应尽量避免除法,尤其是在对时间要求较高的内层循环,尽量不安排除法,如果整个循环中值不变,可以使用在循环外预处理并用一个变量记录,循环内再调用该变量的方法,可以大大提高程序运行效率。

(6)取模运算

净运行时间1178.15ms,与除法运算速度几乎相当,都非常慢。

所以,取模运算也要尽量减少。

在大数运算而只要求求得MODN的值的题目中,应尽量利用数据类型的最大允许范围,在保证不超过MAXINT的前提下,尽量少执行MOD运算。

例如在循环数组、循环队列的应用中,取模运算必不可少,这时优化运算便十分重要。

可利用计数足够一定次数后再统一MOD,循环完后再MOD,使用中间变量记录MOD结果等方法减少次数。

在高精度计算中,许多人喜欢边运算边整理移位,从而在内层循环中有除、模运算各一次,效率极低。

应该利用int的数据范围,先将计算结果保存至当前位,在各位的运算结束后再统一整理。

以下是用统一整理法编写的高精度乘法函数,规模为10000位。

inta[10000]={0},b[10000]={0},c[10000]={0};

voidmul()

{inti,j,t;

10000;

i++)

10000-i;

c[i+j]+=a[i]*b[j];

9999;

{c[i+1]+=c[i]/10;

c[i]%=10;

}

以上函数运行后,平均用时276.55ms。

以下是边运算边整理的程序。

{c[i+j+1]+=(c[i+j]+a[i]*b[j])/10;

c[i+j]=(c[i+j]+a[i]*b[j])%10;

以上函数运行后,平均用时882.80ms。

统一整理与边整理边移位相比,快了3.2倍,有明显优势。

故尽量减少除法、取模运算的次数,是从常数级别降低时间复杂度的方法。

(7)大小比较

if(x>

y)x=y;

净运行时间39.1ms,与加法运算速度相当。

故比较运算也属于较快的基本运算。

二、位运算的速度

(1)左移、右移

x<

<

1;

x>

>

净运行时间无法测出,证明位运算速度极快。

而使用自乘计算需要64.1ms,自除运算需要164.85ms,所以尽可能使用位运算代替乘除。

(2)逻辑运算

t=x|y;

t=x^y;

t=x&

y;

净运行时间约30ms,比加法运算(约40ms)快较多,是因为全是按二进制位计算。

但加减与位运算关系并不大,所以利用位运算主要是利用左右移位的高速度。

三、数组运算的速度

(1)直接应用数组

for(i=0;

for(k=0;

k<

k++)

t=q[k];

净运行时间39.85ms。

这里计算了内层循环的时间。

若改为

t=q[0];

则净运行时间为1.50ms,很快,与202.3ms的循环时间相比,可以忽略。

故应用数组,速度很快,不必担心数组寻址耗时。

同时我们发现,循环耗时在各种运算中是很大的,仅次于乘除,故我们要尽量减少循环次数,能在一个循环中解决的问题不放在两个循环中,减少循环变量次数。

(2)二维数组

5000;

t=z[i][k];

实际运行时间为80.45ms,若规模扩至10000则10s内无法出解,由于频繁访问虚拟内存。

可以试想,若物理内存足够大,则运行时间约为320ms,仅为202.3ms的基准运行时间的3/2,差距似乎并不是很大;

由此推得其净运行时间约为120ms。

但相较加、减等简单操作,速度仍为3倍,尤其与几乎不需时间的一维数组相比差距巨大。

尤其是在计算中,二维数组元素经常调用,时间效率不可忽视。

所以,对于已知数目不多的同样大小的数组,可用几个变量名不同的一维数组表示,如x、y方向,两种不同参数,而不要滥用二维数组。

在滚动数组中,可用两个数组交替计算的方式,用二维数组同样较慢。

四、实数运算的速度

测试方法与“基本运算”类似。

(次数:

100000000,单位:

ms)

运算符

=

+

-

*

/

%

longint

43.05

31.3

-74.75

69.55

299.65

360.5

int64

41.45

14.95

7.9

566.4

1243.45

1858.85

double

46.15

10.25

12.6

33.65

1753.55

--

由上表可见,涉及乘除、取模时int64很慢,要慎用;

int显然最快,但对大数据要小心越界。

若一组变量中既有超出int的,又有不超过int的,则要分类处理,不要直接都定义成int64,尤其在乘除模较多的高精度过程中。

以上讨论了主要基本运算的速度问题。

概括起来说,除、模最慢,二维数组较慢,加减乘、逻辑位运算、比较大小较快,左右移位、一维数组、赋值几乎不需要时间。

而循环for或while语句十分特殊,它的运算速度大于判断大小、自加控制变量所用时间之和,无论采用内部if判断退出,还是在入口处判断,都回用去约200ms的时间。

所以尽量减少循环次数,是优化算法的关键。

对于双层或多层的循环,应把循环次数少的放在最外层,最大的循环位居最内部,以减少内层循环的执行次数。

第二章各种算法的速度

一、排序算法的速度

1.冒泡排序

20000;

a[i]=rand();

s=clock();

n;

i;

if(a[i]>

a[j])

{t=a[i];

a[i]=a[j];

a[j]=t;

运行时间:

1407ms

2.选择排序

s=clock();

{max=0;

if(a[j]>

a[max])

max=j;

b[i]=a[max];

a[max]=-1000000;

t=clock();

1220ms

3.插入排序

for(j=i-1;

j>

=0;

j--)

t)

break;

for(l=i;

l>

j+1;

l--)

a[l]=a[l-1];

a[j+1]=t;

t=clock();

984ms

以上三种都是O(n^2)的排序,其中插入排序最快,且可以用指针加以优化。

从编程复杂度上,冒泡排序最简单。

从算法的稳定性上,插入排序是稳定的,即排序后关键字相同的记录顺序不改变,特别适用于表达式处理等问题。

一般的选择排序是不稳定的,但这里给出的程序由于使用了人类最原始的方法,即依次选择最大的并排除,故是稳定的。

冒泡排序是不稳定的,涉及必须保持数据原顺序的题目时不能选择冒泡排序,而必须选择稳定的排序方式。

以下试验所采用的环境是:

CPUIntelCore1.73GHz*2,内存512M,操作系统Windows7UltimateBeta,程序语言C。

编译环境Dev-c++,以下称为2号机。

由于CPU速度较慢,且操作系统占用资源较多,程序运行速度明显减慢,第一章的“基本运行测试”需要时间约为前者的2倍,即为406ms。

故第一章的程序运行时间此处应乘2。

4.快速排序的标准版

#defineMAX10000000

inta[MAX];

intp(intl,intr)

{intx=a[l],i=l-1,j=r+1,t;

while

(1)

{do{--j;

}while(a[j]<

x);

do{++i;

}while(a[i]>

if(i<

j)

a[i]=a[j];

a[j]=t;

elsereturnj;

voidq(intl,intr)

{intn;

if(l<

r)

{n=p(l,r);

q(l,n);

q(n+1,r);

2948ms。

注意:

不要以为三种平方级排序方法的速度与快速排序可比拟,因为平方级的数据范围是10000,而快速排序的范围是10000000。

对于10000的数据,快速排序只需3.1ms。

另外,快速排序不是稳定的排序,需要保持原顺序的不能用此法。

voidp(intl,intr)

r){

elsebreak;

p(l,j);

p(j+1,r);

若程序改为以上形式,则运行时间为2917ms,稍快了一些,是因为减少了函数调用次数。

对于函数调用,我们进行这样的测试。

test();

t=clock();

inttest()

n++;

净运行时间200ms

净运行时间84ms

净运行时间10ms

由此可见,调用函数本身并不浪费时间,仅相当于循环本身时间400ms的1/40,相当于加法80ms的1/8,是很快的运算。

但由于在函数内部需要进行现场保护,调用系统堆栈,所以用时大幅增加,定义变量后只是一个自增运算就用去120ms,相当于主程序中加法运算时间的3/2倍。

故函数中的运算比主程序中要慢,尤其是反复调用函数,会增加不必要的时间开销。

所以,一些简单的功能尽量在一个函数或主程序内完成,不要使用过多的函数;

涉及全局的变量不要在函数调用时由接口给出,再返回值,尽量使用全局变量。

这些方法可能使程序的可读性降低,不利于调试,但有利于提高时效,正如汇编语言程序比高级语言快一样。

5.优化的快速排序

(1)用插入排序优化

由于递归调用浪费大量时间,本算法的思想是,当首尾间距小于min时,改用效率较高的插入排序,减少反复递归。

这个想法是好的,但运行效果并不如人意。

当min=4时,程序运行时间降为2901ms,优化幅度不大,且增加了编程复杂度,故不宜采用。

其原因是递归调用、插入排序内部循环所用时间过长。

(2)用小数据判断优化

if(l+1<

while

(1){}//与普通快速排序相同

elseif(l+1==r&

&

a[l]<

a[r])

仅就区间长度为2的情况进行判断优化,减少一次递归调用,就能使运行时间缩短为2699ms,降低了200ms以上。

而区间长度不小于3的直接判断有困难。

快速排序运行时间(单位:

数据规模

10000

100000

1000000

10000000

原始快速排序

1.50

25.20

258.55

2716.45

优化快速排序

2.30

23.40

245.40

2564.80

6.归并排序

归并排序是一种稳定的排序方法,且时间效率与快速排序相同,都是O(nlogn)。

但归并排序比快速排序的常数因子大,故快速排序还是最快的排序方法。

归并排序则适用于有特殊要求的题目,如不满足交换律的表达式处理。

inta[MAX],b[MAX];

voidcombine(intfrom,intto)

{inti,t,mid=(from+to)/2,f,r;

if(from==to||from+1==to)

return;

if(from+2==to)

{if(a[from]<

a[from+1])

{t=a[from];

a[from]=a[from+1];

a[from+1]=t;

combine(from,mid);

combine(mid,to);

f=from;

r=mid;

i=from;

while(f!

=mid||r!

=to)

if(f!

=mid&

a[f]>

b[i++]=a[f++];

elseb[i++]=a[r++];

for(i=from;

to;

a[i]=b[i];

调用:

combine(0,MAX);

归并排序算法还可用于统计逆序对数。

所谓逆序对,即为在一个数组a中,满足i<

j且a[i]>

a[j](或a[i]<

a[j],依排序方向而定)的点(i,j)的个数。

voidsort(intfrom,intto)

tot++;

sort(from,mid);

sort(mid,to);

else

{b[i++]=a[r++];

=mid)

tot+=(mid-from);

主函数:

sort(0,n);

%d"

n*(n-1)/2-tot);

7.多关键字排序

多关键字排序,经常出现于要求按某要素排序姓名,该要素相同的按字典序排列的题目。

这样,此要素是第一关键字,姓名是第二关键字。

qist(0,n-1);

j=0;

for(i=1;

if(ist[lo[i]]!

=ist[lo[i-1]])

{qnameist(j,i-1);

j=i;

qnameist(j,n-1);

此算法的意思是,先按第一关键字快速排序,在从左至右扫描,找到每段相同的元素,再按第二关键字快速排序。

此算法复杂度仍为O(nlogn),且比两次快速排序要快,因为第二次排序已用O(n)的时间将其分为若干小块,排序效率大大提高。

8.字符串排序

voidqname(intl,intr)

{inti=l-1,j=r+1,t;

}while(strcmp(name[lo[j]],name[lo[i]])>

0);

}while(strcmp(name[lo[j]],name[lo[i]])<

{t=lo[i];

lo[i]=lo[j];

lo[j]=t;

qname(l,j);

qname(j+1,r);

上述排序方法,实质上是将普通快速排序中的数大小比较换成字符串大小比较。

为了避免字符串交换浪费时间,采用了类似指针的定位数组,只需交换定位数组的元素即可。

当然,用指针直接实现效率更高。

关于字符串比较函数strcmp的时间效率,我们有如下试验:

100;

{a[i]=rand();

b[i]=rand();

if(strcmp(a,b)==0)

break;

1046ms,其中净运行时间约640ms。

这是对长度100的字符串进行10^8次比较的时间。

故strcmp的效率即为约8次整数比较所需时间,比自己编写的函数快。

intstr(chara[],charb[])

{inti=0;

while(a[i]!

=0||b[i]!

=0)

{if(a[i]>

b[i])

return1;

if(a[i]<

return-1;

return0;

1131ms,净运行时间约725ms,相当于9次整数比较时间,比标准库函数稍慢。

显然,标准库函数是经过精心优化的,我们在有库函数可用时尽量用库函数,不仅降低编程复杂度,降低错误率,还能提高时间效率。

9.Hash排序

#defineMOD999997//Asabigprime

#defineMAX10000//Asthemaxlengthofthestring

intELFhash(char*key)

{unsignedlongh=0;

while(*key)

{h=(h<

4)+*key++;

unsignedlongg=h&

0Xf0000000L;

if(g)h^=g>

24;

h&

=~g;

returnh%MOD;

Hash排序,就是先对读入的字符串求Hash值。

第一种方案是,再对Hash值进行快速排序,最后应用二分搜索查找。

此时无需MOD大质数。

第二种方案是,将Hash值MOD后,放入Hash表中,并用开散列等方法处理冲突。

显然,第二种方案更优,但编程复杂度也较高。

综上所述,在选择适当的排序算法时,要注意时间复杂度和编程复杂度,同时保证满足题意。

二、最短路算法的比较

在图论中,最短路算法是常见的。

对于常见的几种算法,到底哪种更优,还是各有千秋?

我们不妨一试。

以下实验在1号机上进行,均为试验20次随机数取平均值的结果。

1.单源最短路径——dijkstra算法

voiddijkstra()

{inti,j,min=0;

dis[i]=d[0][i];

for(i=1;

{min=n;

if(!

use[j]&

d[i][j]<

d[i][min])

min=j;

use[min]=1;

if(dis[min]+d[min][j]<

dis[j])

dis[j]=dis[min]+d[min][j];

随机数产生器:

MAX;

if(i!

=j)

d[i][j]=rand();

当MAX=2000时(即点数为2000个),运行时间:

28.2ms。

当MAX更大时,内存会使用过多,故在1s时限内至多运行规模为2000的单源最短路径35次。

2.单源最短路径——Ford算法

intford()

d[i]=MAXINT;

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

当前位置:首页 > 人文社科 > 视频讲堂

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1