簡體   English   中英

找到素數的位置

[英]Find position of prime number

我需要反過來找到第N個素數,即給定素數,我需要找到它的位置

2, 3, 5, 7...

素數可以很大,大約10^7 此外,還有很多。

我有一個可以二進制搜索的預先計算的素數索引,但我也有50k的空間限制! 可以篩選嗎? 或者任何其他快速方式?

編輯 :非常感謝所有精彩的答案,我沒想到他們! 我希望他們對尋找同樣的人有用。

你的范圍只有一千萬,這對於這種事情來說很小。 我有兩個建議:

1)以方便的間隔創建pi(n)表,然后使用分段的Eratosthenes篩來計算包含所需值的兩個表條目之間的素數。 間隔的大小決定了所需表的大小和計算結果的速度。

2)使用勒讓德的phi(x,a)函數和Lehmer的計數公式直接計算結果。 phi功能需要一些存儲,我不確定究竟有多少。

在這兩個中,我可能會根據您的問題大小選擇第一個替代方案。 我的博客上提供了Eratosthenes分段篩Lehmer的計數功能的實現。

編輯1:

經過反思,我有第三種選擇:

3)使用對數積分來估計pi(n)。 它是單調增加的,並且在你需要的時間間隔內總是大於pi(n)。 但差異很小,從不超過200左右。因此,您可以預先計算所有小於千萬的值的差異,制作200個變化點的表格,然后在請求時計算對數積分並查找校正因子表。 或者你可以用Riemann的R函數做類似的事情。

第三種方案占用的空間最少,但我懷疑第一種方案所需的空間不會太大,篩分可能比計算對數積分更快。 所以我會堅持原來的建議。 我的博客上有對數積分和Riemann R函數的實現。

編輯2:

正如評論所指出的那樣,這種方法效果不佳。 請忽略我的第三個建議。

作為我在建議不起作用的解決方案時出錯的懺悔,我寫了一個程序,它使用pi(n)的值表和一個分段的Eratosthenes篩來計算n <10000000的pi(n)值。將使用Python,而不是原始海報所要求的C,因為Python更簡單,更容易閱讀以用於教學目的。

我們首先計算篩分質量小於千萬的平方根; 這些素數將用於構建pi(n)的值表和執行計算最終答案的篩子。 一千萬的平方根是3162.3。 我們不想使用2作為篩選素數 - 我們只篩選奇數,並將2視為特例 - 但我們確實希望下一個素數大於平方根,因此列表篩分素數永遠不會耗盡(這會導致錯誤)。 所以我們使用這個非常簡單的Eratosthenes篩子來計算篩分質數:

def primes(n):
    b, p, ps = [True] * (n+1), 2, []
    for p in xrange(2, n+1):
        if b[p]:
            ps.append(p)
            for i in xrange(p, n+1, p):
                b[i] = False
    return ps

Eratosthenes的篩子分為兩部分。 首先,從2開始,列出小於目標數的數字。然后,從第一個未交叉的數字開始重復運行列表,並從列表中刪除所有數字的倍數。 最初,2是第一個未交叉的數字,因此交叉4,6,8,10等等。 然后3是下一個未交叉的數字,因此交叉6,9,12,15,依此類推。 然后將4作為2的倍數划掉,下一個未交叉的數字是5,所以划掉10,15,20,25等等。 繼續,直到考慮所有未交叉的數字; 保持未交叉的數字是素數。 p上的循環依次考慮每個數字,如果它是未交叉的,則i上的循環越過多個數。

primes函數返回447個素數的列表:2,3,5,7,11,13,...,3121,3137,3163。我們從列表中取2並在全局ps變量中存儲446個篩選素數:

ps = primes(3163)[1:]

我們需要的主要功能是計算范圍上的素數。 它使用一個篩子,我們將存儲在一個全局數組中,以便它可以重復使用,而不是在每次調用count函數時重新分配:

sieve = [True] * 500

count函數使用分段的Eratosthenes篩來計算從lo到hi的范圍內的素數(lo和hi都包括在范圍內)。 該功能有四個for循環:第一個清除篩子,最后一個計數填料,另外兩個執行篩分,方式類似於上面顯示的簡單篩子:

def count(lo, hi):
    for i in xrange(500):
        sieve[i] = True
    for p in ps:
        if p*p > hi: break
        q = (lo + p + 1) / -2 % p
        if lo+q+q+1 < p*p: q += p
        for j in xrange(q, 500, p):
            sieve[j] = False
    k = 0
    for i in xrange((hi - lo) // 2):
        if sieve[i]: k += 1
    return k

該函數的核心是for p in ps中的for p in ps循環for p in ps它執行篩分,依次取每個篩分素數p。 當篩分素數的平方大於范圍的極限時,循環終止,因為所有質數將在該點被識別(我們需要比平方根更大的下一個素數的原因是為了有篩選素數停止循環)。 神秘變量q是在lo到hi范圍內p的最小倍數的篩子的偏移(注意它不是范圍中p的最小倍數,而是p的最小倍數的偏移的索引)范圍,這可能令人困惑)。 if語句在引用完全正方形的數字時遞增q。 然后j上的循環從篩子上擊中p的倍數。

我們以兩種方式使用count函數。 第一次使用建立一個pi(n)值的表,其值為1000的倍數; 第二次使用在表格內插入。 我們將表存儲在全局變量piTable中:

piTable = [0] * 10000

我們根據原始請求選擇參數1000和10000,以將內存使用量保持在50千字節以內。 (是的,我知道原始的海報放寬了這個要求。但我們無論如何都可以尊重它。)一萬個32位整數將占用40,000字節的存儲空間,並且從lo到hi的1000范圍內進行篩選只需要500字節存儲將非常快。 您可能希望嘗試其他參數以查看它們如何影響程序的空間和時間使用情況。 構建piTable是通過調用count函數一萬次來完成的:

for i in xrange(1, 10000):
    piTable[i] = piTable[i-1] + \
        count(1000 * (i-1), 1000 * i)

到目前為止的所有計算都可以在編譯時而不是運行時完成。 當我在ideone.com上進行這些計算時,它們花了大約五秒鍾,但是這個時間不計算,因為當程序員第一次編寫代碼時,它可以一次完成。 作為一般規則,您應該尋找將代碼從運行時間移動到編譯時間的機會,以使您的程序快速運行。

剩下的唯一事情就是編寫實際計算小於或等於n的素數的函數:

def pi(n):
    if type(n) != int and type(n) != long:
        raise TypeError('must be integer')
    if n < 2: return 0
    if n == 2: return 1
    i = n // 1000
    return piTable[i] + count(1000 * i, n+1)

第一個if語句進行類型檢查。 第二個if語句返回對荒謬輸入的正確響應。 第三個if語句專門處理2個; 我們的篩分使1成為素數和2個復合物,兩者都是不正確的,所以我們在這里進行修復。 然后i被計算為piTable的最大索引小於請求的n,並且return語句將piTable中的值添加到表值和請求值之間的素數計數中; hi限制是n + 1,因為否則在n是素數的情況下它將不被計數。 舉個例子,說:

print pi(6543223)

將導致號碼447519顯示在終端上。

pi功能非常快。 ideone.com上 ,在大約半秒鍾內計算了一千次隨機調用pi(n),因此大約半個毫秒; 其中包括生成素數的時間和結果的總和,因此實際計算pi函數的時間甚至不到半毫秒。 這對我們建造桌子的投資來說是一個相當不錯的回報。

如果你對使用素數進行編程感興趣,我在博客上做了很多工作。 請過來參觀。

如果您事先知道輸入是素數,那么您可以使用近似值pi(n)≈n/ log n和一個小的固定點表來獲取素數,其中舍入結果不足以獲得正確的值ñ。 除了緩慢的蠻力方法之外,我認為這是你在尺寸限制范圍內最好的選擇。

我建議啟發式混合模型在這里工作。 存儲每個第n個素數,然后通過素性測試進行線性搜索。 為了加快速度,您可以使用快速簡單的素性測試(如使用a==2的費馬測試)並預先計算誤報。 根據輸入的最大大小和存儲限制進行一些微調應該很容易解決。

這里有一些有用的代碼。 您應該使用適用於您的輸入范圍的確定性Miller-Rabin檢驗替換基於試驗除法的素性測試。 篩選找到適當小范圍的質數將比試驗分割效果更好,但這是朝着錯誤方向邁出的一步。

#include <stdio.h>
#include <bitset>
using namespace std;

short smallprimes[549]; // about 1100 bytes
char in[19531]; // almost 20k

// Replace me with Miller-Rabin using 2, 7, and 61.
int isprime(int j) {
 if (j<3) return j==2;
 for (int i = 0; i < 549; i++) {
  int p = smallprimes[i];
  if (p*p > j) break;
  if (!(j%p)) return 0;
 }
 return 1;
}

void init() {
 bitset<4000> siv;
 for (int i = 2; i < 64; i++) if (!siv[i])
  for (int j = i+i; j < 4000; j+=i) siv[j] = 1;
 int k = 0;
 for (int i = 3; i < 4000; i+=2) if (!siv[i]) {
  smallprimes[k++] = i;
 }

 for (int a0 = 0; a0 < 10000000; a0 += 512) {
  in[a0/512] = !a0;
  for (int j = a0+1; j < a0+512; j+=2)
   in[a0/512] += isprime(j);
 }
}

int whichprime(int k) {
 if (k==2) return 1;
 int a = k/512;
 int ans = 1 + !a;
 for (int i = 0; i < a; i++) ans += in[i];
 for (int i = a*512+1; i<k; i+=2) ans += isprime(i);
 return ans;
}

int main() {
 int k;
 init();
 while (1 == scanf("%i", &k)) printf("%i\n", whichprime(k));
}

以下聽起來像你在尋找什么。 http://www.geekviewpoint.com/java/numbers/index_of_prime 在那里你會找到代碼和單元測試。 由於你的列表相對較小(即10^7 ),它應該處理它。

基本上你找到2n之間的所有素數,然后計算所有小於n的素數以找到索引。 此外,如果n不是素數,則函數返回-1

你的建議是最好的。 預先計算(或下載 )小於10 ^ 7的素數列表,然后二進制搜索它們。

只有664579個素數小於10 ^ 7,因此該列表將占用~2.7 MB的空間。 解決每個實例的二進制搜索將是超級快速的 - 只有~20次操作。

我這樣做了一次。 寫了一個代碼,給定n,可以快速找到第n個素數,最多n = 203542528,所以約為2e8。 或者,它可以向后,對於任何數字n,可以告訴多少素數小於n。

采用數據庫。 我將所有素數存儲到某一點(我的上限的sqrt。)在你的情況下,這意味着你將所有素數存儲到sqrt(1e7)。 其中有446個,您可以以壓縮形式存儲該列表,因為到該點的最大差異僅為34.超過該點,存儲每個第k個素數(對於某個k值)。然后快速篩子就足夠了在短時間內生成所有素數。

所以在MATLAB中,找到1e7'的素數:

nthprime(1e7)
ans =
   179424673

或者,它可以找到小於1e7的素數:

nthprime(1e7,1)
ans =
      664579

關鍵是,這樣的數據庫易於構建和搜索。 如果您的數據庫不超過50k,那應該沒問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM