• 【ACwing 93】【模版】非递归实现组合型枚举——模拟递归


    (题面来自ACwing)

    从 1~n 这 n 个整数中随机选出 m 个,输出所有可能的选择方案。

    输入格式

    两个整数 n,m ,在同一行用空格隔开。

    输出格式

    按照从小到大的顺序输出所有方案,每行1个。

    首先,同一行内的数升序排列,相邻两个数用一个空格隔开。

    其次,对于两个不同的行,对应下标的数一一比较,字典序较小的排在前面(例如1 3 5 7排在1 3 6 8前面)。

    数据范围

    n>0 ,
    0mn ,
    n+(nm)25

      此题的正解是dfs枚举,在之前的博客中有所提及。现在考虑用循环模拟机器递归的方式来做。

      计算机维护系统栈来实现递归,栈中每个元素记录三个状态:当前函数的参数、上一层递归的位置(旧栈顶),以及递归完成后,上一层递归函数应当执行的下一条语句。我们模拟的栈的上一层栈顶位置是显然的,不需要记录第二个参数。

      递归的过程是:每次将栈顶的元素出栈,确认它对应的状态执行到了哪一步,从这一步继续向下执行,直到返回或者遇到下一个递归调用。举个例子:下面是此题正解的dfs函数,我们可以把它分为3段,如代码所示。

    1. void dfs(int x) {  
    2.     //--------#0  
    3.     if (sel.size() > m || sel.size() + n - x + 1 < m)  
    4.         return;  
    5.     if (x == n + 1) {  
    6.         rep(0, m - 1)  
    7.             printf("%d ", sel[i]);  
    8.         puts("");  
    9.         return;  
    10.     }  
    11.     sel.push_back(x);  
    12.   //--------#0
    13.     dfs(x + 1);  
    14.     //--------#1  
    15.     sel.pop_back();  
    16.     //--------#1
    17.     dfs(x + 1);  
    18.     //--------#2  
    19.     return;  
    20. }  

      基于这段函数体,我们用循环来模拟递归调用的过程。定义状态state包含的两个元素为当前函数参数x、当前函数的执行位置;注意这里的状态和系统栈中有差异。系统栈中记录的是返回后函数的执行位置,这里为了更直观地演示当前函数的执行而做了修改。首先将第一个状态{1, 0}入栈(对应主函数中的递归调用入口)。接下来进入循环体,我们在栈不空时循环执行以下语句:

      1、出栈,记录栈顶参数x和当前执行位置cur_addr。

      2、如果cur_addr为0,说明该函数从头开始执行,我们执行第0段函数体。执行完毕后,我们先入栈{x, 1}(表示再退栈到该状态时应当执行第1段函数体了),仔把下一个函数的参数x+1、下一个函数的执行位置(0)入栈。之后continue进入下一层循环;

      3、如果cur_addr为1,说明该执行第二段函数体;这时我们先后入栈{x, 2}(返回到该状态时执行第2段函数体)、{x+1, 0}(下一层递归),继续循环。

      4、如果cur_addr为2,当前函数已执行完毕,不用再次入栈。继续循环即可。

    完整main函数代码:

    1. struct State {  
    2.     int x, addr;  
    3.     State(int a, int b):  
    4.         x(a), addr(b) {}  
    5. };  
    6. int main() {  
    7.     cin >> n >> m;   
    8.     stack<State> sta;  
    9.     sta.push(State(1, 0));//dfs(1);  
    10.     while (sta.size()) {  
    11.         int x = sta.top().x, cur_addr = sta.top().addr;   
    12.         sta.pop();  
    13.         switch (cur_addr) {  
    14.             case 0:  
    15.                 if (sel.size() > m || sel.size() + n - x + 1 < m)  
    16.                     continue;  
    17.                 if (x == n + 1) {  
    18.                     rep(0, m - 1)  
    19.                         printf("%d ", sel[i]);  
    20.                     puts("");  
    21.                     continue;  
    22.                 }  
    23.                 sel.push_back(x);  
    24.                 sta.push(State(x, 1));  
    25.                 sta.push(State(x+1, 0));   
    26.                 continue;  
    27.             case 1:  
    28.                 sel.pop_back();  
    29.                 sta.push(State(x+1, 0));  
    30.         }  
    31.     }   
    32.     return 0;  
    33. }  

      算法进阶上给出的代码与系统栈的返回方式更加契合,这里一并给出。

    1. #include <iostream>  
    2. #include <cstring>  
    3. #include <cstdio>  
    4. #include <vector>  
    5. using namespace std;  
    6. vector<int> chosen;  
    7. int top, address, sta[100010], n, m;  
    8. inline void call(int x, int ret_addr) {  //模拟系统栈指令call(),记录每个状态的参数和返回语句位置
    9.     int pre = top;  
    10.     sta[++top] = x;  
    11.     sta[++top] = ret_addr;  
    12.     sta[++top] = pre;  
    13. }  
    14. inline int ret() {  //模拟指令return,退栈并返回应该执行的下一条语句
    15.     int ret_addr = sta[top - 1];  
    16.     top = sta[top];  
    17.     return ret_addr;  
    18. }  
    19. int main() {  
    20.     cin >> n >> m;  
    21.     call(1, 0);  
    22.     while (top) {  
    23.         int x = sta[top - 2];  
    24.         switch (address) {  
    25.             case 0:  
    26.                 if (chosen.size() > m || chosen.size() + (n - x + 1) < m) {  
    27.                     address = ret();  
    28.                     continue;  
    29.                 }  
    30.                 if (x == n + 1) {  
    31.                     for (int i = 0; i < chosen.size(); ++i)  
    32.                         printf("%d ", chosen[i]);  
    33.                     puts("");  
    34.                     address = ret();  
    35.                     continue;  
    36.                 }  
    37.                 chosen.push_back(x);  
    38.                 call(x+1, 1);  //入栈下一个状态
    39.                 address = 0;  //下一个函数从头执行
    40.                 continue;  
    41.             case 1:  
    42.                 chosen.pop_back();  
    43.                 call(x+1, 2);  //入栈下一个状态
    44.                 address = 0;  
    45.                 continue;  
    46.             case 2:  
    47.                 address = ret();  //当前状态已执行完毕,返回
    48.         }  
    49.     }  
    50.     return 0;     
    51. }  

     

  • 相关阅读:
    软件开发模型
    Java学习--数组
    10年软件测试经验
    Log4net
    文件下载界面
    VB.NET中文双引号的处理方法
    SOAPUI使用
    .net数字转换成汉字大写
    SQL调用Webservices
    SQL大小写金额转换
  • 原文地址:https://www.cnblogs.com/TY02/p/11307274.html
Copyright © 2020-2023  润新知