C与指针——指针(三)

编程练习(续)

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与指针》就等忙完了这一阵子再继续吧
嗯,用心对待,一期一会

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值