Faktöriyel algoritma, naif çarpmadan daha verimli


37

Hem yinelemeli hem de özyinelemeli kullanarak (örneğin n * factorial(n-1), örneğin) faktörler için kodlamayı biliyorum . Bir ders kitabında (daha fazla açıklama yapılmadan) faktörler için daha etkili bir kodlama yöntemi olduğunu yinelemeli olarak ikiye böldüm.

Neden böyle olabileceğini anlıyorum. Ancak kendi başıma kodlamayı denemek istedim ve nereden başlayacağımı bildiğimi sanmıyorum. Bir arkadaşım önce temel vakaları yazmamı önerdi. ve rakamları izleyebilmek için dizileri kullanmayı düşünüyordum ... ama gerçekten böyle bir kod tasarlamanın bir yolunu göremiyorum.

Ne tür teknikleri araştırmalıyım?

Yanıtlar:


39

Bilinen en iyi algoritma, faktörlüğü en büyük güçlerin bir ürünü olarak ifade etmektir. Bir elek yaklaşımı kullanarak her bir astar için primerlerin yanı sıra doğru gücü de hızlı bir şekilde belirleyebilirsiniz. Her bir gücün hesaplanması, tekrarlanan kareler kullanılarak verimli bir şekilde yapılabilir ve ardından faktörler birlikte çarpılır. Bu, Peter B. Borwein, Faktörlerin Hesaplanmasının Karmaşıklığı Üzerine, Algoritmalar Dergisi 6 376-380, 1985'te tarif edilmiştir. ( PDF ) Kısacası,olarak hesaplanabilmektedir ile karşılaştırıldığında zaman, tanımı kullanıldığında süre gereklidir.n!O(n(logn)3loglogn)Ω(n2logn)

Ders kitabının belki de anlamı, böl ve yönet yöntemiydi. Biri , ürünün normal desenini kullanarak çarpımlarını azaltabilir .n1

Bırakgöstermektedirler uygun bir gösterim olarak. faktörlerini yeniden düzenleyin olarak Şimdi, tamsayısı için olduğunu varsayalım . (Bu, aşağıdaki tartışmadaki komplikasyonlardan kaçınmak için yararlı bir varsayımdır ve fikir genel genişletilebilir .) Sonrave bu nüksü genişleterek, Hesaplama1 3 5 ( 2 n - 1 ) ( 2 n ) ! = 1 2 3 ( 2 n ) ( 2 n ) ! = n ! 2 n3 5 7 ( 2 n - 1 ) . n = 2 k k >n?135(2n1)(2n)!=123(2n)

(2n)!=n!2n357(2n1).
n=2kn ( 2 k ) ! = ( 2 k - 1 ) ! 2 2 k - 1 ( 2 k - 1 ) ? ( 2 k ) ! = ( 2 2 k - 1 + 2 k - 2 + + 2 0 ) k - 1 i = 0 ( 2 i )k>0n(2k)!=(2k1)!22k1(2k1)?( 2 k - 1 ) ? ( k - 2 ) + 2 k - 1 - 2 2 2 k - 2 2 2 k - 1
(2k)!=(22k1+2k2++20)i=0k1(2i)?=(22k1)i=1k1(2i)?.
(2k1)?ve kısmi ürünlerin her aşamada çoğaltılması çarpımını alır. Bu, yaklaşık bir faktör bir gelişmedir den , sadece tanım kullanılarak çarpma. gücünü hesaplamak için bazı ek işlemler yapılması gerekir , ancak ikili aritmetik işleminde bu ucuza yapılabilir (tam olarak neyin gerekli olduğuna bağlı olarak, sadece sıfır eki eklemek gerekebilir ).(k2)+2k1222k222k1

Aşağıdaki Ruby kodu, bunun basitleştirilmiş bir versiyonunu uygular. Bu yeniden hesaplama engellemezyapabildiği yerlerde bile:n?

def oddprod(l,h)
  p = 1
  ml = (l%2>0) ? l : (l+1)
  mh = (h%2>0) ? h : (h-1)
  while ml <= mh do
    p = p * ml
    ml = ml + 2
  end
  p
end

def fact(k)
  f = 1
  for i in 1..k-1
    f *= oddprod(3, 2 ** (i + 1) - 1)
  end
  2 ** (2 ** k - 1) * f
end

print fact(15)

Bu ilk-geçiş kodu bile önemsizliği arttırıyor

f = 1; (1..32768).map{ |i| f *= i }; print f

Testimde yaklaşık% 20 oranında.

Çalışma ile biraz, bu aynı zamanda şartının kaldırılması, daha da geliştirilebilir bir güç olmak (bkz yoğun tartışma ).2n2


Önemli bir faktör bıraktın. Borwein'in kağıdına göre hesaplama süresi O (n log n log log n) değil. O (M (n log n) log log n), burada M (n log n) iki sayı n log n sayısını çarpma zamanıdır.
gnasher729

18

Faktoring fonksiyonunun o kadar hızlı büyüdüğünü ve naif yaklaşımdan daha verimli tekniklerden herhangi bir şekilde faydalanmak için keyfi büyüklükte tam sayılara ihtiyacınız olduğunu unutmayın . 21'in faktörü zaten 64-bit'e sığmayacak kadar büyük unsigned long long int.

Bildiğim kadarıyla, hesaplamak için bir algoritma yok( faktörü ) çarpma işleminden daha hızlıdır.nn!n

Ancak, çarpımı yaptığınız sıralama önemlidir. Bir makine tamsayısında çarpma, tamsayının değeri ne olursa olsun, aynı zamanda geçen temel bir işlemdir. Ama keyfi ölçekli tamsayılar için, geçen süre çoğalmaya bir ve b büyüklüğüne bağlıdır bir ve b : saf algoritma zamanlı olarak çalışır (burada olduğunu basamağının sayısı - istediğiniz tabanda, sonuç çarpımsal bir sabite kadar aynıdır). Orada daha hızlı çarpma algoritmaları , ama orada bariz bir alt sınırΘ(|a||b|)|x|xΩ(|a|+|b|)Çarpma en azından tüm rakamları okumak zorunda olduğundan. Bilinen tüm çarpma algoritmaları doğrusaldan daha hızlı büyür .max(|a|,|b|)

Bu arka plan ile donanmış Wikipedia makalesi anlam ifade etmeli.

Çarpımların karmaşıklığı, çarpılan tam sayıların boyutuna bağlı olduğundan, çarpımları sayıların küçük olmasını sağlayacak şekilde düzenleyerek zaman kazanabilirsiniz. Sayıların kabaca aynı boyutta olmasını düzenlerseniz daha iyi sonuç verir. Ders kitabınızın ifade ettiği “ ikiye bölme ”, bir (çoklu) tamsayı setini çarpmak için aşağıdaki bölme ve fethetme yaklaşımından oluşur :

  1. Ürünü kabaca aynı boyutta olan iki sette çarpılacak sayıları (başlangıçta ile arasındaki tüm tam sayıları ) düzenleyin. Bu çarpma çok daha ucuz:(bir makine ilavesi).n | a b | | a | + | b |1n|ab||a|+|b|
  2. Algoritmayı iki alt kümenin her birine tekrar tekrar uygulayın.
  3. İki ara sonucu çarpın.

Daha fazla bilgi için GMP kılavuzuna bakın .

Faktörlerinin yeniden düzenlemek sadece daha da hızlı yöntem vardır için ancak asal çarpanlara içine çürüyen ve çoğunlukla küçük tamsayılar çıkan çok uzun ürünü yeniden düzenleyerek numaralarını bölün. Peter Borwein tarafından yayınlanan “ Faktörlerin Hesaplanmasının Karmaşıklığı Üzerine” başlıklı Wikipedia makalesinden ve Peter Luschny'nin uygulamalarından referansları alıntılayacağım .n1n

¹ hesaplama hızlı yolu vardır yaklaşımları arasında, ancak bu artık faktoringi hesaplamıyor, bunun yaklaşık bir değerini hesaplıyor.n!


9

Faktoring işlevi çok hızlı büyüdüğünden, bilgisayarınız yalnızcanispeten küçük . Örneğin, bir çift 171'e kadar değerleri saklayabilir. Yani hesaplama için gerçekten hızlı bir algoritma istiyorsanızsadece boyutunda bir masa kullanın .n 171 ! n ! 171n!n171!n!171

Veya işleviyle (veya ) ilgileniyorsanız, soru daha ilginç hale gelir . Tüm bu durumlarda ( Dahil ), ders kitabınızdaki yorumu pek anlamıyorum.Γ log Γ n !log(n!)ΓlogΓn!

Bunun yanı sıra, yinelemeli ve özyinelemeli algoritmalarınız, özyinelemeli özdeyiş kullandığınızdan eşdeğerdir (kayan nokta hatalarına kadar).


"yinelemeli ve özyinelemeli algoritmalar eşdeğer" onların asimptotik karmaşıklığına atıfta bulunuyorsunuz, değil mi? Ders kitabındaki yorumuma gelince, başka bir dilden tercüme ediyorum, belki de çevirim berbat.
user65165 20:13

Kitap yinelemeli ve özyinelemeden bahseder ve sonra neyi bölmek için kullanıp neyi bölmek için nasıl kullanılacağı üzerine yorumlar! yarısında daha hızlı bir çözüm elde edebilirsiniz ...
user65165 20:13

1
Eşitlik kavramım tamamen resmi değil, ancak yapılan aritmetik işlemlerin aynı olduğunu söyleyebiliyorsunuz. "Doğası gereği" farklı bir algoritma, belki de bazı "hile" kullanarak farklı bir hesaplama yapar.
Yuval Filmus

1
Tamsayının boyutunu çarpımın karmaşıklığında bir parametre olarak düşünürseniz, aritmetik işlemler "aynı" olsa bile genel karmaşıklık değişebilir.
Tpecatte

1
@ CharlesOkwuagwu Doğru, bir tablo kullanabilirsiniz.
Yuval Filmus,
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.