一个递归转循环的模式

有些递归是很容易转化成循环的,用一个循环非常直观的映射过去就是了,如求Fibonacci数列; 而有些递归却没有那么直观,甚至可能需要多个循环才能转化过去,这里举个例子:

给出一个集合,如(1, 2, 3, 4),打印出该集合的所有子集

分析一下问题,子集是指取原集合中的任意多个元素,转化一下问题,就是对于原集合中的任何一个元素,我们都有两个选择,包含或者不包含,所以对于n个元素的集合,其子集数为: 2*2*2... = 2^n。那么可以得出其递归算法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void Recursive_Subsets(int* a, bool* b, int start, int end)
{
    if (start <= end)
    {
        // determine current number's status, which has 2 choices
        // and then continue with left ones
        b[start] = true;
        Recursive_Subsets(a, b, start+1, end);
 
        b[start] = false;
        Recursive_Subsets(a, b, start+1, end);
    }
    else
    {
        for (int i = 0; i <= end; i++)
        {
            if (b[i]) cout << a[i];
        }
        cout << endl;
    }
 
}
 
void PrintAllSubsets1(int* a, int n)
{
    bool* b = new bool[n];
    Recursive_Subsets(a, b, 0, n-1);
    delete b;}

递归的优点是直观、易懂:写起来如此,读起来也是这样。但是每次递归都是call stack的不断叠加,对于这个问题,其需要消耗O(n)的栈空间,栈空间,栈空间~~~

于是,我们也可以将其转化成循环的方式来实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
void PrintAllSubsets2(int* a, int n)
{
    // Initialize flags as false
    bool* b = new bool[n];
    for(int i = 0; i < n; i++)
    {
        b[i] = false;
    }
 
    // Use 2 loops to enumerate all possible combinations of (each item's status * number of items),
    // in this case: ([true|false] * size of set)
    while(true)
    {
        // Get one solution, output it!
        for(int i = 0; i < n; i++)
        {
            if(b[i]) cout << a[i];
        }
        cout << endl;
 
 
        // One of the number's status has switched, start over enumeration for that!
        // ex: I have following enumeration for 3 numbers:
        //     0, 0, 0
        //     0, 0, 1
        //     0, 1, 0
        //     0, 1, 1
        // now if I changed the first number's status from 0 to 1, I need to re-enumerate the other 2 numbers
        // to get all possible cases:
        //     1, 0, 0
        //     1, 0, 1
        //     1, 1, 0
        //     1, 1, 1
        int k = n - 1;
 
        while(k >= 0)
        {
            if(b[k] == false)   // have we tried all possible status of k?
            {
                b[k] = true;    // status switch, as we only have 2 status here, I use a boolean rather than an array.
                break;          // break to output the updated status, and further enumeration for this status change, just like a recursion
            }
            else                // We have tried all possible cases for k-th number, now let's consider the previous one
            {
                b[k] = false;   // resume k to its initial status
                k--;            // let's consider k-1
            }
        }
 
        if(k < 0) break;     // all the numbers in the set has been processed, we are done!
    }
 
    // clean up
    delete [] b;
}

详细如何转换在注释中已经说明,对于这类穷举状态的递归程序,我们可以将这个程序的框架当做一个模式,照样子来转化即可。

比如在《编程之美》中有一个电话号码对应英语单词的题,其非递归版本的实现也是这样的模式。

另外,对于递归感兴趣的同学可以读读这两篇文章:

An Introduction to Recursion, Part 1

An Introduction to Recursion, Part 2

posted @   lzprgmr  阅读(10704)  评论(8编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· [AI/GPT/综述] AI Agent的设计模式综述

黄将军

点击右上角即可分享
微信分享提示