帮助加快这个算法? Eratosthenes的筛子

时间:2009-08-17 12:11:23

标签: vb.net algorithm optimization

我已经编写了一个算法,我认为使用Eratosthenes的Sieve来计算高达n的素数是正确的。不幸的是,这个程序依赖于非常大的n值(尝试1000万)。这是我写的......

Protected Function Eratosthenes(ByVal n As Integer) As String
    Dim maxValue As Integer = Math.Sqrt(n)
    Dim values As Generic.List(Of Integer) = New Generic.List(Of Integer)
    Dim i As Integer
    ''//create list.
    For i = 2 To n
        values.Add(i)
    Next

    For i = 2 To maxValue
        If values.Contains(i) Then
            Dim k As Integer
            For k = i + 1 To n
                If values.Contains(k) Then
                    If (k Mod i) = 0 Then
                        values.Remove(k)
                    End If
                End If
            Next
        End If
    Next

    Dim result As String = ""
    For i = 0 To values.Count - 1
        result = result & " " & values(i)
    Next

    Return result
End Function

我怎样才能加速这个算法?我的瓶颈在哪里?

12 个答案:

答案 0 :(得分:8)

从大型列表中删除元素的速度很慢。

为什么不创建一个布尔值数组,并在知道它是非素数时将值设置为“True”?

当你找到一个新的素数时,你不需要通过所有更高的值,只需要多个值,将数组元素设置为True。

如果您想要退回它们,您可以为目前为止找到的素数保留单独的列表。

这是一个C#实现,它只是随着它打印出来。 (在C#中如果我想返回值,则返回IEnumerable<T>并使用迭代器块。)

using System;

public class ShowPrimes
{
    static void Main(string[] args)
    {
        ShowPrimes(10000000);
    }

    static void ShowPrimes(int max)
    {        
        bool[] composite = new bool[max+1];
        int maxFactor = (int) Math.Sqrt(max);

        for (int i=2; i <= maxFactor; i++)
        {
            if (composite[i])
            {
                continue;
            }
            Console.WriteLine("Found {0}", i);
            // This is probably as quick as only
            // multiplying by primes.
            for (int multiples = i * i;
                 multiples <= max;
                 multiples += i)
            {
                composite[multiples] = true;
            }
        }
        // Anything left is a prime, but not
        // worth sieving
        for (int i = maxFactor + 1; i <= max; i++)
        {
            if (composite[i])
            {
                continue;
            }
            Console.WriteLine("Found {0}", i);
        }
    }
}

答案 1 :(得分:4)

我不是VB人,但我想:

values.Remove(k)

是浪费时间,只需将地点设置为0,然后将素数提取到另一个列表或排序相同的列表并立即删除所有零。

答案 2 :(得分:2)

你的瓶颈是使用List,简单明了。 List.contains是O(n),List.Remove(n)。

您可以使用更好的数据结构加速应用程序,即BitArray。假设设置为True的项是素数,而那些不是复合的项。这意味着查找,添加或删除素数集中的项目将是O(1)操作。

答案 3 :(得分:1)

这是一个C#实现,我在一段时间后一起扔了。

也许它会有所帮助!

using System;
using System.Collections.Generic;

namespace sieve050707
{
class MainClass
{
public static void Main(string[] args)
{
Console.WriteLine(”Sieve of Eratosthenes – 05 July 2007″);
int sampleSize = 100;

if (args.Length > 0)
{
sampleSize = Int32.Parse(args[0]);
}

List sampleData = new List();
List primesFound = new List();

for (int counter=2; counter < sampleSize; counter++)
{
sampleData.Add(counter);
}

for (int counter=2; counter < sampleSize; counter++)
{
if(sampleData.Contains(counter))
{
primesFound.Add(counter);
sampleData.Remove(counter);
for (int multiple=counter*2; multiple < sampleSize; multiple = multiple + counter)
{
sampleData.Remove(multiple);
}
}
}
foreach(int prime in primesFound)
{
Console.WriteLine(prime.ToString() + ” is prime”);
}
}
}
}

HTH,

答案 4 :(得分:1)

使用字符串构建器。

如果它真的会经常使用它,那么你可能想要将已知的素数列表传递给它 - 这样你就可以重用现有结果集的计算。

可能会将int列表转换为数组吗?

一旦找到该项,你确定包含终止吗?

也许如果你使用了一个有序的素数列表,你可以得到一个更快的搜索算法来测试现有的而不是从开始到结束的直接迭代(当你知道它接近结束时会永远回到前面)。< / p>

另一种方法是多线程循环,以便您可以使用线程池或自定义实现来使用多个内核,以避免启动和停止线程。你本质上会将新素数返回到函数引用的池中。

答案 5 :(得分:1)

不改变算法,这里有一些优化:

Dim result As String = ""
For i = 0 To values.Count - 1
    result = result & " " & values(i)
Next

这是画家施莱米尔......请使用string.Join。


values.Contains(i)
values.Contains(k)

这些都很昂贵 - 使用HashSet代替List来降低价格。


If values.Contains(k) Then
    If (k Mod i) = 0 Then
        values.Remove(k)
    End If
