递归程序打印所有小于N的仅由数字1或3组成的数字

1.递归程序打印所有小于N的仅由数字1或3组成的数字

在计算机科学中,递归是一个经常使用的编程技巧,递归程序可以让程序员不使用循环结构达到循环的效果。

下面的问题可以使用递归程序解决:打印所有小于N的仅由数字1或3组成的数字。例如,当N=10时,需要输出1, 3, 和9。

1.1 递归程序思路

编写递归程序时需要考虑两个方面:

问题的基本情况

将问题转化成一个或多个子问题,递归地求解子问题,并将子问题的解合并得到原问题的解。

对于本问题,我们可以将问题转化成两个子问题:

所有小于(N-1)的仅由数字1或3组成的数字。

如果N本身是仅由数字1或3组成的数字,则输出N。

该递归程序思路可以用下面的伪代码表示:

void printNumbers(int N)

{

if (N > 0)

{

printNumbers(N-1);

if (isOneOrThree(N))

{

cout << N << '\n';

}

}

}

bool isOneOrThree(int N)

{

while (N > 0)

{

int digit = N % 10;

if (digit != 1 && digit != 3)

{

return false;

}

N /= 10;

}

return true;

}

1.2 递归程序解释

在上面的伪代码中,函数isOneOrThree判断一个数字是否仅由数字1或3构成。该函数在递归函数printNumbers的内部定义,并且该函数使用了while循环结构,而不是递归函数。

函数printNumbers的第一行代码检查N是否大于0。如果N为0或负数,则函数返回,不进行任何操作。

否则,函数通过递归调用自己(即调用printNumbers(N - 1))解决了所有小于(N-1)的仅由数字1或3组成的数字。当然,如果N=1或3,函数printNumbers(N - 1)不会输出任何内容,不过也无伤大雅。这是问题的基本情况。

接下来,如果N本身是仅由数字1或3组成的数字,则输出N。函数isOneOrThree可以完成该任务。这是将问题分解成子问题(即函数isOneOrThree),并将子问题的解合并得到原问题的解(即输出N)的过程。

1.3 递归程序样例

下面是一个使用C++编写的递归程序,可以打印所有小于N的仅由数字1或3组成的数字。

#include <iostream>

using namespace std;

bool isOneOrThree(int N);

void printNumbers(int N)

{

if (N > 0)

{

printNumbers(N-1);

if (isOneOrThree(N))

{

cout << N << '\n';

}

}

}

bool isOneOrThree(int N)

{

while (N > 0)

{

int digit = N % 10;

if (digit != 1 && digit != 3)

{

return false;

}

N /= 10;

}

return true;

}

int main()

{

int N = 10;

cout << "Numbers less than " << N << " consisting of only 1s and 3s:\n";

printNumbers(N);

return 0;

}

输出结果为:

Numbers less than 10 consisting of only 1s and 3s:

1

3

9

2.递归程序的时间复杂度

递归程序的时间复杂度是调用子函数的次数乘以单次调用所需时间。下面我们将分别分析递归程序打印所有小于N的仅由数字1或3组成的数字的时间复杂度。

2.1 时间复杂度分析(函数printNumbers)

函数printNumbers调用了自身N次,且每次调用函数printNumbers的时间复杂度是O(1)。因此,函数printNumbers的时间复杂度是O(N)。

2.2 时间复杂度分析(函数isOneOrThree)

函数isOneOrThree的时间复杂度是O(log N),其中log N是数字N的位数。

函数isOneOrThree使用了while循环,遍历了数字N的每一位。因为一个正整数N的位数是log10N+1(例如,当N=1000时,N的位数是4,即log101000+1=4),所以函数isOneOrThree的时间复杂度是O(log N)。

3.优化递归程序以提高性能

递归程序的时间复杂度可能很高,因此,优化递归程序以提高性能是一种很好的编程实践。

3.1 递归终止条件

递归程序的终止条件可以影响程序的性能。如果递归程序的终止条件在递归深度很大的时候才被满足,程序的时间复杂度将变得很高。

在本问题中,我们可以改进递归终止条件。例如,我们可以改变函数isOneOrThree的定义,使其成为递归函数,并且在采用位运算检查N的每一位时同时检查N的值是否是1或3。这样,当N本身不是由数字1或3构成时,立即返回false,而不是等到检查完N的每一位之后才返回false。这种改进可以大大减少递归深度,提高程序的性能。

下面是改进后的函数isOneOrThree的C++代码:

bool isOneOrThree(int N)

{

if (N == 1 || N == 3)

{

return true;

}

else if (N <= 0 || (N % 10 != 1 && N % 10 != 3))

{

return false;

}

else

{

return isOneOrThree(N / 10);

}

}

3.2 缓存子问题的解

递归程序可能会重复计算相同的子问题,导致程序效率低下。为了避免这种情况,我们可以使用缓存(或称为“记忆化”)技术,缓存子问题的解。

在本问题中,我们可以建立一个数组cache,用于缓存已经计算过的数字。缓存技术的使用可以大大减少递归程序的执行时间,但是需要占用额外的存储空间。

下面是改进后的函数printNumbers的C++代码:

#include <iostream>

#include <vector>

using namespace std;

bool isOneOrThree(int N);

void printNumbers(int N, vector<bool>& cache)

{

if (N > 0 && !cache[N])

{

printNumbers(N-1, cache);

if (isOneOrThree(N))

{

cout << N << '\n';

}

cache[N] = true;

}

}

bool isOneOrThree(int N)

{

if (N == 1 || N == 3)

{

return true;

}

else if (N <= 0 || (N % 10 != 1 && N % 10 != 3))

{

return false;

}

else

{

return isOneOrThree(N / 10);

}

}

int main()

{

int N = 10;

vector<bool> cache(N+1, false);

cout << "Numbers less than " << N << " consisting of only 1s and 3s:\n";

printNumbers(N, cache);

return 0;

}

输出结果与之前的程序相同:

Numbers less than 10 consisting of only 1s and 3s:

1

3

9

4.总结

递归程序是一个常见的编程实践技巧,在本文中,我们使用递归程序打印所有小于N的仅由数字1或3组成的数字。

在程序编写过程中,我们学到了递归程序的思路,如何分解问题成子问题,并将子问题的解合并得到原问题的解。

我们还分析了递归程序的时间复杂度,包括函数printNumbers和函数isOneOrThree的时间复杂度分析。

最后,我们对递归程序进行了优化,包括递归终止条件和缓存技术的使用,以提高程序的性能。

递归程序毫无疑问是一个非常强大的工具,然而,当递归层数很深时,它的性能很容易变得很差。在实践中,适当的情况下应该优先选择迭代程序,而不是递归程序。

后端开发标签