• 栈与递归


      递归的概念

      栈的一个典型应用是程序设计中的递归过程的设计与实现,用栈来保存调用过程中的参数和返回地址。

    递归过程(或函数)就是子程序或函数中直接或间接的调用自己。

      递归定义实例:

      1.某人祖先的递归定义:

    某人的双亲是他的祖先[基本情况]

    某人祖先的双亲同样是某人的祖先[递归步骤]

      2.阶乘函数

           

       3.斐波那契数列

     

       一般递归分为直接递归间接递归

                         

      4.Ackerman函数

      当两个连续函数都趋近于无穷时,我们常用洛必达法则来比较它们趋向无穷的快慢。函数的阶越高,

    它趋向无穷的速度就越快。定义在正整数域上的函数中,n!趋向于正无穷的速度非常快,所以在算法设计中

    如果出现这样的时间复杂度就非常糟糕。logn趋向无穷的速度则非常慢。

       而Ackerman函数可以比n!的增长速度快得多,比logn的增长速度慢的多。同时,并不是所有的

    递归函数都有通项公式,Ackerman函数就是一个例子,它是双向递归函数,有两个自变量(独立),定义如下:

      

                  Ack(0,n)=n+1 , n>=0;

                 

                  Ack(m,0)=Ack(m-1,1) , m>0;

                 

                  Ack(m,n)=Ack(Ack(m-1,n),n-1) , n,m>0

     1 #include <stdio.h>
     2 
     3 #include <stdlib.h>
     4 
     5 int   ack(int  m, int   n)
     6 
     7 {
     8 
     9     int    z;
    10 
    11     if (m == 0)
    12 
    13          z = n + 1; //出口
    14 
    15     else if (n == 0)
    16 
    17          z = ack(m - 1, 1);  //形参m降阶
    18 
    19     else
    20 
    21          z = ack(m - 1, ack(m, n - 1)); //对形参m,n降阶
    22 
    23     return z;
    24 
    25 }
    26 
    27         
    28 
    29 void main() {
    30 
    31      int  m, n;
    32 
    33     scanf("%d%d", &m, &n);
    34 
    35     printf("Ack(%d,%d)=%d
    ",m,n, ack(m, n));
    36 
    37  
    38 
    39 } 

      递归过程的内部实现

      从递归调用的过程可知,它们刚好符合后进先出的原则。因此,利用栈实现递归过程再合适不过。

    下面以求n!为例,介绍它的递归实现过程。

      递归算法为

      

     1 int fact(int N)
     2 
     3 {
     4 
     5 int result;
     6 
     7 if(N == 0)
     8 
     9 result = 1;
    10 
    11 else
    12 
    13 result = fact(n-1)*n  //递归调用自身
    14 
    15 return result;
    16 
    17 }

       在递归执行过程中,需要一个栈LS保存递归调用前的参数及递归的返回地址。参数为fact中的现行值,返回地址为递归语句的下一语句入口地址。设第1次调用的返回地址为p1,第2次调用的返回地址为p2, … 如图1所示

     具体实现为

    (1)遇递归调用时,将当前参数与返回地址保存在LS中;

    (2)遇递归返回语句时,将LS栈顶的参数及返回地址一并弹出,按当前返回地址继续执行

     

      分析递归程序的运行机制,递归用需要1-7的过程,而非递归只需要4-7过程,可见递归程序的运行效率并不高,

    即并不能节约运行时间,相反,由于保存大量的递归调用的参数和返回地址,还要消耗一定的时间,再加上空间上的消耗,

    同非递归递归函数相比毫无优势而言。但是尽管递归在时空方面不占优势,但有编程方便,结构清楚,逻辑结构严谨等优势。

    递归的致命缺点是时空性能不好,但是我们可以用非递归方法来解决递归问题,从而改善程序的时空性能。

    递归消除

           递归的消除有两种:简单递归消除和基于栈的递归消除

      1.简单递归消除

      尾递归是指递归调用用语句只有一个, 且处于算法的最后,尾递归是单向递归的特例. n!递归算法是尾递归的一个典型例子.

      对于尾递归,当递归返回时, 返回到上一层递归调用语句的下一语句时已到程序的末尾, 所以不需要栈LS保存返回地址。

      2.基于栈的递归消除

      具体方法:将递归算法中的递归调用语句改成压入操作;将递归返回语句改为弹出操作。

    sqstack.h:

    https://www.cnblogs.com/mwq1024/p/10146943.html

    //计算n!不需要保存返回地址 原因上面说了

     1 #include <stdio.h>
     2 
     3 #include <stdlib.h>
     4 
     5 #include "sqstack.h" //引入顺序栈储存结构及其基本操作
     6 
     7  
     8 
     9 int fact(int N) {
    10 
    11     int result = 1;
    12 
    13     SqStack s,*S;
    14 
    15     S = &s;
    16 
    17     StackType *e;
    18 
    19     e = (StackType*)malloc(sizeof(StackType));
    20 
    21     InitStack(S);
    22 
    23     while (N)
    24 
    25     {
    26 
    27          Push(S, N);
    28 
    29          N--;
    30 
    31     }
    32 
    33     while (!EmptyStack(S))
    34 
    35     {
    36 
    37          result *= *(S->top - 1);
    38 
    39          Pop(S, e);
    40 
    41     }
    42 
    43     return result;
    44 
    45    
    46 
    47 }
    48 
    49 void main()
    50 
    51 {
    52 
    53     int n;
    54 
    55     printf("请输入n的阶数:");
    56 
    57     scanf("%d", &n);
    58 
    59     printf("result is %d
    ", fact(n));
    60 
    61 }

        利用栈解决汉诺塔问题

        把三个塔座看成三个栈,入栈和出栈就相当于移动盘子。

    汉诺塔算法的非递归实现C++源代码

      

      1 #include <iostream>
      2 
      3 using namespace std;
      4 
      5 //圆盘的个数最多为64
      6 
      7 const int MAX = 64;
      8 
      9 //用来表示每根柱子的信息
     10 
     11 struct st{
     12 
     13 int s[MAX]; //柱子上的圆盘存储情况
     14 
     15 int top; //栈顶,用来最上面的圆盘
     16 
     17 char name; //柱子的名字,可以是A,B,C中的一个
     18 
     19  
     20 
     21 int Top()//取栈顶元素
     22 
     23 {
     24 
     25 return s[top];
     26 
     27 }
     28 
     29 int Pop()//出栈
     30 
     31 {
     32 
     33 return s[top--];
     34 
     35 }
     36 
     37 void Push(int x)//入栈
     38 
     39 {
     40 
     41 s[++top] = x;
     42 
     43 }
     44 
     45 } ;
     46 
     47 long Pow(int x, int y); //计算x^y
     48 
     49 void Creat(st ta[], int n); //给结构数组设置初值
     50 
     51 void Hannuota(st ta[], long max); //移动汉诺塔的主要函数
     52 
     53 int main(void)
     54 
     55 {
     56 
     57 int n;
     58 
     59 cin >> n; //输入圆盘的个数
     60 
     61 st ta[3]; //三根柱子的信息用结构数组存储
     62 
     63 Creat(ta, n); //给结构数组设置初值
     64 
     65 long max = Pow(2, n) - 1;//动的次数应等于2^n - 1
     66 
     67 Hannuota(ta, max);//移动汉诺塔的主要函数
     68 
     69 system("pause");
     70 
     71 return 0;
     72 
     73 }
     74 
     75 void Creat(st ta[], int n)
     76 
     77 {
     78 
     79 ta[0].name = 'A';
     80 
     81 ta[0].top = n-1;
     82 
     83 //把所有的圆盘按从大到小的顺序放在柱子A上
     84 
     85 for (int i=0; i<n; i++)
     86 
     87 ta[0].s[i] = n - i;
     88 
     89 //柱子B,C上开始没有没有圆盘
     90 
     91 ta[1].top = ta[2].top = 0;
     92 
     93 for (int i=0; i<n; i++)
     94 
     95 ta[1].s[i] = ta[2].s[i] = 0;
     96 
     97 //若n为偶数,按顺时针方向依次摆放 A B C
     98 
     99 if (n%2 == 0)
    100 
    101 {
    102 
    103 ta[1].name = 'B';
    104 
    105 ta[2].name = 'C';
    106 
    107 }
    108 
    109 else //若n为奇数,按顺时针方向依次摆放 A C B
    110 
    111 {
    112 
    113 ta[1].name = 'C';
    114 
    115 ta[2].name = 'B';
    116 
    117 }
    118 
    119 }
    120 
    121 long Pow(int x, int y)
    122 
    123 {
    124 
    125 long sum = 1;
    126 
    127 for (int i=0; i<y; i++)
    128 
    129 sum *= x;
    130 
    131 return sum;
    132 
    133 }
    134 
    135 void Hannuota(st ta[], long max)
    136 
    137 {
    138 
    139 int k = 0; //累计移动的次数
    140 
    141 int i = 0;
    142 
    143 int ch;
    144 
    145 while (k < max)
    146 
    147 {
    148 
    149 //按顺时针方向把圆盘1从现在的柱子移动到下一根柱子
    150 
    151 ch = ta[i%3].Pop();
    152 
    153 ta[(i+1)%3].Push(ch);
    154 
    155 cout << ++k << ": " <<
    156 
    157 "Move disk " << ch << " from " << ta[i%3].name <<
    158 
    159 " to " << ta[(i+1)%3].name << endl;
    160 
    161 i++;
    162 
    163 //把另外两根柱子上可以移动的圆盘移动到新的柱子上
    164 
    165 if (k < max)
    166 
    167 { //把非空柱子上的圆盘移动到空柱子上,当两根柱子都为空时,移动较小的圆盘
    168 
    169 if (ta[(i+1)%3].Top() == 0 ||
    170 
    171 ta[(i-1)%3].Top() > 0 &&
    172 
    173 ta[(i+1)%3].Top() > ta[(i-1)%3].Top())
    174 
    175 {
    176 
    177 ch = ta[(i-1)%3].Pop();
    178 
    179 ta[(i+1)%3].Push(ch);
    180 
    181 cout << ++k << ": " << "Move disk "
    182 
    183 << ch << " from " << ta[(i-1)%3].name
    184 
    185 << " to " << ta[(i+1)%3].name << endl;
    186 
    187 }
    188 
    189 else
    190 
    191 {
    192 
    193 ch = ta[(i+1)%3].Pop();
    194 
    195 ta[(i-1)%3].Push(ch);
    196 
    197 cout << ++k << ": " << "Move disk "
    198 
    199 << ch << " from " << ta[(i+1)%3].name
    200 
    201 << " to " << ta[(i-1)%3].name << endl;
    202 
    203             }
    204 
    205         }
    206 
    207     }
    208 
    209 }  
  • 相关阅读:
    公司要上监控,Zabbix 和 Prometheus 怎么选?这么选准没错!
    60 个神级 VS Code 插件!!
    Elasticsearch 实现分页的 3 种方式,还有谁不会??
    紫微斗数是否对外国人有用
    Cygwin 安装时提示 “Could not download mirror sites list” 处理方法
    win 窗体 按钮 .Enabled:=false 中间做很多事情 还是会触发clik事件思考
    Docker Buildx使用教程:使用Buildx构建多平台镜像
    【问题解决】Alpine镜像中执行jstack、arthas等命令提示Unable to get pid of LinuxThreads manager thread
    MongoDB 远程连接配置
    星环TDHsearch启动失败master not discovered exception解决
  • 原文地址:https://www.cnblogs.com/mwq1024/p/10581194.html
Copyright © 2020-2023  润新知