筛选幸运数字

时间:2012-02-19 16:57:48

标签: java algorithm

我正在解决一个问题,即生成第一个n< 1000000幸运数字,但问题是它必须在2秒内生成它们,我尝试了不同的方法,但我总是有时间限制,我也在考虑使用布尔值更改此算法,但没有结果。

在这里你可以找到幸运数字的筛子作为参考http://en.wikipedia.org/wiki/Lucky_number

幸运数字序列如下 1,3,7,9,13,15,21,25,31,33,37,43,49,51,63,67,69,73,75,79,87,93,99 ......

这是我使用ArrayList创建的低效的代码,如果你有任何我可以用来解决它的提示,我会很感激。

public class Luckyumbers {
  public static void main(String[] args) {
    ArrayList<Integer> numbers = new ArrayList<Integer>();
    Integer max = 200000;
    int b;
    int c = 0;
    int p;

    for (int i = 1; i <= max; i += 2) {
      numbers.add(i);
    }

    // System.out.println(numbers);
    Integer bbb = 3;
    Integer j = numbers.size();
    int a = 3;
    while (bbb < j) {
      b = numbers.size();
      p = 1;

      for (int i = bbb; i < b; i += bbb) {
        numbers.remove(i-p);
        p = p + 1;
      }
      b = numbers.size() - 1;
      c = numbers.get(b);
      j = j - numbers.size() / bbb;
      bbb = numbers.get(a-1);
      a = a + 1;
      //  System.out.println(numbers);
    }

    for (int k = 0; k < numbers.size(); k++) {
      int z = numbers.get(k); 
      System.out.print(z + " ");
    }
  }
}

改进版本我更改了代码,其中包含了每个人都给我的建议,我将算法的时间减少到了13秒,但仍然减少了

public static void main(String[] args) {
        int max= 1000000;
        boolean[] numbers = new boolean[max];
        for (int i = 2; i < numbers.length; i+=2) 
            numbers[i]=true;
        int k=2,j=0,l=0,ant=0;
        int p=0;
        for (int i = 2; (k+k) < numbers.length; i++) {

            k=which(numbers,i);
            l=0;
            p=0;
            int sw=0;
            boolean untrue=false;
            for (j = l; l < numbers.length; j++) {
                if((p==k)&&sw==1){
                    numbers[l]=true;
                    untrue=true;
                                        p=0;
                }
                l=Next(numbers,l);
                //if (sw ==1)
                    p++;
                sw=1;
            }
            if (!untrue)
                break;
        }
                System.out.println(counter(numbers));
    }

    static int which(boolean[] numbers,int i){
                int k=0;
        int l;
        for (l = 0; l < i; l++) {
            k=Next(numbers,k);
        }
        return k;
    }
    static int Next(boolean[] numbers,int i){
        for (int l = i+1; l < numbers.length; l++) {
            if (numbers[l]==false)
                return l;   
        }
        return numbers.length+1;
    }

    static int counter(boolean[] numbers){
        int c=0;
        for (int j = 1; j < numbers.length; j++)    
            if(numbers[j]==false)
                c++;
        return c;
    }
}

4 个答案:

答案 0 :(得分:5)

使用标志数组(并且从筛子中消除时将每个元素设置为特殊值)会(可能)快得多。这样您就不需要创建N个Integer对象,将它们添加到集合中,然后再次删除它们。

要注意的一点是确定下一次迭代的“筛子”倍数......

其他几个答案讨论了从ArrayList中删除元素时效率低下的问题。

另请注意,首先创建对象需要时间。创建一个int[]个10M元素并为每个元素写一个值在我的系统上花费50ms,但对Integer个对象进行相同操作需要1100ms,这超过了目标时间的一半设置!

创建和填充10M元素ArrayList<Integer>需要1500毫秒,即使您预先调整它的大小,LinkedList<Integer>需要3200毫秒,所以在您开始筛选之前就没时间了。< / p>

更新:尝试过此操作(没有 btilly 建议的特殊外壳),确实要快得多,在原始设备上扫描的输入数量为8.6秒,原始设备为32.5秒和35M的10M输入数字与原始的137s输入数字。

