อัลกอริธึมแบบแฟคทอเรียลมีประสิทธิภาพมากกว่าการคูณแบบไร้เดียงสา


37

ฉันรู้วิธีเขียนโค้ดสำหรับแฟคทอเรียลที่ใช้ทั้งแบบวนซ้ำและวนซ้ำ (เช่นn * factorial(n-1)สำหรับตัวอย่าง) ฉันอ่านในตำราเรียน (โดยไม่ได้รับคำอธิบายเพิ่มเติมใด ๆ ) ว่ามีวิธีที่มีประสิทธิภาพยิ่งขึ้นในการเขียนโปรแกรมสำหรับแฟคทอเรียลด้วยการแบ่งครึ่งแบบวนซ้ำ

ฉันเข้าใจว่าทำไมถึงเป็นเช่นนั้น อย่างไรก็ตามฉันต้องการลองเขียนโค้ดด้วยตัวเองและฉันไม่คิดว่าจะเริ่มต้นอย่างไร เพื่อนแนะนำให้ฉันเขียนเคสพื้นฐานก่อน และฉันกำลังคิดที่จะใช้อาร์เรย์เพื่อให้ฉันสามารถติดตามตัวเลข ... แต่ฉันไม่เห็นวิธีการออกแบบรหัสดังกล่าว

ฉันควรค้นคว้าเทคนิคแบบใด

คำตอบ:


39

อัลกอริทึมที่ดีที่สุดที่เป็นที่รู้จักคือการแสดงแฟคทอเรียลเป็นผลิตภัณฑ์ของมหาอำนาจ หนึ่งสามารถกำหนดช่วงเวลาได้อย่างรวดเร็วเช่นเดียวกับพลังงานที่เหมาะสมสำหรับแต่ละนายกโดยใช้วิธีการตะแกรง การคำนวณพลังงานแต่ละอย่างสามารถทำได้อย่างมีประสิทธิภาพโดยใช้กำลังสองซ้ำแล้วปัจจัยจะถูกคูณเข้าด้วยกัน สิ่งนี้อธิบายโดย Peter B. Borwein, บนความซับซ้อนของการคำนวณแฟคทอเรียล , วารสารอัลกอริทึม6 376–380, 1985. ( PDF ) โดยย่อ, สามารถคำนวณได้ในO ( n ( บันทึกn ) 3บันทึกบันทึกn )เวลาเมื่อเทียบกับΩ ( nn!O(n(logn)3loglogn)Ω(n2logn)เวลาที่ต้องใช้เมื่อใช้คำจำกัดความ

สิ่งที่ตำราเรียนอาจหมายถึงคือวิธีการแบ่งและพิชิต หนึ่งสามารถลดการคูณn1โดยใช้รูปแบบปกติของผลิตภัณฑ์

n?135(2n1)(2n)!=123(2n)

(2n)!=n!2n357(2n1).
n=2kk>0n(2k)!=(2k1)!22k1(2k1)?
(2k)!=(22k1+2k2++20)i=0k1(2i)?=(22k1)i=1k1(2i)?.
(2k1)?(k2)+2k1222k222k1

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)

แม้แต่รหัสผ่านแรกนี้ก็ยังดีขึ้นเล็กน้อย

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

ประมาณ 20% ในการทดสอบของฉัน

n2


คุณปล่อยให้ปัจจัยสำคัญ เวลาการคำนวณตามกระดาษของ Borwein ไม่ใช่ O (n log n log log n) เป็น O (M (n log n) log log n) โดยที่ M (n log n) เป็นเวลาสำหรับการคูณขนาดสองจำนวน n log n
gnasher729

18

โปรดทราบว่าฟังก์ชันแฟกทอเรียลเติบโตอย่างรวดเร็วจนคุณต้องใช้จำนวนเต็มตามอำเภอใจเพื่อรับประโยชน์จากเทคนิคที่มีประสิทธิภาพมากกว่าวิธีไร้เดียงสา factorial ใน 21 ที่มีอยู่แล้วมีขนาดใหญ่เกินไปที่จะใส่ใน unsigned long long int64

n!n

Θ(|a||b|)|x|xΩ(|a|+|b|)max(|a|,|b|)

ด้วยพื้นหลังนี้บทความ Wikipediaควรสมเหตุสมผล

เนื่องจากความซับซ้อนของการคูณขึ้นอยู่กับขนาดของจำนวนเต็มที่จะถูกคูณคุณสามารถประหยัดเวลาด้วยการจัดเรียงการคูณตามลำดับที่ช่วยให้ตัวเลขถูกคูณด้วยขนาดเล็ก มันจะทำงานได้ดีขึ้นถ้าคุณจัดเรียงตัวเลขให้มีขนาดเท่ากัน "การแบ่งครึ่ง" ที่ตำราของคุณอ้างถึงประกอบด้วยวิธีการแบ่งและพิชิตต่อไปนี้เพื่อคูณชุดจำนวนเต็ม (หลาย):

  1. จัดเรียงตัวเลขที่จะคูณ (เริ่มแรกคือจำนวนเต็มทั้งหมดจากถึง ) ในสองชุดซึ่งผลิตภัณฑ์มีขนาดเท่ากัน นี่มันแพงน้อยกว่าการคูณ:(นอกจากหนึ่งเครื่อง)n | a b | | a | + | b |1n|ab||a|+|b|
  2. ใช้อัลกอริธึมวนซ้ำในแต่ละชุดย่อยสองชุด
  3. คูณผลลัพธ์กลางสองรายการ

ดูคู่มือ GMPสำหรับข้อมูลเฉพาะเพิ่มเติม

มีวิธีการที่เร็วกว่าซึ่งไม่เพียง แต่จะจัดเรียงตัวประกอบถึงแต่แยกตัวเลขโดยแยกย่อยพวกมันออกเป็นตัวประกอบเฉพาะกลุ่ม ฉันเพิ่งจะอ้างอิงจากเอกสารอ้างอิงจากบทความวิกิพีเดีย: “ในความซับซ้อนของการคำนวณแฟกทอเรี” โดยปีเตอร์ Borweinและการใช้งานโดยปีเตอร์ Luschnyn1n

¹ มีวิธีการที่เร็วขึ้นของการคำนวณมีความใกล้เคียงของแต่นั่นไม่ได้คำนวณแฟคทอเรียลอีกต่อไป แต่มันคำนวณโดยประมาณn!


9

เนื่องจากฟังก์ชั่นแฟกทอเรียลเติบโตอย่างรวดเร็วคอมพิวเตอร์ของคุณสามารถเก็บได้เพียงสำหรับขนาดค่อนข้างเล็กnตัวอย่างเช่นdoubleสามารถเก็บค่าได้สูงสุด. ดังนั้นหากคุณต้องการอัลกอริทึมที่รวดเร็วมากสำหรับการคำนวณเพียงแค่ใช้ตารางของขนาด171ไม่มี171 ! n ! 171n!n171!n!171

คำถามจะน่าสนใจยิ่งขึ้นหากคุณสนใจหรือใน function (หรือใน ) ในทุกกรณี (รวมถึง ) ฉันไม่เข้าใจความคิดเห็นในหนังสือเรียนของคุณΓ log Γ n !log(n!)ΓlogΓn!

นอกจากนี้อัลกอริทึมแบบวนซ้ำและแบบเรียกซ้ำของคุณจะเทียบเท่า (ข้อผิดพลาดของจุดลอย) เนื่องจากคุณใช้การเรียกซ้ำแบบหาง


"อัลกอริทึมซ้ำและแบบเรียกซ้ำของคุณเทียบเท่า" คุณหมายถึงความซับซ้อนเชิงซีเคียวริตี้ใช่ไหม? สำหรับความคิดเห็นในหนังสือเรียนฉันแปลจากภาษาอื่นแล้วบางทีการแปลของฉันก็แย่
user65165

หนังสือเล่มนี้พูดเกี่ยวกับการวนซ้ำและเรียกซ้ำแล้วจึงแสดงความคิดเห็นว่าคุณใช้การหารและการพิชิตเพื่อแบ่ง n! ครึ่งคุณจะได้ทางออกที่เร็วขึ้น ...
user65165

1
แนวคิดเรื่องความเท่าเทียมกันของฉันไม่เป็นทางการ แต่คุณสามารถพูดได้ว่าการดำเนินการทางคณิตศาสตร์ดำเนินการเหมือนกัน (หากคุณเปลี่ยนลำดับของตัวถูกดำเนินการในอัลกอริทึมแบบเรียกซ้ำ) อัลกอริทึมที่แตกต่างกัน "โดยเนื้อแท้" จะทำการคำนวณที่แตกต่างกันซึ่งอาจใช้ "เคล็ดลับ" บางอย่าง
Yuval Filmus

1
หากคุณพิจารณาขนาดของจำนวนเต็มเป็นพารามิเตอร์ในความซับซ้อนของการคูณความซับซ้อนโดยรวมสามารถเปลี่ยนแปลงได้แม้ว่าการดำเนินการทางคณิตศาสตร์จะ "เหมือนกัน"
Tpecatte

1
@CharlesOkwuagwu ใช่คุณสามารถใช้ตารางได้
Yuval Filmus
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.