算法效率与程序优化Word文件下载.docx
《算法效率与程序优化Word文件下载.docx》由会员分享,可在线阅读,更多相关《算法效率与程序优化Word文件下载.docx(43页珍藏版)》请在冰豆网上搜索。
便十分重要。
可利用计数足够一定次数后再统一MOD循环完后再MOD使用中间变量记录MOD结果等
方法减少次数。
在高精度计算中,许多人喜欢边运算边整理移位,从而在内层循环中有除、模运算各一次,效率
极低。
应该利用int的数据范围,先将计算结果保存至当前位,在各位的运算结束后再统一整理。
以下是用统一整理法编写的高精度乘法函数,规模为10000位。
inta[10000]={0},b[10000]={0},c[10000]={0};
voidmul()
{inti,j,t;
for(i=0;
iv10000;
i++)
for(j=0;
jv10000-i;
j++)
c[i+j]+=a[i]*b[j];
i<
9999;
{c[i+1]+=c[i]/10;
c[i]%=10;
}
以上函数运行后,平均用时。
以下是边运算边整理的程序。
10000;
j<
10000-i;
{c[i+j+1]+=(c[i+j]+a[i]*b[j])/10;
c[i+j]=(c[i+j]+a[i]*b[j])%10;
统一整理与边整理边移位相比,快了倍,有明显优势。
故尽量减少
除法、取模运算的次数,是从常数级别降低时间复杂度的方法。
(7)大小比较
if(x>
y)x=y;
净运行时间,与加法运算速度相当。
故比较运算也属于较快的基本运算。
二、位运算的速度
(1)左移、右移
x<
<
1;
x>
>
净运行时间无法测岀,证明位运算速度极快。
而使用自乘计算需要,自除运算需要,所以尽可能
使用位运算代替乘除。
(2)逻辑运算
t=x|y;
t=xAy;
t=x&
y;
净运行时间约30ms,比加法运算(约40ms)快较多,是因为全是按二进制位计算。
但加减与位运
算关系并不大,所以利用位运算主要是利用左右移位的高速度。
三、数组运算的速度
(1)直接应用数组
for(k=0;
k<
k++)
t=q[k];
净运行时间。
这里计算了内层循环的时间。
若改为
0;
t=q[0];
则净运行时间为,很快,与的循环时间相比,可以忽略。
故应用数组,速度很快,不必担心数组寻址耗时。
同时我们发现,循环耗时在各种运算中是很大的,仅次于乘除,故我们要尽量减少循环次数,能在一个循环中解决的问题不放在两个循环中,减少循环变量次数。
(2)二维数组
5000;
t=z[i][k];
实际运行时间为,若规模扩至10000则10s内无法岀解,由于频繁访问虚拟内存。
可以试想,若物
理内存足够大,则运行时间约为320ms,仅为的基准运行时间的3/2,差距似乎并不是很大;
由此推得
其净运行时间约为120ms。
但相较加、减等简单操作,速度仍为3倍,尤其与几乎不需时间的一维数组
相比差距巨大。
尤其是在计算中,二维数组元素经常调用,时间效率不可忽视。
所以,对于已知数目不多的同样大小的数组,可用几个变量名不同的一维数组表示,如x、y方向,两种不同参数,而不要
滥用二维数组。
在滚动数组中,可用两个数组交替计算的方式,用二维数组同样较慢。
四、实数运算的速度
测试方法与“基本运算”类似。
运算符
=
+
-
*
/
%
longint
int64
double
--
由上表可见,涉及乘除、取模时int64很慢,要慎用;
int显然最快,但对大数据要小心越界。
若
一组变量中既有超岀int的,又有不超过int的,则要分类处理,不要直接都定义成int64,尤其在乘
除模较多的高精度过程中。
以上讨论了主要基本运算的速度问题。
概括起来说,除、模最慢,二维数组较慢,加减乘、逻辑位运算、比较大小较快,左右移位、一维数组、赋值几乎不需要时间。
而循环for或while语句十分特
殊,它的运算速度大于判断大小、自加控制变量所用时间之和,无论采用内部if判断退岀,还是在入
口处判断,都回用去约200ms的时间。
所以尽量减少循环次数,是优化算法的关键。
对于双层或多层的
循环,应把循环次数少的放在最外层,最大的循环位居最内部,以减少内层循环的执行次数。
第二章各种算法的速度
一、排序算法的速度
1.冒泡排序
20000;
a[i]=rand();
s=clock();
i<
n;
i;
if(a[i]>
a[j])
b=clock();
运行时间:
1407ms
2.选择排序
n;
{max=0;
if(a[j]>
a[max])
max=j;
b[i]=a[max];
a[max]=-1000000;
t=clock();
1220ms
3.插入排序
{t=a[i];
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;
984ms
以上三种都是0(n^2)的排序,其中插入排序最快,且可以用指针加以优化。
从编程复杂度上,冒泡排序最简单。
从算法的稳定性上,插入排序是稳定的,即排序后关键字相同的记录顺序不改变,特别适用于表达式处理等问题。
一般的选择排序是不稳定的,但这里给出的程序由于使用了人类最原始的方法,即依次选择最大的并排除,故是稳定的。
冒泡排序是不稳定的,涉及必须保持数据原顺序的题目时不能选择冒泡排序,而必须选择稳定的排序方式。
以下试验所采用的环境是:
CPUIntelCore*2,内存512M,操作系统Windows7UltimateBeta,
程序语言C。
编译环境Dev-c++,以下称为2号机。
由于CPU速度较慢,且操作系统占用资源较多,程序运行速度明显减慢,第一章的“基本运行测试”需要时间约为前者的2倍,即为406ms。
故第一章的
程序运行时间此处应乘2。
4.快速排序的标准版
#defineMAX
inta[MAX];
intp(intl,intr)
{intx=a[l],i=l-1,j=r+1,t;
while⑴
{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,而快速排序的范围是。
对于10000的数据,快速排序只需。
另外,快速排序不是
稳定的排序,需要保持原顺序的不能用此法。
voidp(intl,intr)
r){
while
(1)
elsebreak;
P(l,j);
P(j+1,r);
若程序改为以上形式,则运行时间为2917ms,稍快了一些,是因为减少了函数调用次数。
对于函
数调用,我们进行这样的测试。
由此可见,调用函数本身并不浪费时间,仅相当于循环本身时间400ms的1/40,相当于加法80ms
的1/8,是很快的运算。
但由于在函数内部需要进行现场保护,调用系统堆栈,所以用时大幅增加,定义变量后只是一个自增运算就用去120ms,相当于主程序中加法运算时间的3/2倍。
故函数中的运算比
主程序中要慢,尤其是反复调用函数,会增加不必要的时间开销。
所以,一些简单的功能尽量在一个函数或主程序内完成,不要使用过多的函数;
涉及全局的变量不要在函数调用时由接口给岀,再返回值,尽量使用全局变量。
这些方法可能使程序的可读性降低,不利于调试,但有利于提高时效,正如汇编语言程序比高级语言快一样。
5.优化的快速排序
(1)用插入排序优化
由于递归调用浪费大量时间,本算法的思想是,当首尾间距小于min时,改用效率较高的插入排
序,减少反复递归。
这个想法是好的,但运行效果并不如人意。
当min=4时,程序运行时间降为
2901ms,优化幅度不大,且增加了编程复杂度,故不宜采用。
其原因是递归调用、插入排序内部循环所用时间过长。
(2)用小数据判断优化
if(l+1<
while
(1){}
并排序
归并排序是一种稳定的排序方法,且时间效率与快速排序相同,都是0(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]>
a[r])
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);
=to)
else
{b[i++]=a[r++];
=mid)tot+=(mid-from);
主函数:
sort(0,n);
printf("
%d"
n*(n-1)/2-tot);
7.多关键字排序
多关键字排序,经常出现于要求按某要素排序姓名,该要素相同的按字典序排列的题目。
这样,此要素是第一关键字,姓名是第二关键字。
qist(O,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(n)的
此算法的意思是,先按第一关键字快速排序,在从左至右扫描,找到每段相同的元素,再按第二关键字快速排序。
此算法复杂度仍为0(nlogn),且比两次快速排序要快,因为第二次排序已用
时间将其分为若干小块,排序效率大大提高。
8.字符串排序
voidqname(intl,intr)
{inti=l-1,j=叶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);
上述排序方法,实质上是将普通快速排序中的数大小比较换成字符串大小比较。
为了避免字符串交换浪费时间,采用了类似指针的定位数组,只需交换定位数组的元素即可。
当然,用指针直接实现效率更高。
100;
{a[i]=rand();
b[i]=rand();
if(strcmp(a,b)==0)
1046ms,其中净运行时间约640ms。
这是对长度100的字符串进行10A8次比较的时
间。
故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次整数比较时间,比标准库函数稍慢。
显然,标准库函数是经过精心优化的,我们在有库函数可用时尽量用库函数,不仅降低编程复杂度,降低错误率,还能提高时间效率。
排序
#defineMOD999997源最短路径——dijkstra算法
voiddijkstra()
{inti,j,min=0;
i++)dis[i]=d[0][i];
{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个),运行时间:
。
当MAX更大时,内存会使用过多,故在1s时
限内至多运行规模为2000的单源最短路径35次。
2.单源最短路径一一Ford算法
intford()
{inti,j;
d[i]=MAXINT;
j<
num;
if(d[f[j]]+w[j]vd[e[j]])d[e[j]]=d[f[j]]+w[j];
if(d[f[j]]+w[j]<
d[e[j]])
①数据构造:
10*n条随机边。
当n=1000时,运行时间:
当n=10000时,运行时间:
6766ms。
②数据构造:
n*n条边的完全图。
当n=1000时,运行时间6469ms。
比Dijkstra和SPFA都慢很多,因为
其算法的理论时间复杂度就达到了O(VE),属于介于0(nA2)与0(nF)的算法。
但我们仍然需要这种算
法,因为当图中存在负权时,必须使用Ford算法。
同时,该算法还能判断图中是否存在负权回路(无
解)。
3.单源最短路径一一SPFA算法
structmising{
intnum;
intpoo;
structmising*p;
};
intwor[500000];
intdis[60001];
intyes[60001]={0};
intbeg=0,end=1;
main()
{
intn,m;
inta,b,c,i;
structmisingq[60001],*qq[60001],*x;
doubles,t;
FILE*fp;
fp=fopen("
"
"
r"
);
fscanf(fp,"
%d%d"
,&
n,&
m);
qq[i]=&
q[i];
m;
%d%d%d"
&
a,&
b,&
c);
a__;
b--;
qq[a]->
p=(structmising*)malloc(sizeof(structmising));
qq[b]->
p=(structmising*)malloc(sizeof(structmising));
qq[a]=qq[a]->
p;
qq[b]=qq[b]->
poo=b;
qq[b]->
poo=a;
num=qq[b]->
num=c;
qq[i]->
p=NULL;
dis[i]=-1;
yes[0]=1;
wor[0]=0;
dis[0]=0;
while(beg!
=end)
x=q[wor[beg]].p;
while(x匸NULL)
f(dis[x->
poo]==-1||(dis[x->
poo]>
x->
num+dis[wor[beg]]&
dis[x>
poo]!
=1)){
dis[x->
poo]=dis[wor[beg]]+x->
wor[end]=x->
poo;
if(yes[x>
poo]==O)
yes[x->
poo]=1;
end++;
x=x->
yes[wor[beg]]=0;
beg++;
dis[n-1]);
t=clock();
随机数生成模块:
fprintf(fp,"
%d%d\n"
n,10*n);
10*n;
{j=rand()%n+1;
k=rand()%n+1;
while(j==k){j=rand()%n+1;
%d%d%d\n"
j,k,rand());
运行时间(不含文件操作):
当n=100000时,运行时间1937ms;
当n=10000时,运行