zoukankan      html  css  js  c++  java
  • 小小c#算法题 2 求素数

    题目:(a) 求1~n内的所有素数。

             (b) 找出一个无序的整数数组内的所有素数。

    这两个题目是同样的解法,只不过形式变了一下。这里只对(a)给出解法,(b)类似。

    (1)第一种解法

    首先明白什么是素数,素数也叫质数,就是只能被1和它本身整除的整数。像1,2,3,5,7都是素数。

    明白了素数的概念,那么程序就很容易写出来了。要想知道一个数x是否是素数,那么只需要把1~x之间的所有整数来除x,如果存在这么一个数(1和x除外),其能够整除x(余数为0),那么x就不是素数,否则x就是素数。这是从概论出发的解法。那么下面就要看怎么优化了。

    其实我们没有必要检查1~x之间的所有整数。只需检查1~[根号x]之间的数就可以了。至于为什么,可以从乘法的角度理解,我们在做除法,其实反过来也就是乘法。我们用判断15是否是素数的情况来举个例子。当我们除3的时候,3*5=15。所以,在判断3的同时也判断了另一个数5。所以我们只需要检查到根号x就可以了,当检查到根号x的时候,根号x之后也检查过了。

    代码(c#): 

            private static void PrintPrimes(int n)
            {

                //输出1~n之间的所有素数,n>=3
                Console.Write("1 2 ");
                int i, j = 0;
                for (i = 3; i <= n; i = i + 2)
                {
                    int k = (int)Math.Sqrt(i);
                    for (j = 2; j <= k; j++)
                    {
                        if ((i % j) == 0)
                        {
                            break;
                        }
                    }

                    if (j > k)
                    {
                        Console.Write(i.ToString() + " ");
                    }
                }
            }

    注意for循环里递增的间隔是2,而不是i++。因为偶数肯定不是素数,这样也在一定程度上提高了效率。

    (2)第二种解法

    由于1~n是有序的且递增的,所以可以有这么一种解法:建立一个标识数组,长度为n,下标与1~n每个数对应,在每次判断完一个数是否是素数之后,将该数对应的标识位重置,0为素数,1为非素数,并且将这个数的所有倍数的标识位置1,这里其实就是剔除一些重复判断。最后遍历标识数组,输出所有素数。但这样做的代价是额外开辟了一段内存做为标识数组。当然,如果原数组不需要保存的话,你也可以直接在原数组上进行操作。

    听起来不错,但其实比第一种方法快不了多少。通过用Stopwatch看运算时间,这种解法并不怎么样,只有在数据量比较大的时候,其运行效率会比上一种方法要高那么一点点。我想是因为2的倍数太多了(方法一种循环步增是2),所以方法一已经剔除了相当一部分,再有,为每个素数计算倍数,然后再为标识数组赋值,也需要时间。

    代码(c#):

            private static void PrintPrimes2(int n)
            {
                int[] flags = new int[n];
                int j = 2;
                for (int i = 2; i <= n; i++)
                {
                    if (flags[i - 1] == 0)
                    {
                        if (!IsPrime(i))
                        {
                            flags[i - 1] = 1;
                        }
    
                        if (i <= n / 2)
                        {
                            j = 2;
                            while (i * j < n)
                            {
                                flags[i * j - 1] = 1;
                                j++;
                            }
                        }
                    }
                }
    
                for (int k = 1; k < n; k++)
                {
                    if (flags[k] == 0)
                    {
                        Console.Write((k + 1).ToString() + " ");
                    }
                }
            }
    
            private static bool IsPrime(int x)
            {
                if (x < 1)
                {
                    throw new ArgumentException("Cannot be less than 1");
                }
    
                if (x < 3)
                {
                    return true;
                }
                else
                {
                    int k = (int)Math.Sqrt(x);
                    int i = 2;
                    for (; i <= k; i++)
                    {
                        if (x % i == 0)
                        {
                            break;
                        }
                    }
    
                    if (i > k)
                    {
                        return true;
                    }
                    else
                    {
                        return false;
                    }
                }
            }


     (3)第三种解法

    如果能事先构造好素数库,而且很容易在构造的时候使之有序。那么判断一个数是否是素数的话,直接二分查找就能很快的判断。如果要查找n范围内的素数的话,直接把库中小于n的所有数取出来即可。所以,怎么构造一个素数库呢?

    首先,从解法一中我们知道,判断一个数是否为素数,只需与2~Sqrt(n)之间的整数做取余操作即可。这个判断过程的效率可以进一步的提高。试想,如果3,6,9都属于2~Sqrt(n)之间的整数,如果我们要判断的整数不能被3整除的话,肯定也不能被6和9整除,所以这些步骤可以省略。我们在构造的时候完全可以利用已经被构造的素数序列!

    假设我们已经有素数序列: p1, p2, .. pn

    现在要判断pn+1是否是素数, 则仅需要(1, sqrt(pn+1))范围内的所有素数序列即可,而不是其间的所有整数。

    而这个素数序列显然已经作为p1, p2, .. pn的一个子集被包含了!

    构造素数库或素数序列的代码如下:

    方法创建2~num之间的素数序列并返回。

            static List<int> CreatePrimes(int num)
            {
                List<int> primes = new List<int>();
                primes.Add(2);
                primes.Add(3);
    
                for (int i = 5; i <= num; i += 2)
                {
                    bool isPrime = true;
    
                    // 为什么j从1开始:因为i是一个奇数加偶数,所以只能是奇数,所以不可能被2整除,所以primes集合的第一个
                    // 元素2就不用计算了
                    for (int j = 1; primes[j] * primes[j] <= i; ++j)
                    {
                        if (i % primes[j] == 0)
                        {
                            isPrime = false; 
                            break;
                        }
                    }
    
                    if (isPrime)
                    {
                        primes.Add(i);
                    }
                }
    
                return primes;
            }

    如此一来,如果求素数操作很频繁的话,那么一开始就可以构造一个比较大的素数序列存储起来。然后判断素数时用二分查找法查找,取素数序列时也很方便。

    有一篇博客对求素数的算法写得也不错,见http://www.cnblogs.com/luluping/archive/2010/03/03/1677552.html

    最后想提醒一点的是,目前我们处理的都是int,当然你也可以换作long, double。只是终究是有个范围的。如果以字符串的形式给了一个相当大的数,远远超出了long的范围,那么怎么判断呢?

    1. 自己构造一个新的数据结构来存储,具体我不清楚。曾经有个搞芯片开发的人跟我说这不是问题。

    2. 自己写方法把求余操作实现,其实就是写代码实现小学的除法运算。

    就写这么多吧,希望对大家有所帮助。

  • 相关阅读:
    Redis主从同步原理-SYNC【转】
    redis3.0集群部署和测试
    Zabbix3.0配置邮件报警
    3分钟学会git命令的基础使用
    Rsync文件同步工具
    logstash grok 内置正则
    logrotate实现Mysql慢日志分割
    Python之unittest测试代码
    Zabbix如何实现批量监控端口状态
    Centos7搭建Confluence破解版
  • 原文地址:https://www.cnblogs.com/CSharpSPF/p/2432105.html
Copyright © 2011-2022 走看看