编程练习(续)
4.
质数就是只能被1和本身整除的整数。Eratosthenes筛选法是一种计算质数的有效办法。这个算法的第一步就是写下所有从2至某个上限之间的所有整数。在算法的剩余部分,你遍历整个列表并剔除所有不是质数的整数。
后面的步骤是这样的。找到列表中的第1个不被剔除的数(也就是2),然后将列表后面所有逢双的数都剔除,因为它们都可以被2整除,因此不是质数。接着,再回到列表的头部重新开始,此时列表中尚未被剔除的第一个数是3,所以在3之后把每逢第3个数(3的倍数)剔除。完成这一步之后,再回到列表开头,3后面的下一个数是4,但它是2的倍数,已经被剔除,所以将其跳过,轮到5,将所有5的倍数剔除。这样依次类推、反复进行,最后列表中未被剔除的数均为质数。
编写一个程序,实现这个算法,使用数组表示你的列表。每个数组元素的值用于标记对应的数是否剔除。开始时数组所有元素的值都设置为TURE,当算法要求“剔除”其对应的数时,就把这个元素设置为FALSE。如果你的程序运行于16位的机器上,小心考虑是不是需要把某个变量声明为long。一开始先使用包含1000个元素的数组。如果你使用字符数组,使用相同的空间,你将会比使用整数数组找到更多的质数。你可以使用下标来表示指向数组首元素和尾元素的指针,但你应该使用指针来访问数组元素。
注意除了2以外,所有的偶数都不是质数。稍微多想一下,你可以使程序的空间效率大为提高,方法是数组中的所有元素只对应奇数。这样,在相同的数组空间内,你可以寻找到的质数的个数大约是原先的两倍。
埃式筛选法:
(1)先把1删除(现今数学界1既不是质数也不是合数)
(2)读取队列中当前最小的数2,然后把2的倍数删去
(3)读取队列中当前最小的数3,然后把3的倍数删去
(4)读取队列中当前最小的数5,然后把5的倍数删去
(5)读取队列中当前最小的数7,然后把7的倍数删去
(6)如上所述直到需求的范围内所有的数均删除或读取
注:此处的队列并非数据结构队列,如需保留运算结果,处于存储空间的充分利用以及大量删除操作的实施,建议采用链表的数据结构。
程序如下:
/*以字符数组为存储单位且在数组中仅使用奇数的方法筛选质数*/
#include <stdio.h>
#define MAX 1001
void Eratosthenes(char []);
int main()
{
int i;
int count = 0; //计数器
char prime[1001]; //使用字符数组找到更多(多于整数数组的)质数
for(i = 1; i < MAX; i++)
prime[i] = 1;//起初所有数组元素值设为真
printf("the prime range is 2 to %d\n",MAX * 2 + 1);//质数范围为2~2003
Eratosthenes(prime);
printf("2\n"); //输出2(除2外所有偶数均非质数)
for(i = 1; i < MAX; i++){ //这里i作为数组下标
if(prime[i] == 1){
printf("%d ",i * 2 + 1); //第一次输出3
count++;
if(count % 15 == 0)//15组数字换一行
printf("\n");
}
}
}
void Eratosthenes(char prime[]) //埃式筛法(埃拉托色尼筛选法)
{
int i,j;
//0<i<1001;6<j<2003;i为数组中元素的大小,j为筛选质数的范围(即题干中的某个上限)
int i_p,j_p;//i_p为奇数,j_p为需剔除的下标
for(i = 1; i < MAX; i++){//每剔除一个数的倍数后,需回到列表的头部重新开始
if(prime[i] == 1){ //将数组中所有元素设为真
i_p = 2 * i + 1; //初始i_p=3
for(j = 2 * i_p; j < MAX * 2 + 1; j += i_p)
//去除所有奇数的倍数。j=6时不执行下一步的if,循环一次后j=9
if(j % 2 == 1){ //若j(奇数的倍数)为奇数(j=9)时执行
j_p = (j - 1)/2; //j_p=(9-1)/2=4
prime[j_p] = 0; //剔除下标为4的数组
}
}
}
}
/*在每一次剔除某奇数的倍数时很有可能会剔除掉一些质数,
比如在剔除3的倍数的过程中,会剔除掉7、13等质数,
所以这便需要在Eratosthenes函数的第一个if语句中将当前数字重新设为真*/
运行结果:
注:
在看这道题的程序时,涉及到算法,如若在理解的过程中略微吃力,建议可书面手写循环过程帮助理解。
另外在读到“如果你使用字符数组,使用相同的空间,你将会比使用整数数组找到更多的质数”题干中的这句话,还仔细地回顾了一下字符数组与整型数组的区别。
1.字符型数组中的每个元素占一个字节,而整型数组中的每个元素占四个字节,因此,在相同的空间下,使用字符数组便可以得到更多想要的结果;
2.字符型数组在字符串的最后面需要添加一个字符串结束符’\0’,而整型数组没有此规定。
之前使用的字符型数组通常用来存放字符型数据,这是因为字符型数据是以字符的ASCII代码存储在存储单元中的,一般占一个字节,而ASCII也属于整数形式,所以在C99标准中,字符类型也归属于整数类型。C语言中没有字符串类型,字符串是存放在字符型数组中的。
5.
修改前一题的Eratosthenes程序,使用位的数组而不是字符数组,这里要用到第5章编程练习中所开发的位数组函数。这个修改使程序的空间效率进一步提高,不过代价是时间效率降低。在你的系统中,使用这个方法,你所能找到的最大质数是多少?
程序如下:
#include<stdio.h>
#define CHAR_BIT 8
void set_bit(char bit_array[20],unsigned bit_number);
void clear_bit(char bit_array[],unsigned bit_number);
void assign_bit(char bit_array[],unsigned bit_number,int value);
int test_bit(char bit_array[],unsigned bit_number);
int main() {
int i ;
char arr[1000];
PrimeUseBit(arr, 1000);
printf("2");
for (i = 0; i < 8000; ++i) { // 循环次数需要更改
if (test_bit(arr,i)) { // 判断条件需要更改
printf(" %d", 2 * i + 3);
}
}
}
// 使用位数组
int PrimeUseBit(char *arr, int size) {
int i,curr,del;
if (arr == NULL) {
return;
}
size *= 8;
// 起初将所有元素值设为TRUE
for (i = 0; i < size; ++i) {
set_bit(arr, i);
}
// 剔除偶数,在对应的元素上置0
for (curr = 0; curr < size; ++curr) {
if (test_bit(arr,curr)) { // 该位置未被剔除,即参数中指定位非0则为真
int span = 2 * curr + 3;
for (del = curr + span; del < size; del += span) {
clear_bit(arr, del); // 剔除该位置
}
}
}
}
unsigned character_offset(unsigned bit_number)
{
return bit_number / CHAR_BIT;
} //将bit_number转换为字符数组中的元素
unsigned bit_offset(unsigned bit_number)
{
return bit_number % CHAR_BIT;
}
void set_bit(char bit_array[], unsigned bit_number)
{
bit_array[character_offset(bit_number)] |= 1 << bit_offset(bit_number);
}
void clear_bit(char bit_array[], unsigned bit_number)
{
bit_array[character_offset(bit_number)] &=~ (1 << bit_offset(bit_number));
}
void assign_bit(char bit_array[], unsigned bit_number, int value)
{
if(value != 0)
set_bit(bit_array,bit_number);
else
clear_bit(bit_array,bit_number);
}
int test_bit(char bit_array[], unsigned bit_number)
{
if(bit_array[character_offset(bit_number)] & 1 << bit_offset(bit_number) != 0)
return 1;
else
return 0;
}
运行结果:
关于位数组的代码注释可参考我《C与指针——操作符和表达式(二)》中的内容,在这里便不赘述了。这段代码的运行结果其实还是存在一定问题的,到运行结果的后面出现了一些非质数的数字混在其中。当然,我也花了不少时间找bug……待我日后时间充裕且豁然开朗之时再来修改。
6.
大质数是不是和小质数一样多?换句话说,在50 000和51 000之间的质数是不是和1 000 000到1 001 000之间的质数一样多?使用前面的程序计算0到1 000之间有多少个质数?1 000到2 000之间有多少个质数?以此每隔1 000类推,到1000000(或是你机器上允许的最大正整数)有多少个质数?每隔1 000个数中质数的数量呈什么趋势?
程序如下:
#include <stdio.h>
#define MAX 1000001
int Eratosthenes(char [], int max);
int main()
{
int i,max;
int sum = 0;
int count = 0;
char prime[MAX];
for(i = 1; i < MAX; i++)
prime[i] = 1;
for(max = 100000; max < MAX; max += 100000){
count = Eratosthenes(prime,max);
printf("%f\n",(float)max/count * 1000);
}//为看出其趋势,对每个区间的质数分别求其平均数
}
int Eratosthenes(char prime[], int max)
{
int count = 0;
int i,j;
int i_p,j_p;
for(i = 1; i < max; i++)
prime[i] = 1;
for(i = 1; i < max; i++){
if(prime[i] == 1){
i_p = 2 * i + 1;
for(j = 2 * i_p; j < max * 2 + 1; j += i_p)
if(j % 2 == 1 && prime[(j-1)/2] == 1){
j_p = (j - 1)/2;
prime[j_p] = 0;
count++;
}
}
}
return count;
}
运行结果:
这个运行结果,看上去也像是有一定规律存在的,对这道题的后续探索,就留给对数学感兴趣的网友啦。
这一章节中的挺多程序还是非常值得好好研究的,也确实花了我不少时间,甚至一整天只为思索一个bug,睡前闭眼脑海里都是指针在指来指去。日后这部分内容会进行相应的回顾,这篇博客日后也是要进行相应的完善,毕竟有些知识点现在还未完全掌握。
最近身边的氛围过于焦躁
就连空气中都悬浮着不安与彷徨
也对,大抵该是拨开迷雾的时候了
近期内会收心准备竞赛
《C与指针》就等忙完了这一阵子再继续吧
嗯,用心对待,一期一会