数学在计算机中的应用_Honeyhacker_百度空间

【序言】本文以六个实例,来探讨数学在计算机编程中的应用。               

【正文】

  首先我们来看一个使用数学方法可以大大提高效率的例子。
  实例一:素数判断。给定一个自然数a,判断它是不是质数。
  普通的想法:若a是合数,那么必然有一个因数不大于a1/2,建立一个a1/2以内的质数表,逐一检索。显然,这样速度太慢!
  下面介绍一种基于费马小定理的Miller-Rabin测试算法:
  首先是引理:费马小定理,相信大家都有耳闻,这里我也不嫌累赘,仍旧列出:若n是质数,(a,n)=1,则an-1mod n =1。
  同样,若我们选取若干个a,都满足以上等式的话,几乎可以肯定n是素数。(尽管不能xx确认,但在实际操作中是可行的)下面给出算法:
  Function Miller-Rabin(n:longint):Boolean;
  Begin
  For I:=1 to s do
          Begin
                  a:=random(n-2)+2;
                         If modular_exp(a,n-1,n)<>1 then return false;
                                   End;
                                   Return true;
  End;
  在RSA算法中,就应用了上述算法来检测一个大数是否为素数。事实上,数学在计算机当中最为重要的还是递推关系的应用:许多看似棘手的题目,在有了这一层的关系后便显得柳暗花明了。

实例二:Hannoi塔问题
Hanoi塔由n个大小不同的圆盘和三根木柱a,b,c组成。开始时,这n个圆盘由大到小依次套在a柱上,要求把a柱上n个圆盘按下述规则移到c柱上:
(1)一次只能移一个圆盘;
(2)圆盘只能在三个柱上存放;
(3)在移动过程中,不允许大盘压小盘。
问将这n个盘子从a柱移到c柱上,总计需要移动多少个盘次?
解:设h(n)为n个盘子从a柱移到c柱所需移动的盘次。显然,当n=1时,只需把a柱上的盘子直接移动到c柱就可以了,故h(1)=1。当n=2时,先将a柱上面的小盘子移动到b柱上去;然后将大盘子从a柱移到c柱;{zh1},将b柱上的小盘子移到c柱上,共计3个盘次,故h(2)=3。以此类推,当a柱上有n(n>=2)个盘子时,总是先借助c柱把上面的n-1个盘移动到b柱上,然后把a柱最下面的盘子移动到c柱上;再借助a柱把b柱上的n-1个盘子移动到c柱上;总共移动h(n-1)+1+h(n-1)个盘次。所以:h(n)=2h(n-1)+1 (边界条件:h1=1)

  这个问题其实只是数学题目的简单变形。下面再来看一个应用更加灵活的例子:

实例三:方格取数:在一个n*m的方格中,m为奇数,放置有n*m个数,方格中间的下方有一人,此人可按照正前方相临的五个方向(方格)前进但不能越出方格。人每走过一个方格必须取此方格中的数。要求找到一条从底到顶的路径,使其数相加之和为{zd0}。输出和的{zd0}值。
解:这题在本质上类似于递推,是从一个点可以到达的点计算可以到达一个点的所有可能点,然后从中发掘它们的关系。我们用坐标(x,y){wy}确定一个点,其中(m,n)表示图的右上角,而人的出发点是([m/2],0),受人前进方向的限制,能直接到达点(x,y)的点只有(x+2,y-1),(x+1,y-1),(x,y-1),(x-1,y-1),(x-2,y-1)。
  到达(x,y)的路径中和{zd0}的路径必然要从到(x+2,y-1),(x+1,y-1),(x,y-1),(x-1,y-1),(x-2,y-1)的几条路径中产生,既然要求{zy}方案,当然要挑一条和{zd0}的路径,关系式如下:F(x,y)=Max{F(x+2,y-1),F(x+1,y-1),F(x,y-1),F(x-1,y-1),F(x-2,y-1)}+Num(x,y),其中Num(x,y)表示(x,y)点上的数字。(边界条件为:F([m/2],0)=0,F(x,0)=-0(1<=x<=m且x<>[m/2]))。
  这种问题,涉及到最值,采用的递推手法被称为"动态规划"。简称DP。
  程序设计中可采用多种数学方法,恰如其分的数学方法可以大大减少程序运行的时间和所需空间,起到优化程序的作用。遇到一道题目时,如进制运算,多项式运算等,应不急于马上用递归,回溯等搜索算法,特别是测试数据的范围很大的时候。不妨先用笔算,从中发现一些规律.但是也不是每一道题都可以用数学方法完成,数学方法只能用于一些求总数,最值之类的题目上。
下面便是经典应用之一:


实例四:砝码设计:设有一个天平,可以用来称重.
   任务一:设计n个砝码的重量,用他们能称出尽可能多的整数重量.例如,n=2,设计2个砝码的重量分别为1和3, 可称重为1,2,3,4的连续重量.
   任务二:在给出n个砝码能称出{zd0}重量范围的一重量x,试给出称出x的方案.
在上例中:给出x=2称出的方案为2+1:3
             x=4称出的方案为4:1+3
             x=1称出的方案为1:1
输入:n,x(n为砝码个数,x是在称出{zd0}重量范围内的重量)
输出:砝码方案,称出x的方案.
输入样例1:2,2 输入样例2:2,4
输出样例1:1,3 输出样例2:1,3
由题意可知此题不适合搜索。由任务一可知:n=2时砝码重量{zy}解为1、3。 我们可以试n=3,n=4...的情况,不难发现本题是一个“平衡三进制”的应用,砝码的重量均为3的1至n次方,由于理论推导涉及到累加等数学知识,我们着重看任务二。
任务二要求输出用砝码称出重为x的重量,实际上就是用3的0至n-1次方的和差来表示x,如样例中的2=3-1,4=1+3等,不难发现,当x除以3余1时,必然x要表示为
x=a1+a2...+1,余2时x=a1+a2...-1,余0时不用1的砝码.因此取x除以3的余数,可以确定砝码1用不用和用在天平的哪一边.同理,判断3的砝码位置时,可先将x先除以3
四舍五入,再除以3取余判断.能用3的1至n次方的和差来表示x后,屏幕输出再用一个数组来处理就行了。
参考算法:
  procedure   balance (x:integer);
  var
  j,b:integer;
  begin
       t1:=0; t2:=0; j:=1;
    repeat
  b:=x mod 3;
  x:=round(x/3);
  if b=2 then begin t1:=t1+1; ch1[t1]:=j; end;    {物品一端}
  if b=1 then begin t2:=t2+1; ch2[t2]:=j; end;    {砝码一端}
  j:=j*3;
  until x=0;
  end;

接下来,让我们来看一个数学原理在图论中的应用:
实例五:欧拉回路
首先让我们了解一个定义:欧拉回路与欧拉图 包含了图G的每一条边,且每条边仅出现一次的通路,就是欧拉通路。包含了图G的每一条边,且每条边仅出现一次的回路,就是欧拉回路。存在欧拉回路的图,称为欧拉图。存在欧拉通路,但不存在欧拉回路的图,称为半欧拉图。欧拉回路要求边不能重复,结点可以重复。笔不离开纸,不重复地走完所有的边,回到原处。就是所谓的一笔画。
怎样判定一个图是不是欧拉图呢,我们有以下方法:
欧拉图的判定
(1) 无向连通图G是欧拉图?G不含奇数度的结点(即G的所有结点的度均为偶数(0视为偶数));(定理1)
(2) 非0平凡图G有欧拉通路?G最多有两个奇数度的结点;(定理1的推论)
(3) 有向图D是欧拉图?D连通且D的所有结点的入度等于出度。有向连通图有欧拉通路?除两个结点外,其余结点的出度均等于入度,且这两点deg-(v)-deg+(v)=±1。 (定理2)
来看一个实际应用的题目:农民John每年有很多栅栏要修理。他总是骑着马穿过每一个栅栏并修复它破损的地方。John是一个与其他农民一样懒的人。他讨厌骑马,因此从来不两次经过一个一个栅栏。你必须编一个程序,读入栅栏网络的描述,并计算出一条修栅栏的路径,使每个栅栏都恰好被经过一次。John能从任何一个顶点(即两个栅栏的交点)开始骑马,在任意一个顶点结束。每一个栅栏连接两个顶点,顶点用1到500标号(虽然有的农场并没有500个顶点)。一个顶点上可连接任意多(>=1)个栅栏。所有栅栏都是连通的(也就是你可以从任意一个栅栏到达另外的所有栅栏)。
你的程序必须输出骑马的路径(用路上依次经过的顶点号码表示)。
输入数据保证至少有一个解。
这便是一个典型的欧拉回路问题,每一个栅栏都是一条边。
根据上文的阐述,我们有以下算法:
任取一个起点(若有两个奇数度的点,则从其中一点开始),开始下面的步骤
如果该点没有相连的点,就将该点加进路径中然后返回。
如果该点有相连的点,就列一张相连点的表然后遍历它们直到该点没有相连的点。(遍历一个点,删除一个点)
处理当前的点,删除和这个点相连的边,在它相邻的点上重复上面的步骤,把当前这个点加入路径中。
  参考程序:
  var
  net:array[1..500,1..500] of boolean;    {存储网络图}
  path:array[1..500,1..500] of integer;   {记两个顶点有多少条路径,不一定只有一条}
  du:array[1..500] of integer;            {记每个顶点的度}
  queen,stack:array[1..10000] of integer; {堆栈和队列}
  i,k,len,r,max,m,n,f,q:integer;
  flag:boolean;
  begin
  fillchar(net,sizeof(net),false);
  fillchar(du,sizeof(du),0);
  fillchar(path,sizeof(path),0);
  readln(f);
  max:=0;                {记录出现的{zd0}结点编号}
  for i:=1 to f do
  begin
  readln(n,m);
  inc(du[n]);    inc(du[m]);
  if max
  if max
  net[n,m]:=true; net[m,n]:=true;
  inc(path[n,m]); inc(path[m,n]);
  end;
  {----------------------   main    ------------------}
  q:=1;
  len:=0;
  for i:=1 to max do
  if du[i] mod 2=1 then begin q:=i; break; end;     {找奇度点}
  k:=q;
  repeat
flag:=true;
  for i:=1 to max do   {找遍与k点相连的所有边}
  if net[k,i] then
  begin
  if path[i,k]>1 then begin path[i,k]:=path[i,k]-1; path[k,i]:=path[k,i]-1; end
  else begin net[k,i]:=false; net[i,k]:=false; end;
  r:=r+1; stack[r]:=k; k:=i; flag:=false; break;
  end;
  if flag then begin inc(len); queen[len]:=k; k:=stack[r]; r:=r-1; end;   {出栈}
  until r<0;
  for i:=len downto 1 do writeln(queen[i]);      {输出欧拉路}
  end.
  在计算机编程中,组合数学的应用也非常广泛,{zh1},让我们看一个组合数学在编程中的实际例子:

实例六:无聊的排序:你弟弟有一项家庭作业需要你帮助完成。老师给了他一列数,需要他把这些数按升序排列。你可以每次交换两个数的位置,而一次交换的代价被定义成被交换的两个数的和。写一个程序,用最小的交换代价和来帮助弟弟完成这项无聊的排序工作。
输入:{dy}行为一个数N(N<=100),第二行为互不相同的N个数。
输出:输出一个数,为最小的交换代价。
解:本题可以抽象为把一列数从初始状态变成目标状态,即完成一个置换。根据群论知识,置换可以分解为s个不相交的循环的乘积。显然,由于每次只有被交换两个数的位置改变,所以要想改变一个数的位置,只能通过交换完成,而不能像插入排序一样,可以借助其他数来完成,即各个循环是相互独立的,所以应该依次完成每个循环。为了得到尽量少的交换代价,在每个循环中较好的方法是让循环中最小元素或全局最小元素参加所有的交换。至于用循环内的最小元素还是用全局中的最小元素,就要比较哪个交换代价更小了。
  参考程序:
  var
  a,b:array[1..100] of integer;
  n,i,inum,num,numtmp,len,j,k:integer;
  sum:longint;
  begin
  readln(n); num:=maxint;
  while n<>0 do
  begin
  for i:=1 to n do    read(a[i]);                         {读入信息存放a中}
  readln;
  b:=a;
  for i:=1 to n-1 do                              {选择排序b数组}
  begin
  k:=i;
  for j:=i+1 to n do
  if b[j]
  if k<>i then begin numtmp:=b[k]; b[k]:=b[i]; b[i]:=numtmp; end;
  end;
  num:=b[1];
  sum:=0;
  for i:=1 to n do
  if a[i]<>-1 then    {分别处理每个循环节}
  begin
  inum:=b[i]; numtmp:=a[i]; len:=0;
while inum<>a[i] do
  begin
  for k:=1 to n do
  if a[k]=inum then break;
  if inum
  sum:=sum+inum;
  inum:=b[k];
  a[k]:=-1;
  inc(len);
  end;
  sum:=sum+inum; inc(len); a[i]:=-1;
  if (len-2)*numtmp
  sum:=sum+ (len-2)*numtmp
  else
  sum:=sum+ numtmp+(len+1)*num;
  end;
  writeln(sum);
  readln(n);
  end;
  end.
【结束语】:数学方法的合理运用,可以给编程带来很大方便。

  注:本文转载,出处不详,略有修改。



郑重声明:资讯 【数学在计算机中的应用_Honeyhacker_百度空间】由 发布,版权归原作者及其所在单位,其原创性以及文中陈述文字和内容未经(企业库qiyeku.com)证实,请读者仅作参考,并请自行核实相关内容。若本文有侵犯到您的版权, 请你提供相关证明及申请并与我们联系(qiyeku # qq.com)或【在线投诉】,我们审核后将会尽快处理。
—— 相关资讯 ——