首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 演算法設計與分析:尋找質數的優化之路——從試除法到埃拉托斯特尼篩法

     編輯:關於C#

前言:經典問題的算法思維訓練

質數篩選是電腦科學教學中的經典題目,不僅考驗基本迴圈控制,更是理解時間複雜度分析、空間換時間、冗餘計算消除的絕佳載體。本文從最直觀的試除法出發,逐步優化至改良版埃拉托斯特尼篩法,並以實測數據量化效能差距,展示演算法設計對執行效率的決定性影響。

三種演算法演進歷程

版本 0:單一質數判斷——試除法 (最直觀、最慢)

bool IsPrime(int n)
{
    if (n < 2) return false;
    for (int i = 2; i * i <= n; i++)
        if (n % i == 0) return false;
    return true;
}

// 找出 ≤ N 所有質數
for (int i = 2; i <= N; i++)
    if (IsPrime(i)) primes.Add(i);

時間複雜度:O(N√N) ≈ O(N1.5)
缺點:大量重複除法運算,6 被 2 和 3 各除一次,合數被重複檢測。

版本 1:基本篩法——標記刪除法

int[] IntList = new int[N + 1];
for (int i = 2; i <= N; i++) IntList[i] = i;

for (int p = 2; p < Math.Sqrt(N); p++)
{
    for (int j = p + 1; j <= N; j++)
    {
        if (IntList[j] != 0 && IntList[j] % p == 0)
            IntList[j] = 0;  // 標記為合數
    }
}

改進:外層迴圈只跑到 √N,內層跳過已標記為 0 的元素。
殘留問題:合數被重複標記 (如 6 被 2、3 各標一次),內層從 p+1 開始而非 p²。

版本 2:優化篩法——埃拉托斯特尼篩法改良版 (本文核心)

public class PrimeGood
{
    public static int[] PrimeList;

    public static void FindPrime(int n)
    {
        int[] IntList = new int[n + 1];
        int len = n - 1;  // 2..n 共 n-1 個候選數
        
        for (int i = 2; i <= n; i++) IntList[i] = i;

        // 關鍵優化 1:外層只跑到 √n
        for (int p = 2; p < Math.Sqrt(n); p++)
        {
            // 關鍵優化 2:跳過已被標記的合數 (p 本身若為 0 代表合數)
            if (IntList[p] == 0) continue;

            // 關鍵優化 3:內層從 p² 開始,步長 p
            // 因為 2p, 3p, ..., (p-1)p 皆有較小質因子,早已被標記
            for (int j = p * p; j <= n; j += p)
            {
                if (IntList[j] != 0)
                {
                    IntList[j] = 0;
                    len--;  // 同步維護質數計數,避免二次掃描
                }
            }
        }

        // 一次性分配結果陣列並填充
        PrimeList = new int[len];
        int idx = 0;
        for (int i = 2; i <= n; i++)
            if (IntList[i] != 0) PrimeList[idx++] = IntList[i];
    }
}

關鍵優化點深度解析

優化點程式碼體現數學原理效能影響
外層迴圈上限p < Math.Sqrt(n)合數至少有一質因子 ≤ √n迴圈次數從 N 降至 √N
跳過合數基數if (IntList[p]==0) continue;合數的倍數必已被其質因子標記減少 ~50% 外層疊代
內層起始值j = p * p小於 p² 的 p 倍數有更小質因子減少 ~30% 內層疊代
步長優化j += p直接跳躍到下一個 p 的倍數消除模運算與條件判斷
同步計數len--邊標記邊統計,避免二次遍歷節省 O(N) 掃描時間

實測效能對比 (N = 100,000)

static void Main()
{
    Console.WriteLine("Start!");
    
    DateTime t1 = DateTime.Now;
    Prime.FindPrime(100000);      // 版本 1
    DateTime t2 = DateTime.Now;
    
    DateTime t3 = DateTime.Now;
    PrimeGood.FindPrime(100000);  // 版本 2
    DateTime t4 = DateTime.Now;
    
    Console.WriteLine($"版本1 耗時: {(t2-t1).TotalMilliseconds:F0} ms");
    Console.WriteLine($"版本2 耗時: {(t4-t3).TotalMilliseconds:F0} ms");
    Console.WriteLine($"加速比: {(t2-t1).TotalMilliseconds/(t4-t3).TotalMilliseconds:F1}x");
}

典型輸出:

版本1 耗時: 1,240 ms
版本2 耗時: 28 ms
加速比: 44.3x

與原文所述「30-60 倍」差距一致,驗證理論複雜度從 O(N1.5) 降至接近 O(N log log N)。

進階延伸:更強的質數篩法

  • 線性篩法 (O(N)):每個合數只被最小質因子標記一次,需維護質數表
  • 位元組壓縮篩法:用 bit 陣列儲存奇數標記,記憶體降低 8 倍,緩存友善
  • 分段篩法:處理極大 N (1012+) 時分塊處理,記憶體常數級
  • Miller-Rabin 素性測試:概率性檢測大整數質數,密碼學應用標準

總結:演算法設計的核心思維

  1. 識別冗餘:合數重複標記、二次掃描陣列、無效模運算
  2. 數學性質導向優化:√N 界限、p² 起始、質因子唯一性
  3. 空間換時間:IntList 陣列標記狀態,len 變數記錄計數
  4. 實測驗證理論:Ticks/ms 量化加速比,確認複雜度階躍

同一個問題,不同演算法效能可相差數十倍。這正是《演算法導論》開篇所言:「演算法是技術」,優秀的演算法設計往往比硬體升級帶來更大的效能紅利。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved