打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
计算机语言递归原理及行列式递归求值
计算机语言递归原理及行列式递归求值
 杨忆鸿    2015.11.11   
 
     如果要彻底明白递归执行原理,就必须有一个循序渐进的过程,从基本开始才能了解,栈很简单,属于数据结构的概念,如果对栈一点都不了解,就必须先了解什么是栈单元、栈顶变量、入栈、出栈。
1.子程序执行的过程:
       调用子程序的程序称为调用者,调用者先将调用参数值压入栈中,再将返回地址也压入栈中,跳转到子程序里执行,子程序中每有局部变量都在栈中分配,执行完复合语句则清除刚在栈中分配的局部 变量。执行完子程序,从栈中得到返回地址返回到调用者,并由调用者清除掉栈中的调用参数单元。 这时栈恢复到调用前的情况。子程序调用过程离不开“栈”,所以,要首先明白栈的原理,才能进一步明白递归调用原理。如果一个子程序有返回值,这子程序也可称为函数,函数执行完,把返回值放到一个固定的静态单元或寄存器中,函数执行完调用者必须取走返回值,否则返回值将被冲掉。一个子程序调用另一个子程序,下一级执行完必须自动返回到上一级,递归调用同样必须遵守这一原则,称为子程序的“返回原则”。
 
2.什么是递归调用?
      子程序自已调用自已就称为递归调用,这跟普通子程序调用一样的原理,都利用栈,离不开栈。

3.为什么能够递归调用?
      子程序执行中,调用参数、返回地址、局部变量都在栈中分配或存放的,每一次调用未结束前,这些重要参数都在栈中得到保护,栈单元先进后出、后进先出,多级子程序互不干扰,当然要求栈的单元足够多。都是基于栈的原理,所以,递归调用与非递归调用都是一样可以实现的,每向下调用一次,栈就增加一些单元,都是子程序调用原理,没有大的区别,递归只是调用了自已而已,你要理解成一回事。

4.递归调用有什么特殊要求?
    由于递归是自已调用自已,这样就会无限调用下去,直到栈单元不够用而溢出,就会引起程序崩溃。所以递归子程序中,必须设置结束条件,一旦到达某种特殊情况,子程序就不再是递归的,以便有返回到上级调用者的机会。 

5.为什么需要递归?递归调用有什么优点与缺点?
   对递归算法的理解可以不需要了解递归的具体执行原理,这就是“透明性质”。
   例如:最简单的阶乘 n!=n*(n-1)!, 1!=1。
  有此算法的描述本身就是递归的,如果用递归程序实用递归算法,就会很简单。
  如:斐波那契数列,F0=0,F1=1,F(n)=F(n-1)+F(n-2)(n≥2)就是递归描述,特殊情况F(0),F(1)  是不需要递归的,就是递归的结束条件。
  再比“汉诺塔”,就是经典的递归算法,只有几行程序,百度一下,了解它的递归算法原理。
  Hanoi(int n,char a,char b, char c)
  {
     if (n==1)
         显示(n,a,c);
     else
        {
           hanoi(n-1,a,c,b);
           显示(n-1,a,c);
           hanoi(n-1,b,a,c);
        }
  }
  递归的优点是:许多算法本身就是递归的,用递归实现非常简单,改成非递归就很复杂。
  递归的缺点:多级递归,占用了大量的栈单元,许多是重复递归,效率很低。

6.递归的种类?
  1.单级递归:
       递归算法中,只出现一次调用自已,就是单级递归。
       这是最简单的递归,如阶乘。n!=n*(n-1)!,1!=1
       算法中,虽出现一次递归调用,但真正实现计算时,递归了n-1次,
       单级递归完全可以改成循环迭代实现。
  2.二级递归:
       递归算法中,出现两次调用自已,就是二级递归。
       斐波那契数列、汉诺塔、二叉树的遍历就是二级递归;实际递归执行时将
       产生2的n-1次方递归调用,消耗的栈单元较多。
  3.多级递归:
       递归算法中,出现两次以上调用自已,就是多级递归,递归次数呈指数级关系,
       将消耗大量的栈单元,只有练习意义,没有实用价值,行列式的递归计算就是典型例子。
7.递归的具体执行原理?
      递归算法能够正确执行,但递归是如何执行的,常令初学者关切。
      实际它跟普通子程序调用并没有大的区别,遵守子程序的“返回原则”而已,
      你只要把递归调用理解成调用另一个同名的子程序一样即可。
   
    int p(int n)  // 求阶乘递归子程序
      {
         if (n==1)
            return(1);
         else
            return(n*p(n-1));
      }
    如果你在主程序调用了 P(4),当它执行到 4*P(3)时,等待P(3)执行完后才能返回;
      执行p(3)时,当它执行3*p(2),等待P(2)执行完后才能返回;
      执行p(2)时,当它执行2*p(1),等待P(1)执行完后才能返回;
      执行p(1),直接得到了结果1,P(1)执行结束,
      按照返回原则,P(1)将返回到它的上一级调用者P(2),2*P(1)得到结果2,P(2)执行结束;
      遵照返回原则,P(2)将返回到它的上一级调用者P(3),3*P(2)得到结果6,P(3)执行结束;
      遵照返回原则,P(3)将返回到它的上一级调用者P(4),4*P(3)得到结果24,P(4)执行结束;     
      遵照返回原则,P(4)将返回到它的上一级调用者主程序, 执行结束;

      当不能得到直接结果就会再次调用,在栈中保存局部变量及返回地址,栈越来越深,
      当不再递归时,就会按栈中的返回地址,返回到它的上一级调用者继续执行,
      直到返回到主程序,就象倒着执行一样。