我也尝试使用位数组而不是int数组,这显然可以节省大量内存但速度只有一半。

另一个想法 - 关于质数筛子的问题有很多问题,也可能讨论类似的性能技术?

答案 1 :(得分:2)

使用ArrayList执行此操作效率很低,因为每次从列表中删除元素时,列表中的所有其他数字都必须复制到先前的位置。使用LinkedList可能会有所帮助,但如果您只使用一个数组并在必须删除的索引处放置一些标记值(例如-1),它会更快。如果您尝试使用LinkedList,请确保永远不要调用get()方法,但要使用迭代器,并使用Iterator的remove方法删除元素。

另外,使用List强制不断地从int转换为Integer,反之亦然。

您还可以通过将ArrayList的初始大小设置为正确的值来获得一些时间。否则,它会强制它在每次必须扩展时制作内部数组的副本。

答案 2 :(得分:2)

每次调用remove时,都在调用O(n)操作,因为删除后的所有元素都需要逐个移向数组的开头。尝试使用LinkedList来提高性能。但是,get操作将为O(n)。尝试使用迭代器来解决这个问题。 (您必须手动执行iterator.next()七次才能从当前元素中获取第七个元素,但这是值得的。)

你的表现现在是O(n ^ 3),LinkedList它应该是O(n ^ 2)。理由:

  • 对于每次删除,您都有O(n)操作。
  • 您执行n / k删除,其中k是您筛选的当前数字,因此您执行O(n)次删除。
  • 有筛选的O(n)数,但是这个数字 小于n,我不确定它们的数量是否在O(log(n))中好。

使用LinkedList,第一个操作是O(1)。

答案 3 :(得分:0)

顺序很好,这些幸运数字(+1)。由于我想知道布尔数组是否是最优的,我使用了一个数组来保存尚未过滤掉的下一个元素的索引,请参阅下面的实现。有了这个,我不必为每个数字2,3,7,9迭代整个数组,但是可以跳过已经过滤掉的数组的片段。

public class LuckyNumbers {

    List<Integer> result;
    int sieveSize = -1;

    public LuckyNumbers(final int length) {
        sieveSize = length;
        result = new ArrayList<Integer>();
        final int[] sieve = new int[sieveSize];
        int oldIndex = 1;
        //initialize: each array-element points to the next index; index corresponds to number (ignore sieve[0] for simplicity)
        for (int index = 2; index <= sieveSize; index++) {
            sieve[oldIndex] = index;
            oldIndex = index;
        }
    }

    private void computeWithSieve() {
        int oldIndex = 1;
        // filters are 2,3,7,9,...
        for (int filter = 2; filter < sieveSize; filter = sieve[filter]) {
//          System.out.println("Filter " + filter);
            int moduloCounter = 1;
            oldIndex = -1;
//          if outdated filter-indexes are not set to -1: use filter == 2 ? 3 : sieve[filter] instead
            for (int index = 1; index < sieveSize; index = sieve[index]) {
                assert moduloCounter <= filter;
                if (moduloCounter == filter) {
                    moduloCounter = 1;
                    assert oldIndex > 0;
                    sieve[oldIndex] = sieve[index];
                    //unnecessary: sieve[index] = -1;
                    //jump back to oldIndex, such that nextElement() in the loop finds the correct successor:
                    index = oldIndex;
                } else {
                    oldIndex = index;
                    moduloCounter++;
                }
            }
        }
//      for (int index = 1; index < sieveSize; index = sieve[index]) {
//          result.add(index);
//      }
    }


    public String toString() {
        return result.toString(); 
    }

    public static void main(String[] args) {
        int size = (args.length == 0 || args[0] == null ? 100000 : new Integer(args[0]));
        LuckyNumbers ln = new LuckyNumbers(size);
        long start = System.currentTimeMillis();
        ln.computeWithSieve();
        System.out.println(System.currentTimeMillis() - start + "milliseconds required for " + ln.result.size());
//      System.out.println("Sieve: "+ln);
    }
}

不幸的是,我只能在2秒内计算高达100.000的幸运数字。但话说回来,只需在我的慢速计算机上初始化1000000个元素阵列需要大约1秒钟。所以我认为2秒的时间框是不可行的。