问题:
- 不超过2000的素数有哪些?
- 她的QQ号与手机号相加以后是素数吗?
解决:
已知:
- 1不是素数
- 2是素数
- 大于2的偶数不是素数
- 大于2的素数是奇数
- 当自然数k > 1时,素数的k倍数不是素数
策略:
- 采用大型布尔数组,其元素取值只有0(FALSE_FLAG)和1(TRUE_FLAG)两种
- 用大型布尔数组中下标为i的位置代表奇数(2*i + 1)
- 断定(2*i + 1)为素数当且仅当在大型布尔数组中下标为i的位置的元素的值为1(TRUE_FLAG)
- 引入全局布尔数组,以规避在程序运行中多处重复生成全局布尔数组的子集的情形
编码:
头文件:prime_number.h
1 #ifndef PRIME_NUMBER_H 2 #define PRIME_NUMBER_H 3 4 typedef enum { FALSE_FLAG, TRUE_FLAG } BOOLEAN_FLAG; // 当作布尔类型来用 5 typedef unsigned long long COUNT, PRIME_INTEGER; // 制定素数数组的下标数据类型和元素数据类型 6 7 typedef struct { 8 BOOLEAN_FLAG *boolean_flag_array; // 可变容的布尔数组 9 COUNT count; // 布尔数组当前的元素总个数 10 COUNT use_count; // 现场需要用到的布尔数组元素总个数(潜在约束:use_count <= count) 11 }BOOLEAN_FLAG_ARRAY; // 大型布尔数组 12 13 /************************************全局常量的声明************************************/ 14 15 extern const BOOLEAN_FLAG_ARRAY 16 EMPTY_BOOLEAN_FLAG_ARRAY; // 空BOOLEAN_FLAG_ARRAY 17 18 extern BOOLEAN_FLAG_ARRAY 19 GLOBAL_BOOLEAN_FLAG_ARRAY; // 全局唯一且最大的BOOLEAN_FLAG_ARRAY,用来避免在多处重复生成序列的小端 20 21 extern const char 22 *PRINT_FORMAT_STRING_OF_PRIME_INTEGER_TYPE, // 素数数组的元素的输出格式符 23 *PRINT_FORMAT_STRING_OF_COUNT_TYPE; // 素数数组的下标的输出格式符 24 25 /**************************************方法的声明**************************************/ 26 27 /*获取布尔数组,其最大下标表示的奇数不超过upper_limit*/ 28 BOOLEAN_FLAG_ARRAY getBooleanFlagArrayFor(PRIME_INTEGER upper_limit); 29 30 /*格式化地打印upper_limit以内的全部素数*/ 31 void printPrimeIntegerArrayBellow(PRIME_INTEGER upper_limit); 32 33 /*判断一个给定的数是否为素数*/ 34 BOOLEAN_FLAG isPrimeInteger(PRIME_INTEGER n); 35 36 #endif
源文件:prime_number.c
1 #include "prime_number.h" 2 #include <stdio.h> 3 #include <stdlib.h> 4 5 /************************************全局常量的定义************************************/ 6 7 const BOOLEAN_FLAG_ARRAY 8 EMPTY_BOOLEAN_FLAG_ARRAY = { NULL, 0, 0 }; // 空BOOLEAN_FLAG_ARRAY 9 10 BOOLEAN_FLAG_ARRAY 11 /*全局的BOOLEAN_FLAG_ARRAY*/ 12 GLOBAL_BOOLEAN_FLAG_ARRAY = { NULL, 0, 0 }; // 先初始化为空BOOLEAN_FLAG_ARRAY 13 14 const char 15 *PRINT_FORMAT_STRING_OF_PRIME_INTEGER_TYPE = "%3llu", // 制定素数数组的元素的输出格式符 16 *PRINT_FORMAT_STRING_OF_COUNT_TYPE = "%02llu"; // 制定素数数组的下标的输出格式符 17 18 /**************************************方法的定义**************************************/ 19 20 /*修剪BOOLEAN_FLAG_ARRAY末尾的冗余段*/ 21 BOOLEAN_FLAG_ARRAY trimBooleanFlagArray(BOOLEAN_FLAG_ARRAY bfa) { 22 /*释放boolean_flag_array末尾可能存在的连续多个FALSE_FLAG*/ 23 int i = bfa.count; 24 while (bfa.boolean_flag_array[--i] == FALSE_FLAG); 25 /* 26 **若boolean_flag_array末尾连续FALSE_FLAG的数目在整个布尔数组中占比超过20%, 27 **那么就释放掉boolean_flag_array末尾连续FALSE_FLAG,缩减内存占用 28 */ 29 if ((bfa.count - ++i) != 0 && bfa.count / (bfa.count - i) < 5) { 30 /*修剪BOOLEAN_FLAG_ARRAY末尾的冗余段*/ 31 bfa.boolean_flag_array = (BOOLEAN_FLAG *)realloc(bfa.boolean_flag_array, (i) * sizeof(BOOLEAN_FLAG)); 32 bfa.use_count = (bfa.count = i); 33 } 34 return bfa; 35 } 36 37 /*获取布尔数组,其最大下标表示的奇数不超过upper_limit*/ 38 BOOLEAN_FLAG_ARRAY 39 getBooleanFlagArrayFor(PRIME_INTEGER upper_limit) { 40 /*不存在小于2的素数*/ 41 if (upper_limit < 2) { 42 return EMPTY_BOOLEAN_FLAG_ARRAY; 43 } 44 /* 45 **upper_limit以内至多有max_count个奇数, 46 **且其中的第一个奇数(用下标为0的位置代表)是1,已知1不是素数, 47 **于是相应地开辟含有max_count个位置的boolean_flag_array, 48 **且boolean_flag_array的第一个位置仅起占位作用, 49 **在1以后的奇数序列为3, 5, 7, 9, 11, 13 ... 50 **依次用下标为1, 2, 3, 4, 5, 6 ...的位置来代表 51 **这样的话,下标为i的位置代表的奇数就是(2*i + 1) 52 **下标为i的位置的BOOLEAN_FLAG值就用来指示该位置代表的奇数是否为素数 53 */ 54 COUNT max_count = (upper_limit + 1) / 2; 55 /*若现成的GLOBAL_BOOLEAN_FLAG_ARRAY已足够大,就直接拿来用*/ 56 if (max_count <= GLOBAL_BOOLEAN_FLAG_ARRAY.count) { 57 GLOBAL_BOOLEAN_FLAG_ARRAY.use_count = max_count; // 实际要用多少个元素 58 return GLOBAL_BOOLEAN_FLAG_ARRAY; 59 } 60 /*若现成的GLOBAL_BOOLEAN_FLAG_ARRAY不够大,那就得另起炉灶*/ 61 BOOLEAN_FLAG *boolean_flag_array = (BOOLEAN_FLAG *)malloc(max_count * sizeof(BOOLEAN_FLAG)); 62 COUNT i = 1, j; 63 while (i < max_count) { 64 /*先把第一个元素(代表奇数1)之后的所有位置标记为TRUE_FLAG*/ 65 boolean_flag_array[i++] = TRUE_FLAG; 66 } 67 /* 68 **(对于下面这块注释,假设k是正整数,约定“设否”即“标记为FALSE_FLAG”的意思) 69 **下标为1的位置代表的奇数是1*2+1=3,3是素数,那就把它后面所有代表3的奇数倍数的奇数的位置设否 70 **也就是要设否下标为1 + k*(2*1+1) = 4, 7, 10 ...的位置, 71 **而这些位置代表的奇数依次是2*4+1=9, 2*7+1=15, 2*10+1=21 ...,都是素数奇数3的奇数倍; 72 **下标为2的位置代表的奇数是2*2+1=5,5是素数,那就把它后面所有代表3的奇数倍数的奇数的位置设否; 73 **依此进行下去, 74 **boolean_flag_array中的下一个位置i若尚未被设否,那么该位置代表的奇数(2*i+1)必定是素数, 75 **就把其后下标为i+k*(2*i+1)的代表它的奇数(大于1的奇数)倍数的奇数的位置设否; 76 **boolean_flag_array中的下一个位置i若已经被设否,那么该位置代表的奇数(2*i+1)必定是合数, 77 **且其后代表它的最小质因数的奇数倍数的奇数的位置都必定已经被设否了,那就直接挪到下一个位置i+1去看看, 78 **如此循环着进行下去,逐步把max_count个奇数中的所有合数对应的位置设否 79 */ 80 for (i = 1; i < max_count; ++i) { 81 if (boolean_flag_array[i] == FALSE_FLAG) { 82 continue; // 它的最小质因数的所有奇数(大于1的奇数)倍数都必定已经被设否了 83 } 84 for (j = i; (j += 2 * i + 1) < max_count; ) { 85 /*(k>1), 素数的k倍数必然是合数,故而把对应的位置都设否*/ 86 boolean_flag_array[j] = FALSE_FLAG; 87 } 88 } 89 /*释放掉原来的那个较小的GLOBAL_BOOLEAN_FLAG_ARRAY*/ 90 free(GLOBAL_BOOLEAN_FLAG_ARRAY.boolean_flag_array); 91 /*把新生成的这个大的进行一下尾部修剪,然后拿来更新全局布尔数组*/ 92 return GLOBAL_BOOLEAN_FLAG_ARRAY = trimBooleanFlagArray((BOOLEAN_FLAG_ARRAY) { boolean_flag_array, max_count, max_count }); 93 } 94 95 /*以“PrimeIntegerArray(index) = prime_integer”并换行的格式打印素数元素*/ 96 void 97 printPrimeIntegerElement(COUNT index, PRIME_INTEGER prime_integer) { 98 printf("PrimeIntegerArray("); 99 printf(PRINT_FORMAT_STRING_OF_COUNT_TYPE, index); 100 printf(") = "); 101 printf(PRINT_FORMAT_STRING_OF_PRIME_INTEGER_TYPE, prime_integer); 102 printf(" "); 103 } 104 105 /*格式化地打印upper_limit以内的全部素数*/ 106 void 107 printPrimeIntegerArrayBellow(PRIME_INTEGER upper_limit) { 108 /*无*/ 109 if (upper_limit < 2) { 110 printf("EMPTY_BOOLEAN_FLAG_ARRAY "); 111 return; 112 } 113 /*至少有一个*/ 114 printPrimeIntegerElement(0, 2); 115 if (upper_limit == 2) { 116 return; 117 } 118 /*有好多个呢*/ 119 BOOLEAN_FLAG_ARRAY bfa = getBooleanFlagArrayFor(upper_limit); 120 COUNT i = 1, j = 1; 121 while (i < bfa.use_count) { 122 if (bfa.boolean_flag_array[i] == TRUE_FLAG) { 123 /*这里的i是boolean_flag_array数组下标,它的特性如下: 124 **(1)在boolean_flag_array数组中下标为i的位置代表的奇数就是(2*i + 1) 125 **(2)断定(2*i + 1)为素数当且仅当在boolean_flag_array数组中下标为i的位置的BOOLEAN_FLAG为TRUE_FLAG 126 127 **而这里的j是要输出的素数数组的下标 128 **只有确实输出了一个素数,j才加1 129 */ 130 printPrimeIntegerElement(j++, 2 * i + 1); 131 } 132 ++i; 133 } 134 } 135 136 /*判断一个给定的数是否为素数*/ 137 BOOLEAN_FLAG 138 isPrimeInteger(PRIME_INTEGER n) { 139 /*已知2是素数*/ 140 if (n == 2) { 141 return TRUE_FLAG; 142 } 143 /*小于2的数以及大于2的偶数都不是素数*/ 144 if (n < 2 || n % 2 == 0) { 145 return FALSE_FLAG; 146 } 147 /* 148 **大于2的奇数在boolean_flag_array中对应的位置的下标为(n - 1) / 2 149 **该位置的BOOLEAN_FLAG值就指示着对应的奇数是否确实为素数 150 **在getBooleanFlagArrayFor这个函数退出之前剔除了boolean_flag_array尾部的若干FALSE_FLAG, 151 **这些被剔除的位置将无法访问,但知道它们的值是FALSE_FLAG 152 */ 153 BOOLEAN_FLAG_ARRAY bfa = getBooleanFlagArrayFor(n); 154 COUNT index = (n - 1) / 2; 155 return (index < bfa.count) ? bfa.boolean_flag_array[index] : FALSE_FLAG; 156 }
源文件:main.c
1 #include "prime_number.h" 2 #include <stdio.h> 3 4 int main(int argc, char **argv) { 5 6 printPrimeIntegerArrayBellow(200); 7 printf("isPrimeInteger(65537) = %s ", isPrimeInteger(65537) ? "Yes" : "No"); 8 9 return 0; 10 }
运行:
Microsoft Visual Studio Enterprise 2017 version 15.6.6 on Windows 10 Pro 1709
GCC version 7.3.1 on Manjaro Linux