8.稍复杂递归算法:行列式值的计算
    n阶行列式值的计算可以化为n个n-1阶行列式的和,可以按第1列(y=0)展开,这就是行列式递归求值算
法的原理。
        存放行列式用二维数组较好,但一般语言都不支持动态阶数,所以,就用一维数组顺序存放行列式数据,再用二维的宏表达式detV(d,x,y)模仿二维数组d[x,y],可理解性稍差点,但用一维数据可以方便余子式的产生,只要一次new就可创建单元,而指针数组要n+1次new就太多了。
        行列式类TDet,就含一个阶数与一维数组地址,并封装了单元申请用释放,你可以改成不用class类。
        detCopy(x,y,d,d1);  就是复制行列式 x行y列的余子式数据,余子式用了顺序存放*p++,简单化了。
        递归子程序detCal(det1) 很短而简单,用了一重循环,在第一列(y=0)展开,v累计结果,余子式是有符号变化的,称代数余子式,首行正号、下一行负号,交替变化,而余子式的计算就是再次递归调用而已。
        二级或一级行列式,就是递归的结束条件,二阶就直接计算了。


/-------------------------------------------
//   递归法求行列式的值 detCal
//              杨忆鸿  2015.11.11
//-------------------------------------------
#define detV(d,x,y)  (d->det[(x)*(d->nn)+(y)])  // 一维d[]映射成二维d[x,y]

class  TDet                  //  定义行列式类
     {
       public:
          double *det;       //  一维数组作为行列式,节省单元,也方便余子式的产生
          int nn;                 //  行列式为nn X nn个单元:  nn>=0

          TDet(int n)         //  行列式单元申请
          {
             nn=n;
             det=new double[nn*nn];
          }
          ~TDet()
          {
             delete [] det;  //  释放行列式单元
          }
      };

 void detCopy(int x,int y,TDet *d,TDet *d1)
    {     // 将行列式d的x行y列的余子式复制到另一行列式d1,(nn-1)单元
       double *p=d1->det;
       for (int i=0;i<d->nn;i++)
           for (int j=0;j<d->nn;j++)
               if (x!=i && y!=j)
                  *p++=detV(d,i,j);        // 顺序存放到d1中
    }

double  detCal(TDet *d)     //  递归法求行列式的值
{
    if (d->nn<=1)                       // 一阶行列式
       return(detV(d,0,0));
    else
    if (d->nn==2)                       // 二阶行列式
       return(detV(d,0,0)*detV(d,1,1)-
              detV(d,0,1)*detV(d,1,0));
    else
       {                                         // 按第0列展开行列式
           double v=0.0;                // 累计行列式值
           TDet *d1=new TDet(d->nn-1);  // 就一次创建余子式d1单元
           for (int i=0;i<d->nn;i++)    // 行列式按第一列展开
             {
                detCopy(i,0,d,d1);      // i行0列产生余子式
                double t=detV(d,i,0);
                if (i%2!=0))                 // i奇数则余子式符号反向
                   t=-t;
                v=v+t*detCal(d1);       // 再次递归求余子式d1
             }
           delete d1;                        // 释放余子式d1单元
           return(v);
       }
}
//  以下是C++BUILDER主程序

String disp(double d)
{
         char buf[20];
         sprintf(buf,"%5.1lf",d);
         return(String(buf));
}
void __fastcall TForm1::Button1Click(TObject *Sender)
{   //
         double d2[2][2]={{1,2},{3,4}};
         double d3[3][3]={{1,2,3},{6,5,4},{9,8,1}};
         double d4[4][4]={{4,3,2,1},{6,8,7,5},{12,11,10,9},{7,9,4,1}};
         TDet *d=new TDet(3);     // 数组送进行列式
         for (int x=0;x<d->nn;x++)
            for (int y=0;y<d->nn;y++)
               detV(d,x,y)=d3[x][y];
         double t=detCal(d);
         delete d;
         Memo1->Lines->Add(disp(t));  // 显示值
}
//


 ------------------------

本人原创,引用须明出处及作者

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
18张图,直观理解神经网络、流形和拓扑
漫谈递归:递归与循环
colah 深度学习系列长文(一)
第06讲 矩阵行列式算法初步
[做题要思考的问题]信息学竞赛之设计获胜策略
MIT开发新系统 让初学者也能处理复杂程序
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服