日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

你真的会求素数吗?

發布時間:2024/4/11 编程问答 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 你真的会求素数吗? 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

你真的會求素數嗎?

文章目錄

    • 你真的會求素數嗎?
    • 煉氣期
    • 金丹期
    • 大乘期
    • 飛升期

素數的定義看起來很簡單, 如果一個數如果只能被 1 和它本身整除,那么這個數就是素數。

煉氣期

不要覺得素數的定義簡單,恐怕沒多少人真的能把素數相關的算法寫得高效。比如讓你寫這樣一個函數:

// 返回區間 [2, n) 中有幾個素數 int countPrimes(int n)// 比如 countPrimes(10) 返回 4 // 因為 2,3,5,7 是素數

你會如何寫這個函數?我想大家應該會這樣寫:

int countPrimes(int n) {int count = 0;for (int i = 2; i < n; i++)if (isPrim(i)) count++;return count; }// 判斷整數 n 是否是素數 boolean isPrime(int n) {for (int i = 2; i < n; i++)if (n % i == 0)// 有其他整除因子return false;return true; }

這樣寫的話時間復雜度 O(n2)O(n^2)O(n2),問題很大。首先你用 isPrime 函數來輔助的思路就不夠高效;

而且就算你要用 isPrime 函數,這樣寫算法也是存在計算冗余的。

金丹期

先來簡單說下如果你要判斷一個數是不是素數,應該如何寫算法。只需稍微修改一下上面的 isPrim 代碼中的 for 循環條件:

boolean isPrime(int n) {for (int i = 2; i * i <= n; i++)... }

換句話說,i 不需要遍歷到 n,而只需要到 sqrt(n) 即可。為什么呢,我們舉個例子,假設 n = 12。

12 = 2 × 6 12 = 3 × 4 12 = sqrt(12) × sqrt(12) 12 = 4 × 3 12 = 6 × 2

可以看到,后兩個乘積就是前面兩個反過來,反轉臨界點就在 sqrt(n)。
換句話說,如果在 [2,sqrt(n)] 這個區間之內沒有發現可整除因子,就可以直接斷定 n 是素數了,因為在區間 [sqrt(n),n] 也一定不會發現可整除因子。

現在,isPrime 函數的時間復雜度降為 O(sqrt(N)),但是我們實現 countPrimes 函數其實并不需要這個函數,以上只是希望讀者明白 sqrt(n) 的含義,因為等會還會用到。

大乘期

高效實現 countPrimes

高效解決這個問題的核心思路是和上面的常規思路反著來:
首先從 2 開始,我們知道 2 是一個素數,那么 2 × 2 = 4, 3 × 2 = 6, 4 × 2 = 8… 都不可能是素數了。
然后我們發現 3 也是素數,那么 3 × 2 = 6, 3 × 3 = 9, 3 × 4 = 12… 也都不可能是素數了。

看到這里,你是否有點明白這個排除法的邏輯了呢?先看我們的第一版代碼:

int countPrimes(int n) {boolean[] isPrim = new boolean[n];// 將數組都初始化為 trueArrays.fill(isPrim, true);for (int i = 2; i < n; i++) if (isPrim[i]) // i 的倍數不可能是素數了for (int j = 2 * i; j < n; j += i) isPrim[j] = false;int count = 0;for (int i = 2; i < n; i++)if (isPrim[i]) count++;return count; }

如果上面這段代碼你能夠理解,那么你已經掌握了整體思路,但是還有兩個細微的地方可以優化。

飛升期

首先,回想剛才判斷一個數是否是素數的 isPrime 函數,由于因子的對稱性,其中的 for 循環只需要遍歷 [2,sqrt(n)] 就夠了。這里也是類似的,我們外層的 for 循環也只需要遍歷到 sqrt(n):

for (int i = 2; i * i < n; i++) if (isPrim[i]) ...

除此之外,很難注意到內層的 for 循環也可以優化。我們之前的做法是:

for (int j = 2 * i; j < n; j += i) isPrim[j] = false;

這樣可以把 i 的整數倍都標記為 false,但是仍然存在計算冗余。

比如 n = 25,i = 4 時算法會標記 4 × 2 = 8,4 × 3 = 12 等等數字,但是這兩個數字已經被 i = 2 和 i = 3 的 2 × 4 和 3 × 4 標記了。

我們可以稍微優化一下,讓 j 從 i 的平方開始遍歷,而不是從 2 * i 開始:

for (int j = i * i; j < n; j += i) isPrim[j] = false;

這樣,素數計數的算法就高效實現了,其實這個算法有一個名字,叫做 Sieve of Eratosthenes。看下完整的最終代碼:

int countPrimes(int n) {boolean[] isPrim = new boolean[n];Arrays.fill(isPrim, true);for (int i = 2; i * i < n; i++) if (isPrim[i]) for (int j = i * i; j < n; j += i) isPrim[j] = false;int count = 0;for (int i = 2; i < n; i++)if (isPrim[i]) count++;return count; }

該算法的時間復雜度比較難算,顯然時間跟這兩個嵌套的 for 循環有關,其操作數應該是:
n/2 + n/3 + n/5 + n/7 + … = n × (1/2 + 1/3 + 1/5 + 1/7…)
括號中是素數的倒數。其最終結果是 O(N?loglogN)O(N * loglogN)O(N?loglogN),有興趣的讀者可以查一下該算法的時間復雜度證明。

總結

以上是生活随笔為你收集整理的你真的会求素数吗?的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。