Перейти к содержанию

Реализации алгоритмов/Решето Сундарама

Материал из Викиучебника — открытых книг для открытого мира

Решето́ Сундара́ма — детерминированный алгоритм нахождения всех простых чисел до некоторого целого числа .

 sundaram n = 2 : [2*m+1 | m <- [1..k], m `notElem` zs]
   where
     zs = [i + j + 2*i*j | i <- [1..k`div`3], j <- [i..(k-i)`div`(2*i + 1)]]
     k  = (n-1) `div` 2
void sundaram(int n){
    int *a = new int [n], i, j, k;
    memset(a, 0, sizeof(int) * n);
    for(i = 1; 3*i+1 < n; i++){
 	  for(j = 1; (k = i+j+2*i*j) < n && j <= i; j++){
 		a[k] = 1;
 	  }
    }
    for(i = 1; i < n; i++){
 	  if(a[i] == 0) cout << (2 * i + 1) << " ";
    }
    delete [] a;
 }
                К.Королев
                О соотношении Сундарама

Так называемое соотношение Сундарама

i m n = 2mn + m + n , m, n - натуральные (1)

используется как решето простых чисел по принципу «как есть» и это соотношение действительно есть готовый оператор для любого языка программирования высокого уровня. Однако, если рассматривать это соотношение как симметричную матрицу, то решето можно реализовать более эффективно. Но сначала обратимся ко 2-му тому культовой монографии Д.Кнута «Искусство программирования», точнее, к упр.8 раздела 4.5.4. «Разложение на простые множители». В упражнении предлагается найти алгоритм высеивания, использующий только операции сложения и вычитания, а в ответе на него приведены соотношения, непосредственно вытекающие из (1): в оригинальных обозначениях монографии это p = 2j + 1 и q = 2j + 2j2 соответственно. Сам же Д.Кнут происхождение этих выражений не объясняет, возможно, по причине весьма скромного рейтинга упражнения (задача на 15 – 20 минут), которое имеет, хотя тематически и не совсем логичное, продолжение в упражнении 15 раздела 5.2.3 3-го тома. Приведенное в ответе, цитата, «решение несколько замысловато: в нем пропускаются все числа, кратные 3». Решение предложено Б.Чартресом.

Однако, используя (1), можно построить более простой и эффективный алгоритм, пропускающий все числа, кратные 3. Вернее, с точки зрения матрицы (1) этот алгоритм пропускает все строки и столбцы матрицы, кратные 3-м. Приведем этот алгоритм, реализованный на ассемблере. Смысл используемых переменных следующий:

  • NOdd+1 – количество нечетных чисел, из которых надо отсеять составные;
  • Prime – однобайтовый массив такой, что если Prime[J – 1] = 1, то соответствующее простое

число вычисляется как 2J+1. Понятно, что отсутствует элемент массива, соответствующий тривиальной двойке.

SS_33-процедура с исключением строк и столбцов, «кратных» 3-ем

;Решето Сундарама: отсеивание из последовательности нечетных чисел всех составных чисел.
;Инициализация массива Prime, нулевой элемент которого, соответствующий 
;простому числу 3, равен 1. При этом отсеиваются все числа, кратные 3-ем. 
    mov    Byte Ptr  Prime[0],1       ; простое
    mov    eax,1
@@INIT:
    mov    Word Ptr  Prime[eax],0101h ; возможно, простые числа
    add    eax,2
    mov    Byte Ptr  Prime[eax],0 ; составное число, кратное 3-ём (исключение столбца)
    inc    eax
    cmp    eax,DWord Ptr NOdd
    jb     @@INIT
;====================== 
;Отсеивание остальных чисел
    mov    eax,12-1   ; второй диагональный элемент матрицы минус единица
    mov    ecx,5      ; период второй строки   
    mov    edx,10     ; для исключения столбца второй строки, «кратного» 3-ем
    mov    bl,2       ; счетчик для исключения  строк, «кратных» 3-ем
   ;=======  Движение по строке  =======
;Из-за «переопределенности» (1) следующие пересылки выполняются 
;многократно по одному и тому же адресу
@@S0:
    mov    edi,eax 	; сохранение диагонального элемента
@@S1:
    mov    Byte Ptr Prime[eax],0      ; составное число
    add    eax,ecx 
    cmp    eax,DWord Ptr NOdd
    jnb    @@S3 	; eax ≥ NOdd
@@S2:
    mov    Byte Ptr Prime[eax],0    ; составное число
    add    eax,edx                  ; исключение столбца, «кратного» 3-ем
    cmp    eax, DWord Ptr NOdd
    jb     @@S1	; eax < NOdd
@@S3:
    ja     @@S4	; eax > NOdd
    mov    Byte Ptr Prime[eax],0    ; последнее число в строке составное 
@@S4:
; ======= Переход к следующей строке =======
    mov    eax,edi	    ; восстановление диагонального элемента
    add    eax,ecx
    add    ecx,2     	    ; период следующей строки
    add    eax,ecx          ; следующий диагональный элемент минус единица
    mov    edx,ecx		     
    shl    edx,1            ; для исключения столбца очередной строки, «кратного» 3-ем
    dec    bl 
    jnz    @@S5            ; строка не «кратна» 3-ем
; Исключение строки, «кратной» 3-ем
    mov     bl,2                                                     
    add     eax,ecx
    add     ecx,2     	    ; период следующей строки
    add     eax,ecx         ; следующий диагональный элемент минус единица               
    mov     edx,ecx 
    shl     edx,1           ; для исключения столбца очередной строки, «кратного» 3-ем
    cmp     eax, DWord Ptr NOdd
    jb      @@S0                 ; на столбец, не «кратный» 3-ем
    ja      @@End 
    jz      @@S6
@@S5:
    mov     edi,eax 
    cmp     eax, DWord Ptr NOdd
    jb      @@S2                 ; исключить столбец, «кратный» 3-ем
    ja      @@End 
@@S6:
    mov     Byte Ptr Prime[eax],0    ; последнее нечетное число составное
@@End:

Приведенный алгоритм работает в 3-4 раза быстрее (по времени), например, алгоритма Аткина-Бернстейна. Кроме того, последний, так же, кстати, как и решение Чартреса, требует в два раза больше памяти, т.к. работает на всем множестве натуральных чисел. Несколько сложнее выглядят процедуры, исключающие дополнительно строки, «кратные» 5-ти и 7-ми соответственно, но они имеют еще большее преимущество.

Кстати, это не единственный результат, который можно получить из (1). Так, за 15-20 минут из (1) невозможно не получить бинарный алгоритм вычисления НОД (наибольшего общего делителя).

См. также

[править]

Решето Аткина

Решето Эратосфена