【问题标题】:Is a Recursive-Iterative Method Better than a Purely Iterative Method to find out if a number is prime?递归迭代方法是否比纯迭代方法更好地确定一个数字是否为素数?
【发布时间】:2013-11-07 06:06:47
【问题描述】:

我用 C 语言编写了这个程序,用于测试 数字是否为素数。我还不熟悉算法复杂性和所有大 O 的东西,所以我不确定我的方法是迭代和递归的组合,实际上是否比使用 纯迭代方法。

#include<stdio.h>
#include<stdlib.h>
#include<math.h>

typedef struct primenode{
    long int key;
    struct primenode * next;
}primenode;

typedef struct{
    primenode * head;
    primenode * tail;
    primenode * curr;
    unsigned long int size;
}primelist;

int isPrime(long int number, primelist * list ,long int * calls, long int * searchcalls);
primenode * primelist_insert(long int prime, primelist * list);
int primelist_search(long int searchval, primenode * searchat, long int * calls);
void primelist_destroy(primenode * destroyat);

int main(){
    long int n;
    long int callstoisprime = 0;
    long int callstosearch = 0;
    int result = 0;
    primelist primes;

    //Initialize primelist
    primes.head = NULL;
    primes.tail = NULL;
    primes.size = 0;

    //Insert 2 as a default prime (optional step)
    primelist_insert(2, &primes);

    printf("\n\nPlease enter a number: ");
    scanf("%d",&n);
    printf("Please wait while I crunch the numbers...");
    result = isPrime(n, &primes, &callstoisprime, &callstosearch);
    switch(result){
        case 1: printf("\n%ld is a prime.",n); break;
        case -1: printf("\n%ld is a special case. It's neither prime nor composite.",n); break;
        default: printf("\n%ld is composite.",n); break;
    }
    printf("\n\n%d calls made to function: isPrime()",callstoisprime);
    printf("\n%d calls made to function: primelist_search()",callstosearch);

    //Print all prime numbers in the linked list
    printf("\n\nHere are all the prime numbers in the linked list:\n\n");
    primes.curr = primes.head;
    while(primes.curr != NULL){
        printf("%ld ", primes.curr->key);
        primes.curr = primes.curr->next;
    }
    printf("\n\nNote: Only primes up to the square root of your number are listed.\n"
                "If your number is negative, only the smallest prime will be listed.\n"
                "If your number is a prime, it will itself be listed.\n\n");

    //Free up linked list before exiting
    primelist_destroy(primes.head);

    return 0;
}

int isPrime(long int number, primelist * list ,long int * calls, long int *searchcalls){
//Returns 1 if prime
//          0 if composite
//          -1 if special case
    *calls += 1;
    long int i = 2;
    if(number==0||number==1){
        return -1;
    }
    if(number<0){
        return 0;
    }
    //Search for it in the linked list of previously found primes
    if(primelist_search(number, list->head, searchcalls) == 1){
        return 1;
    }
    //Go through all possible prime factors up to its square root
    for(i = 2; i <= sqrt(number); i++){ 
        if(isPrime(i, list,calls,searchcalls)){
            if(number%i==0) return 0; //It's not a prime
        }
    }
    primelist_insert(number, list); /*Insert into linked list so it doesn't have to keep checking
                                                if this number is prime every time*/
    return 1;
}

primenode * primelist_insert(long int prime, primelist * list){
    list->curr = malloc(sizeof(primenode));
    list->curr->next = NULL;

    if(list->head == NULL){
        list->head = list->curr;
    }
    else{
        list->tail->next = list->curr;
    }
    list->tail = list->curr;
    list->curr->key = prime;
    list->size += 1;

    return list->curr;
}

int primelist_search(long int searchval, primenode * searchat, long int * calls){
    *calls += 1;
    if(searchat == NULL) return 0;
    if(searchat->key == searchval) return 1;
    return primelist_search(searchval, searchat->next, calls);
}

void primelist_destroy(primenode * destroyat){
    if(destroyat == NULL) return;
    primelist_destroy(destroyat->next);
    free(destroyat);
    return;
}

基本上,我见过的很多简单的 primalty 测试都是: 0. 2 是素数。 1. 循环遍历从 2 到一半或被测试数字的平方根的所有整数。 2.如果数能被任何东西整除,则break并返回false;它是复合的。 3.否则,最后一次迭代后返回true;这是主要的。

