本文章将讲解质数的判断方法、回文数的判断方法、以及某给定区间内回文质数的判断方法。不要问为什么写得如此粗制滥造,只因作者初次接触编程之广阔世界,能力有限,实 请 见 恕。
质数
一个整数n是否为质数可以通过以下步骤来判断:
- 判断n是否小于2,若小于2则不是质数
- 从2开始,到n-1为止,判断n是否能被其中任意一个数整除,若能,则不是质数
- 若在2到n-1范围内都没有找到能整除n的数,则n为质数
C语言实现:
int is_prime(int n) {
if (n < 2)
return 0;
for (int i = 2; i <= n - 1; i++){
if (n % i == 0)
return 0;
}
return 1;
}
回文数
回文数是指无论从左往右读还是从右往左读,所表示的数字都相同的数,例如101、1221、12321等。
判断回文数有两种方法,一种是拿第一位和最后一位作比较,如果相等,便比较第二位和倒数第二位…另一种是把这个数字倒过来,如果倒过来的数字和原来的数字相等,那么它便是一个回文数。
这里使用第二种方法,也就是先将一个整型数字 n 逆序输出,再拿这个新的数字 m 和原来的数字 n 作比较,如果二者相等,那么它便是一个回文数。
逆序输出
所以,第一步便是创造一个能够将整数逆序输出的函数。也就是将 n 的末尾放到 m 的首位,然后 n 除以 10 消除末位,直到 n 等于 0 为止:
int reverse_num(int n) {
int m;
for (m = 0; n > 0; n /= 10)
m = m * 10 + n % 10;
return m;
}
回文数判断
进一步地,便可以编写一个函数,判断 n 是不是回文数:
int judge_reverse_num(int n)
{
int m, number;
for (number = n, m = 0; number > 0; number /= 10)
m = m * 10 + number % 10;
if (n == m)
return 1;
else
return 0;
}
回文质数
接下来,尝试找出给定范围 [a,b] 内的所有回文质数(5<=a<b<=100000000),也就是一亿以内某个区间既是回文数又是质数的数字。
这看起来貌似只是把前面两个函数复制到一起,然后利用 for 循环一个一个判断:
#include<stdio.h>
int is_prime(int n) {
if (n < 2)
return 0;
for (int i = 2; i <= n - 1; i++) {
if (n % i == 0)
return 0;
}
return 1;
}
int judge_reverse_num(int n)
{
int m, number;
for (number = n, m = 0; number > 0; number /= 10)
m = m * 10 + number % 10;
if (n == m)
return 1;
else
return 0;
}
int main() {
int a, b;
scanf("%d%d", &a, &b);
if (a % 2 == 0)
a++;
for (a, b; a <= b; a++) {
if (is_prime(a) == 0)
continue;
if (judge_reverse_num(a) == 0)
continue;
printf("%d\n", a);
}
}
但 是 !
这样做虽然答案正确,却超 时 了 !
也就是说,这样判断过于低效,导致耗费的时间太多,我们需要优化自己的程序。
优化可以分为两部分进行:质数判断的优化和回文数判断的优化。
质数优化
判断一个数字是否是质数,真的要从 2 尝试到 n-1 吗?能不能减少范围?
假设 n 不是素数,则必然存在两个因子 a 和 b,使得 a*b=n。其中,a 和 b 至少有一个小于等于根号 n,另一个大于等于根号 n。如果不存在这样的 a 和 b,那么 n 就是一个素数。
假设有一个大于根号 n 的因子 c,那么另一个因子 d 必然小于根号 n,否则 c*d>n,与a和b作为两个因子的假设矛盾。因此,为了判断 n 是否是素数,我们只需要遍历到根号 n 就可以了,也就是说,如果在 2 到根号 n 之间找不到一个数能整除 n,那么 n 就是一个素数。这个方法比直接遍历到 n-1 要快得多,特别是当 n 很大的时候。
注意,求平方根的函数为 sqrt(n) ,需要头文件#include<math.h>:
int is_prime(int n) {
if (n < 2)
return 0;
int max = sqrt(n) + 1;
for (int i = 2; i <= max; i++) {
if (n % i == 0)
return 0;
}
return 1;
}
质数优化2
还可以进一步优化。比如,偶数一定不是质数,所以没有必要遍历所有的数字,只需要遍历所有的奇数就可以了,这可以省下一半的时间。
所以,我们将原来从a遍历到b的 for 循环:
for (a, b; a <= b; a++)
更改为如下:
if (a % 2 == 0)
a++;
for (a, b; a <= b; a += 2)
如果 a 是偶数,则 a 加一,之后每次 for 循环只遍历奇数。
回文数优化
然而还是会超时,于是我们需要针对回文数的性质进行优化。
我们发现,数字位数为偶数的回文数都是11的倍数!也就是说,它们一定不是质数。例如 11 、 44 、 77 等两位数,2332 等四位数…
我们完全没有必要去判断这些数字,而是可以直接跳过。方法有很多,因为此处数据小于一亿,所以主函数可以如下优化:
int main() {
int a, b;
scanf("%d%d", &a, &b);
if (a % 2 == 0)
a++;
for (a, b; a <= b; a += 2) {
if (a > 1000 && a < 9999)
a = 10001;
else if (a > 100000 && a < 999999)
a = 1000001;
else if (a > 10000000 && a < 99999999)
a = 100000001;
if (is_prime(a) == 0)
continue;
if (judge_reverse_num(a) == 0)
continue;
printf("%d\n", a);
}
}
回文数优化2
当然,这样进行判断显得过于…寒碜,而且仍旧不够快,远远不够。我们不妨转变思路,不是从所有的自然数中找出既是质数又是回文数的数字,而是直接从回文数中找出质数。
也就是说,从小到大地,我们直接制造回文数。
我们知道如下性质:
- 偶数位数的回文数不可能是质数,如123321。
- 最后一位是偶数的回文数不可能是质数。
- 1位数的大于等于5的回文质数显然只有5、7;2位数的回文质数有11。
综上所述,我们只需要生成3、5、7、9位的回文数即可:
#include<stdio.h>
#include<math.h>
//判断数字n是否是质数
int is_prime(int n) {
if (n < 2)
return 0;
int max = sqrt(n) + 1;
for (int i = 2; i <= max; i++) {
if (n % i == 0)
return 0;
}
return 1;
}
//主函数
int main() {
int a, b;
scanf("%d%d", &a, &b);
int palindrome = 0;
/*一百以内回文质数*/
if (5 >= a && 5 <= b)
printf("5\n");
if (7 >= a && 7 <= b)
printf("7\n");
if (11 >= a && 11 <= b)
printf("11\n");
/*生成三位数回文数*/
for (int d1 = 1; d1 <= 9; d1 += 2) {
for (int mid = 0; mid <= 9; mid++) {
palindrome = 100 * d1 + 10 * mid + 1 * d1;
if (is_prime(palindrome) == 1 && palindrome >= a && palindrome <= b)
printf("%d\n", palindrome);
}
}
/*生成五位数回文数*/
for (int d1 = 1; d1 <= 9; d1 += 2) {
for (int d2 = 0; d2 <= 9; d2++) {
for (int mid = 0; mid <= 9; mid++) {
palindrome = 10000 * d1 + 1000 * d2 + 100 * mid + 10 * d2 + 1 * d1;
if (is_prime(palindrome) == 1 && palindrome >= a && palindrome <= b)
printf("%d\n", palindrome);
}
}
}
/*生成七位数回文数*/
for (int d1 = 1; d1 <= 9; d1 += 2) {
for (int d2 = 0; d2 <= 9; d2++) {
for (int d3 = 0; d3 <= 9; d3++) {
for (int mid = 0; mid <= 9; mid++) {
palindrome = 1000000 * d1 + 100000 * d2 + 10000 * d3 + 1000 * mid + 100 * d3 + 10 * d2 + 1 * d1;
if (is_prime(palindrome) == 1 && palindrome >= a && palindrome <= b)
printf("%d\n", palindrome);
}
}
}
}
/*生成九位数回文数*/
for (int d1 = 1; d1 <= 9; d1 += 2) {//只有偶数才会是回文数,所以首位(末尾)必须是奇数
for (int d2 = 0; d2 <= 9; d2++) {
for (int d3 = 0; d3 <= 9; d3++) {
for (int d4 = 0; d4 <= 9; d4++) {
for (int mid = 0; mid <= 9; mid++) {
palindrome = 100000000 * d1 + 10000000 * d2 + 1000000 * d3 + 100000 * d4
+ 10000 * mid + 1000 * d4 + 100 * d3 + 10 * d2 + 1 * d1;
if (is_prime(palindrome) == 1 && palindrome >= a && palindrome <= b)
printf("%d\n", palindrome);
}
}
}
}
}
}
这样虽然代码麻烦了些,但是却能用最短的时间得到答案!可喜可贺,可喜可贺!
最后,总结一下算法中优化的部分:
- 判断n是否为质数,可以只遍历到n的平方根。
- 遍历区间寻找质数时,可以直接跳过所有的偶数。
- 数字位数为偶数的回文数一定是11的倍数,也就是说,一定不是质数。
- 与其遍历大区间(从 a 到 b 的所有自然数)判断两个条件(是否是回文数、是否是质数),不如遍历小区间(从 a 到 b 的所有回文数)判断一个条件(是否是质数)。