Bootstrap

20世纪最好的十大算法、算法笔记(2008-11-15 22:16:57、2011-04-21 19:29:05)

Algorithm(算法)一词与9世纪的阿拉伯学者al-Khwarizmi有关,他写的书《al-jabr w’al muqabalah》(代数学)演变成为现在中学的代数教科书。Ad-Khwarizmi强调求解问题的有条理的步骤。
20世纪最好的算法,计算机时代的挑选标准是对科学和工程的研究和实践影响最大。下面就是按年代次序排列的20世纪最好的10个算法。
1. Monte Carlo方法
1946年,在洛斯阿拉莫斯科学实验室工作的John von Neumann,Stan Ulam和Nick Metropolis编制了Metropolis算法,也称为Monte Carlo方法。Metropolis算法旨在通过模仿随机过程,来得到具有难以控制的大量的自由度的数值问题和具有阶乘规模的组合问题的近似解法。数字计算机是确定性问题的计算的强有力工具,但是对于随机性(不确定性)问题如何当时并不知晓,Metropolis算法可以说是最早的用来生成随机数,解决不确定性问题的算法之一。
2. 线性规划的单纯形方法
1947年,兰德公司的Grorge Dantzig创造了线性规划的单纯形方法。就其广泛的应用而言,Dantzig算法一直是最成功的算法之一。线性规划对于那些要想在经济上站住脚,同时又有赖于是否具有在预算和其他约束条件下达到最优化的能力的工业界,有着决定性的影响(当然,工业中的“实际”问题往往是非线性的;使用线性规划有时候是由于估计的预算,从而简化了模型而促成的)。单纯形法是一种能达到最优解的精细的方法。尽管理论上讲其效果是指数衰减的,但在实践中该算法是高度有效的——它本身说明了有关计算的本质的一些有趣的事情。
3. Krylov子空间叠代法
1950年,来自美国国家标准局的数值分析研究所的Magnus Hestenes, Eduard Stiefel和Cornelius Lanczos开创了Krylov子空间叠代法的研制。这些算法处理看似简单的求解形为
Ax=b
的方程的问题。当然隐藏的困难在于A是一个巨型的n*n 矩阵,致使代数解
x=b/A
是不容易计算的(确实,矩阵的“相除”不是一个实际上有用的概念)。叠代法——诸如求解形为
Kx(k+1)=Kx(k)+b-Ax(k)
的方程,其中K 是一个理想地“接近”A 的较为简单的矩阵——导致了Krylov子空间的研究。以俄罗斯数学家Nikolai Krylov命名的Krylov子空间由作用在初始“余量”向量
r(0)=b-Ax(0)
上的矩阵幂张成的。当 A是对称矩阵时,Lanczos找到了一种生成这种子空间的正交基的极好的方法。对于对称正定的方程组,Hestenes 和Stiefel提出了称为共轭梯度法的甚至更妙的方法。过去的50年中,许多研究人员改进并扩展了这些算法。当前的一套方法包括非对称方程组的求解技巧,像字首缩拼词为GMRES和Bi-CGSTAB那样的算法。(GMRES和Bi-CGSTAB分别首次出现于1986和1992 SIAM journal on Scientific and Statistical computing(美国工业与应用数学学会的科学和统计计算杂志)。
4. 矩阵计算的分解方法
1951年,橡树岭国家实验室的A1ston Householder系统阐述了矩阵计算的分解方法。研究证明能把矩阵因子分解为三角、对角、正交和其他特殊形式的矩阵是极其有用的。这种分解方法使软件研究人员能生产出灵活有效的矩阵软件包。这也促进了数值线性代数中反复出现的大问题之一的舍入误差分析问题。 (1961年伦敦国家物理实验室的James Wilkinson基于把矩阵分解为下和上三角矩阵因子的积的LU分解,在美国计算机协会(ACM)的杂志上发表了一篇题为“矩阵逆的直接方法的误差分析”的重要文章。)
5. Fortran最优编译程序
1957年,John Backus在IBM领导一个小组研制Fortran最优编译程序。Fortran的创造可能是计算机编程历史上独一无二的最重要的事件:科学家(和其他人)终于可以无需依靠像地狱那样可怕的机器代码,就可告诉计算机他们想要做什么。虽然现代编译程序的标准并不过分――Fortran I只包含23,500条汇编语言指令――早期的编译程序仍然能完成令人吃惊的复杂计算。就像Backus本人在1998年在IEEE annals of the History of computing 发表的有关Fortran I,II, III的近代历史的文章中回忆道:编译程序“所产生的如此有效的代码,使得其输出令研究它的编程人员都感到吓了一跳。”
6. 矩阵本征值计算的QR算法
1959—61年,伦敦Ferranti Ltd.的J.G. F. Francis找到了一种称为QR算法的计算本征值的稳定的方法。本征值大概是和矩阵相连在—起的最重要的数了,而且计算它们可能是最需要技巧的。把—个方阵变换为一个“几乎是”上三角的矩阵――意即在紧挨着矩阵主对角线下面的一斜列上可能有非零元素――是相对容易的,但要想不产生大量的误差就把这些非零元素消去,就不是平凡的事了。QR 算法正好是能达到这一目的的方法,基于QR 分解, A可以写成正交矩阵Q 和一个三角矩阵R 的乘积,这种方法叠代地把 A=Q(k)R(k) 变成 A(k+1)==Q(k)R(k) 就加速收敛到上三角矩阵而言多少有点不能指望。20世纪60年代中期QR 算法把一度难以对付的本征值问题变成了例行程序的计算。
7. 快速分类法
1962:伦敦Elliott Brothers, Ltd.的Tony Hoare提出了快速(按大小)分类法.把n个事物按数或字母的次序排列起来,在心智上是不会有什么触动的单调平凡的事。智力的挑战在于发明一种快速完成排序的方法。Hoare的算法利用了古老的分割开和控制的递归策略来解决问题:挑一个元素作为“主元”、把其余的元素分成“大的”和“小的”两堆(当和主元比较时)、再在每一堆中重复这一过程。尽管可能要做受到严厉责备的做完全部N(N-1)/2 次的比较(特别是,如果你把主元作为早已按大小分类好的表列的第一个元素的话!),快速分类法运行的平均次数具有O(Nlog(N)) 的有效性,其优美的简洁性使之成为计算复杂性的著名的例子。
8. 快速Fourier变换
1965年,IBM的T. J. Watson研究中心的James Cooley以及普林斯顿大学和AT&T贝尔实验室的John Tukey向公众透露了快速Fourier变换(方法)(FFT)。应用数学中意义最深远的算法,无疑是使信号处理实现突破性进展的FFT。其基本思想要追溯到Gauss(他需要计算小行星的轨道),但是Cooley—Tukey的论文弄清楚了Fourier变换计算起来有多容易。就像快速分类法一样,FFT有赖于用分割开和控制的策略,把表面上令人讨厌的O(N*N) 降到令人欢乐的O(Nlog(N)) 。但是不像快速分类法,其执行(初一看)是非直观的而且不那么直接。其本身就给计算机科学一种推动力去研究计算问题和算法的固有复杂性。
9. 整数关系侦查算法
1977年,BrighamYoung大学的Helaman Ferguson 和Rodney Forcade提出了整数关系侦查算法。这是一个古老的问题:给定—组实数,例如说x(1),x(2),...,x(n) ,是否存在整数a(1),a(2),..,a(n) (不全为零),使得
a(1)x(1)+a(2)x(2)+...+a(n)x(n)=0
对于n=2 ,历史悠久的欧几里得算法能做这项工作、计算x(1)/x(2) 的连分数展开中的各项。如果x(1)/x(2) 是有理数,展开会终止,在适当展开后就给出了“最小的”整数a(1)和a(2) 。欧几里得算法不终止——或者如果你只是简单地由于厌倦计算——那么展开的过程至少提供了最小整数关系的大小的下界。Ferguson和Forcade的推广更有威力,尽管这种推广更难于执行(和理解)。例如,他们的侦查算法被用来求得逻辑斯谛(logistic)映射的第三和第四个分歧点,b(3)=3.544090 和 b(4)=3.564407所满足的多项式的精确系数。(后者是120 阶的多项式;它的最大的系数是257^30 。)已证明该算法在简化量子场论中的Feynman图的计算中是有用的。
10. 快速多极算法
1987年,耶鲁大学的Leslie Greengard 和Vladimir Rokhlin发明了快速多极算法。该算法克服了N体模拟中最令人头疼的困难之一:经由引力或静电力相互作用的N个粒子运动的精确计算(想象一下银河系中的星体,或者蛋白质中的原于)看来需要O(N*N) 的计算量——比较每一对质点需要一次计算。该算法利用多极展开(净电荷或质量、偶极矩、四矩,等等)来近似遥远的一组质点对当地一组质点的影响。空间的层次分解用来确定当距离增大时,比以往任何时候都更大的质点组。快速多极算法的一个明显优点是具有严格的误差估计,这是许多算法所缺少的性质。


?算法在同构意义下的分类
简单算法与由简单算法组成的复杂算法,一个算法对应一个函数
算法复杂性常用术语
大O估计:常数复杂性O(1),对数复杂性O(logn),线性复杂性O(n),nlogn复杂性O(nlogn),多项式复杂性O(n^b),指数复杂性O(b^n),b>1,阶乘复杂性O(n!)
算法的最坏情况分析告诉我们算法需要多少次运算就保证给出问题的解答。
复杂性分析:最坏情况分析,平均情况分析
能用具有多项式最坏情况复杂性的算法解决的问题称为易处理的,易处理的问题属于P类
另有:不易处理的,不可解的
能以多项式时间验证解的问题属于NP类
递归程序比迭代版本占用更多的存储空间,花费更多的时间
在设计好的算法中,经常使用的算法有以下43种[很多数值算法和非数值算法都没有列举出来]:
排序1~16
eg15:冒泡排序
procedure bubblesort(a1,,an)
for i:=1 to n-1//i循环n-1次,即使数组下标从0开始,也是i循环n-1次
begin
for j:=1 to n-i//j循环n-i次
if a_j>a_j+1 then 交换a_j与a_j+1
end{a1,,an为升序}
调用mathlib72.dll中函数
_pibub@8
对n个整数进行冒泡升序排序,函数原型为extern "C" _declspec(dllexport)void __stdcall pibub(int *p,int n);
//5678901234
// the sorted number;0123456789
记向量A=A[i=-1]={8,6,5,4,7,9,2,3,10,1},则冒泡排序的外层i循环有n-1=9次,升序排序过程为:
A[i=0]={6,5,4,7,8,2,3,9,1,10},
A[i=1]={5,4,6,7,2,3,8,1,9,10},
A[i=2]={4,5,6,2,3,7,1,8,9,10},
A[i=3]={4,5,2,3,6,1,7,8,9,10},
……
A[i=8]={1,2,3,4,5,6,7,8,9,10}。
以上人工分析的排序过程与计算机程序调试输出的结果是一致的:
A[i=0]=65478239110
A[i=1]=54672381910
A[i=2]=45623718910
A[i=3]=45236178910
A[i=4]=42351678910
A[i=5]=23415678910
A[i=6]=23145678910
A[i=7]=21345678910
A[i=8]=12345678910
只需要在函数体里加入以下代码:
printf("\n");
printf("A[i=%d]=",i);
for(int k=0;k<n;k++)
printf("%d",p[k]);
调用mathlib72.dll中函数_pcbub@8对n个字符进行冒泡升序排序,函数原型为extern "C" _declspec(dllexport)void __stdcall pcbub(char *p,int n);
调用MATHLIB.DLL中函数_phbub@16对n个字符串进行冒泡升序排序,函数原型为extern "C" _declspec(dllexport)void __stdcall phbub(char **p,int n,int k,int m);
……
static char *p[10]={"main","gou","zhao","lin","wang","zhang","li","zhen","ma","sub"};
phbub(p,10,0,9);//10个字符串排序
……
//main , gou , zhao , lin , wang , zhang , li , zhen , ma , sub ,
//gou , li , lin , ma , main , sub , wang , zhang , zhao , zhen ,
#include<stdio.h>
//#pragma comment(lib,"F:\\实用程序与重要数据备份\\mathlib72.lib")
//extern "C" _declspec(dllexport)void __stdcall pibub(int *p,int n,int ascending);
//bubble_sort

void __stdcall pibub(int *p,int n,int ascending)//ascending=1表示升序,0表示降序
{
int temp,i,j;
if(ascending)
{
for(i=0;i<n-1;i++)
{
for(j=0;j<n-i-1;j++)
if(p[j]>p[j+1])//冒泡升序
{
temp=p[j];
p[j]=p[j+1];
p[j+1]=temp;
}
}
}
else
{
for(i=0;i<n-1;i++)//i循环n-1次
{
for(j=0;j<n-i-1;j++)//不能写成for(j=0;j<n-i;j++),即j循环n-i-1次
if(p[j]<p[j+1])//冒泡降序
{
temp=p[j];
p[j]=p[j+1];
p[j+1]=temp;
}
}
}
}
void __stdcall pisort(int *p,int n,int ascending)//ascending=1表示升序,0表示降序
{
int temp,i,j;
if(ascending)
{
for(i=0;i<n;i++)
{
for(j=0;j<n;j++)
if(p[i]<p[j])//升序
或for(j=i;j<n;j++)
{
if(p[i]>p[j])//选择升序排序Ⅰ

{
temp=p[i];
p[i]=p[j];
p[j]=temp;
}
}
}
else
{
for(i=0;i<n;i++)
{
for(j=0;j<n;j++)
if(p[i]>p[j])//降序
或for(i=0;i<n;i++)
{
for(j=i;j<n;j++)
if(p[i]<p[j])//选择降序排序Ⅰ

{
temp=p[i];
p[i]=p[j];
p[j]=temp;
}
}
}
}

int main()
{
int a[10]={0},i;
for(i=0;i<5;i++)
a[i]=i+5;
for(i=5;i<10;i++)
a[i]=i-5;
for(i=0;i<10;i++)
printf("%d",a[i]);   
pibub(a,10,1);//10个整数冒泡升序排序
printf("\n the sorted number;");
for(i=0;i<10;i++)
printf("%d",a[i]);
pibub(a,10,0);//10个整数冒泡降序排序
printf("\n the sorted number;");
for(i=0;i<10;i++)
printf("%d",a[i]);
return 0;
}

5678901234
 the sorted number;0123456789
 the sorted number;9876543210
若内外循环处的代码改为
for(i=0;i<n-1;i++)
{
for(j=i;j<n-1;j++)
{
temp=p[i];
if(p[i]>p[j+1])//选择升序,?不是p[j]>p[j+1]
{
p[i]=p[j+1];
p[j+1]=temp;
}
}就叫做选择升序排序Ⅱ,
以下是选择升序排序的过程:
86547923101
A[i=0]=18657943102[?不是18657942103]
A[i=1]=12867954103
A[i=2]=12387965104
A[i=3]=12348976105
A[i=4]=12345987106
A[i=5]=12345698107
A[i=6]=12345679108
A[i=7]=12345678109
A[i=8]=12345678910
若内外循环处的代码改为
for(i=0;i<n;i++)
{
for(j=0;j<i;j++)
{
if(p[j]>p[i])//插入升序排序
{
temp=p[i];
//插入
for(int k=i;k>=j;k--)p[k]=p[k-1];
p[j]=temp;
}
}
就叫做插入升序排序,
以下是插入升序排序的过程:
86547923101
A[i=0]=86547923101
A[i=1]=68547923101
A[i=2]=56847923101
A[i=3]=45687923101
A[i=4]=45678923101
A[i=5]=45678923101
A[i=6]=24567893101
A[i=7]=23456789101
A[i=8]=23456789101
A[i=9]=12345678910

另外有快速排序(1962年Hoare提出该算法)的几个函数:_piqck@8_pcqck@8_phqck@16,函数原型完全与冒泡排序相同。
Void quicksort(char *arr,int startPos,int endPos)//对字符数组进行快速升序排序
{
Char ch=arr[startPos];
Int i= startPos;
Int j=endPos;
While(i<j)
{
While(arr[j]>=ch && i<j)—j;
Arr=arr[j];
While(arr[j]<=ch && i<j)++I;
Arr[j]=arr;
}
Arr=ch;
If(i-1>startPos)quicksort(arr,startpos,i-1);
If(endpos>i+1) quicksort(arr,i+1,endpos);
}
选择17
查找18

顺序查找法适合于存储结构为顺序存储或链接存储的线性表。
int seqsearch(sqlist r,int k,int n)
{
int i=0;
while(i<n&&r[i].key!=k)
i++;
if(i>n)i=-1;
return (i);
}
递归顺序搜索算法
procedure search(i,j,x)
if x=a_i then
location:=i
else if(i=j) then
location:=0
else
search(i+1,j,x)
eg2:线性搜索算法,O(n)
procedure linear_search(x:整数,a_1,a_2,,a_n:递增整数)
i:=1{i是搜索区间的左端点}
while i<=n&&x!=a_i
i:=i+1
if i<=n then location:=i
else location:=0
{location是等于x的项的下标,或在找不到x时为0}

 

二分查找:又称为对分搜索/折半查找(Binary Search),它要求线性表中结点必须按值递增或递减顺序排列。
二分查找的存储结构仅限于顺序存储结构,且是有序的。
int binsearch(sqlist r,int k,int n)
{
int i,low=0,high=n-1,mid,find=0;
while(low<=high&&!find)
{
mid=(low+high)/2;
if(k<r[mid].key)high=mid-1;
else if(k>r[mid].key)high=mid+1;
else{i=mid;find=1;}
}
if(!find)i=-1;
return (i);
}


递归二叉搜

;