我认为您不必针对从 2 到平方根的每个数字进行测试,只需针对每个 prime 数字进行测试,因为所有其他数字都是质数的倍数。因此,该函数调用自身来确定一个数字是否为素数,然后再对其使用模数。这行得通,但我认为一遍又一遍地测试所有这些素数有点乏味。 因此,我也使用了一个链表来存储在其中找到的每个素数,以便在测试素数之前,程序首先搜索列表。

它真的更快,或更高效,还是我只是浪费了很多时间?我确实在我的电脑上对其进行了测试,对于较大的素数,它确实看起来更快,但我不确定。我也不知道它是否使用了更多的内存,因为无论我做什么,任务管理器都保持恒定的 0.7 MB。

感谢您的任何回答!

【问题讨论】:

  • 可能更适合Code Review
  • 谢谢,@Kninnug。我想你可能是对的。我已经在Code Review 上发布了这个帖子,如果先得到回复,我会删除这个帖子。
  • 谢谢大家的回答。 Code Review here 上有一个非常有用的答案。

标签: c performance recursion iteration primes


【解决方案1】:

由于您的程序只测试一个数字,因此您正在浪费时间试图避免使用复合材料进行测试。您执行大量计算以节省一次微不足道的模运算。

如果您要连续测试多个数字的素数,那么将素数预先计算到该范围上限的 sqrt 并在测试候选者时检查这些素数是有意义的.

更好的是执行 offset sieve of Eratosthenes (C code here),以找到给定范围内的素数。埃拉托色尼筛子寻找从 2 到 N 的素数的时间复杂度为O(N log log N);和试除以素数到 sqrt,O(N^1.5 / (log N)^2)(更糟;例如,the ratio of run times 筛子高达 100 万与 100k 相比是 10.7 倍,而试除法是 22 倍;200 万比 1 百万是 2.04 倍筛子, 2.7x 为试验分区)。

Eratosthenes 偏移筛的伪代码:

Input: two Integers n >= m > 1

Let k = Floor(Sqrt(n)),
Let A be an array of Boolean values, indexed by Integers 2 to k, and
    B an array of Booleans indexed by Integers from m to n,
    initially all set to True.

for i = 2, 3, 4, ..., not exceeding k:
  if A[i] is True:
    for j = i^2, i^2+i, i^2+2i, ..., not greater than k:
      A[j] := False
    for j = i^2, i^2+i, i^2+2i, ..., between m and n, inclusive:
      B[j] := False

Output: all `i`s such that B[i] is True, are all the primes 
                                     between m and n, inclusive.

一个常见的优化是只使用赔率,i = 3,5,7,...,从一开始就避免任何偶数(无论如何,2 都是素数,任何偶数都是合数)。然后2i 的步骤,而不仅仅是i,可以在两个内部循环中使用。因此,偶数索引被完全排除在处理之外(通常使用精简寻址方案,val = start + 2*i)。

【讨论】:

    【解决方案2】:

    以下sn-p可以大大增强:

    //Go through all possible prime factors up to its square root
    for(i = 2; i <= sqrt(number); i++){ 
        if(isPrime(i, list,calls,searchcalls)){
            if(number%i==0) return 0; //It's not a prime
        }
    }
    

    不是循环遍历所有数字,而是循环遍历链表中的数字:

    // Go through all possible prime factors up to its square root
    primenode *p;
    for (p = primes.head; p->key <= sqrt(number); p = p->next) { 
            if (number%(p->key) == 0) return 0; //It's not a prime
        }
    }
    

    【讨论】:

    • 这对我不起作用。它仅将 2 的倍数识别为合数,但除此之外所有数字都是素数。根据程序的输出,我认为这是因为这种方法需要已经建立素数链表。使用这个,找到的唯一素数是 2(已经插入)和数字(除非它可以被 2 整除)。
    • 只有顶部调用需要遍历所有数字,递归调用只需要检查列表。
    猜你喜欢
    • 2013-02-27
    • 2021-03-30
    • 1970-01-01
    • 1970-01-01
    • 2014-03-24
    • 1970-01-01
    • 2021-12-24
    • 2016-07-21
    • 2021-08-09
    相关资源
    最近更新 更多