End If

Mod比Contains便宜(即使使用HashSet)。先做Mod检查。

答案 6 :(得分:1)

如果你使用的是.Net 3.5,你可以像这样重写它:

Protected Function Eratosthenes(ByVal n As Integer) As String
    Dim values As List(Of Integer) = Enumerable.Range(0,n).ToList()

    For i = 2 To Math.Sqrt(n)
        If values(i) > 0 Then
            For k As Integer = i + 1 To n
                If values(k) AndAlso (k Mod i) = 0 Then
                    values(k) = 0
                End If
            Next
        End If
    Next

    Return string.Join(" ", values.Where(Function(i) i>1).Select(Function(i) i.ToString()).ToArray())
End Function

答案 7 :(得分:1)

我认为你错过了筛选一个优秀的算法工具的关键点......

筛分的伟大之处在于它允许你避免进行昂贵的模数操作:如果你知道p是素数,要消除它的所有倍数,而不是检查筛子中的所有数字是否可以被p整除,你消除物品2p,3p,4p ......实际上,Erathostenes筛子的工作方式,可以证明你要消除的第一个项目是p 2 ,所以你只需要检查一下p 2 ,p 2 + p,p 2 + 2p,p 2 + 3p。

由于我完全缺乏对Visual Basic的了解,这应该照顾到你的主要瓶颈:

Protected Function Eratosthenes(ByVal n As Integer) As String
    Dim maxValue As Integer = Math.Sqrt(n)
    Dim values As Generic.List(Of Integer) = New Generic.List(Of Integer)
    Dim i As Integer
    ''//create list.
    For i = 2 To n
        values.Add(i)
    Next

    For i = 2 To maxValue
        If values.Contains(i) Then
            Dim k As Integer
            For k = i * i To n Step i
                If values.Contains(k) Then
                    values.Remove(k)
                End If
            Next
        End If
    Next

    Dim result As String = ""
    For i = 0 To values.Count - 1
        result = result & " " & values(i)
    Next

    Return result
End Function

您可能还想考虑,正如其他人发布的那样,使用一系列布尔来保存您的筛子,从而避免所有那些包含检查,从计算的角度来看这可能是非常昂贵的。

Python是我选择的语言,所以即使它可能不适合您的需求,我也会在博客上发布关于素数here的筛选,尽管您也可能会发现thisthis兴趣...

答案 8 :(得分:0)

事实证明只在区间2..sqrt(maxValue)上检查素数就足够了

答案 9 :(得分:0)

此代码中的一个重大瓶颈是调用values.Contains(i)和values.Contains(k)重复每个整数候选。使当前方法更快的一种简单方法是在第一个列表旁边放置第二个列表,对每个数字存储false / true,这样O(n)算法不允许执行包含,而是允许直接查找O( 1)。在这种情况下,最后一个循环将检查是否应该包含该值。

然而,有更快的方法来解决问题。传统的Eratosthenes算法是生成从2到sqrt(n)的数字,并针对素数列表中已知的每个素数测试它们。如果数字完全除以它们中的任何一个,那么它将被抛出并尝试下一个数字,否则它将被添加为该列表中的素数。这可能会比我上面建议的优化速度更快。

答案 10 :(得分:0)

创建一个包含所有数字的数组,直到输入的最大数字是疯狂的;因为根据定义,它们中的大多数都不是素数。所以你已经拥有了至少2倍的数据量。

有很多众所周知的寻找素数的方法。

你想做什么?打印超过1000万?还有多少?这里的目标是什么?

如果只是为了了解素数,请研究GCD和其他有趣的事情。 (中国剩余定理)。

答案 11 :(得分:0)

这将在.28秒内进行n = 10,000,000,并且在约4秒内进行n = 100,000,000。 (VB 2008,Pentium e8500)

最慢的部分是字符串连接。 VB实际上很慢地连接大字符串,因此我使用整数数组来保存素数。

不是为每个值做一个mod,而是可以相乘,你只需要考虑一小部分值。

VB没有在“for k”语句中优化int(i / 2),因此我将其移动到变量max2。

对于大n,应答数组的重新定义成为瓶颈,所以每次需要扩展时我都会添加100,000个元素。

像往常一样......我没有彻底检查,所以可能存在错误。

Protected Function Eratosthenes(ByVal n As Integer) As Integer()

Dim nPrime As Integer
Dim values(n) As Byte
Dim ans(1) As Integer

Dim maxValue As Integer = Math.Sqrt(n)
For i = 0 To n
    values(i) = 1
Next i

Dim max2 As Integer
For i = 2 To maxValue
    If values(i) <> 0 Then
        max2 = Int(n / i)
        For k = 2 To max2
            values(k * i) = 0
        Next k
    End If
Next i

nPrime = 0
For i = 2 To UBound(values)
    If values(i) <> 0 Then
      nPrime = nPrime + 1
      If nPrime > UBound(ans) Then ReDim Preserve ans(nPrime + 100000)
      ans(nPrime) = i
    End If
Next i

ReDim Preserve ans(nPrime)
Eratosthenes = ans

End Function