测试素数程序
我正在尝试用 JS 编写一个程序,该程序接受输入并使用递归测试它是否为素数。
在我的代码中,我创建了函数
isPrime
。作为我的“基础”,如果
x==1
,则返回
false
,如果
x==2
,则返回
true
,因为
2
是第一个素数。
之后,我有一个
if
语句,用于测试
x
是否为素数。
但是,当我执行代码时,我的控制台返回
Uncaught RangeError: Maximum call stack size reached
。
我不确定程序返回错误代码的具体原因。
let x = prompt("Enter a number to test as a prime number: ");
let result = isPrime(x, 2);
if (result) {
alert("x is prime");
} else {
alert("x is not prime");
}
function isPrime(number, divisor) {
if (number == 1) {
return false;
} else if (number == 2) {
return true;
} else {
return isPrime(number, ++divisor);
}
if (number % divisor == 0) {
return false;
} else if (divisor ** 2 >= number) {
return true;
} else {
return isPrime(number, ++divisor);
}
}
当前代码存在问题
您的函数存在两个实际问题。首先,您有一堆无法访问的代码:
function isPrime(...) {
if (...) {
return false
} else if (...) {
return true
} else {
return isPrime(...)
}
// Everything from here down cannot be reached. You already returned from this
// function in one branch or another in the block above.
}
其次,尽管您确实正确地包含了基本情况,但您从未朝着它们前进:
function isPrime(number, divisor) {
if (number == 1) { // Base case, testing on `number`
return false;
} else if (number == 2) { // Base case, testing on `number`
return true;
} else {
return isPrime(number, ++divisor); // Recursive case, doesn't change `number`
}
// Even if you could reach this section, it has the same problem.
}
要使递归起作用,您的递归情况必须以某种方式朝着基本情况前进。进展可能很复杂,但如果您无法证明每个连续调用都以某种方式更接近基本情况,那么您甚至不知道您的程序是否可以完成,更不用说它是否正确了。
为什么选择递归?
但我有一个更基本的问题。您为什么要在这里使用递归?这对您来说是一项学习练习,是家庭作业还是您自己的个人学习?如果是这样,那很好。这是一个完全合法的学习问题。
但递归和迭代同样强大。很容易证明,任何你能用一个完成的事情,你都可以用另一个完成。因此,选择通常归结为你的数据结构是否更适合递归或迭代。对于许多结构来说,递归显然是最佳选择。例如,如果你正在处理树结构,递归几乎总是更干净、更优雅。(性能是一个单独的问题,将涉及尾部递归之类的问题。)如果你正在处理完整的项目列表,递归或迭代可能更好。
但这里的问题是一个寻找任何除数的问题,并在第一个除数处停止。您可以使用简单的迭代来实现这一点: 它能被 2 整除吗? , 它能被 3 整除吗? , 它能被 4 整除吗? ,...,在第一次 是 时停止。递归并没有增加任何使它特别清晰的东西。
(请注意,一个重要的性能改进是,只要我们通过目标值的平方根,就立即停止,因为如果有因数,其中一个必须小于平方根。)
递归解决方案
话虽如此,我们当然 可以 使用递归来实现这一点。我可能会这样写:
const isPrime = (n, d = 2) =>
d * d > n
? true
: n % d == 0
? false
: isPrime (n, d + 1)
console .log (isPrime (40)) //=> false
console .log (isPrime (41)) //=> true
或者,按照您当前代码的样式,
function isPrime(n, d = 2) {
if (d * d > n) {
return true;
} else if (n % d == 0) {
return false;
} else {
return isPrime(n , d + 1)
}
}
并且,虽然我们 也可以 将其写为
const isPrime = (n, d = 2) =>
d * d > n || (n % d !== 0 && isPrime (n, d + 1))
我认为这使理解递归变得更加困难。
一个更好的递归练习问题
最后,如果这是为了帮助您学习递归而设计的,那么让我建议一个相关问题,其中递归更可能合适。编写一个函数来查找
n
的所有素因数:
primeFactors(17) //=> [17]
primeFactors(18) //=> [2, 3, 3]
primeFactors(19) //=> [19]
primeFactors(20) //=> [2, 2, 5]
primeFactors(55440) //=> [2, 2, 2, 2, 3, 3, 5, 7, 11]
同样,这可以迭代或递归地完成,但我认为递归解决方案可能是更好的代码。
此处的逻辑代码用于检查数字是否为素数
let x = prompt("Enter a number to test as a prime number: ");
let result = isPrime(x);
if (result) {
alert("x is prime");
} else {
alert("x is not prime");
}
function isPrime(n)
{
if (n===1)
{
return false;
}
else if(n === 2)
{
return true;
}
else
{
for(var x = 2; x < n; x++)
{
if(n % x === 0)
{
return false;
}
}
return true;
}
}
在您的代码中
if (number == 1) {
return false;
} else if (number == 2) {
return true;
} else {
return isPrime(number, ++divisor); // this is infinite recursion acurring
}
要了解原因,一个好的做法是记录传递给函数的参数并限制递归。尝试使用较小的
x
(例如 10),然后思考发生了什么。
var iterations = 20
let x = prompt("Enter a number to test as a prime number: ");
let result = isPrime(x, 2);
if (result) {
alert("x is prime");
} else {
alert("x is not prime");
}
function isPrime(number, divisor) {
console.log(JSON.stringify(Array.from(arguments)));
// stop infinite recursion
if (iterations-- < 0)
return;
if (number == 1) {
return false;
} else if (number == 2) {
return true;
} else {
return isPrime(number, ++divisor);
}
if (number % divisor == 0) {
return false;
} else if (divisor ** 2 >= number) {
return true;
} else {
return isPrime(number, ++divisor);
}
}