一个递归转循环的模式
有些递归是很容易转化成循环的,用一个循环非常直观的映射过去就是了,如求Fibonacci数列; 而有些递归却没有那么直观,甚至可能需要多个循环才能转化过去,这里举个例子:
给出一个集合,如(1, 2, 3, 4),打印出该集合的所有子集
分析一下问题,子集是指取原集合中的任意多个元素,转化一下问题,就是对于原集合中的任何一个元素,我们都有两个选择,包含或者不包含,所以对于n个元素的集合,其子集数为: 2*2*2... = 2^n。那么可以得出其递归算法:
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)的栈空间,栈空间,栈空间~~~
于是,我们也可以将其转化成循环的方式来实现:
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; }
详细如何转换在注释中已经说明,对于这类穷举状态的递归程序,我们可以将这个程序的框架当做一个模式,照样子来转化即可。
比如在《编程之美》中有一个电话号码对应英语单词的题,其非递归版本的实现也是这样的模式。
另外,对于递归感兴趣的同学可以读读这两篇文章: