คำถามก่อนหน้านี้กล่าวถึงปัจจัยบางประการที่อาจทำให้อัลกอริทึมมีความซับซ้อน O (log n)
อะไรจะทำให้อัลกอริทึมมีความซับซ้อนของเวลา O (log log n)?
คำถามก่อนหน้านี้กล่าวถึงปัจจัยบางประการที่อาจทำให้อัลกอริทึมมีความซับซ้อน O (log n)
อะไรจะทำให้อัลกอริทึมมีความซับซ้อนของเวลา O (log log n)?
คำตอบ:
คำศัพท์ O (log n) สามารถแสดงในที่ต่างๆได้ แต่โดยทั่วไปจะมีสองเส้นทางหลักที่จะมาถึงรันไทม์นี้
ดังที่ได้กล่าวไว้ในคำตอบสำหรับคำถามที่เชื่อมโยงวิธีทั่วไปสำหรับอัลกอริทึมที่มีความซับซ้อนของเวลา O (log n) คือการที่อัลกอริทึมนั้นทำงานโดยการตัดขนาดของอินพุตซ้ำ ๆ ลงด้วยปัจจัยคงที่ในการวนซ้ำแต่ละครั้ง หากเป็นกรณีนี้อัลกอริทึมจะต้องยุติหลังจากการทำซ้ำ O (log n) เนื่องจากหลังจากทำการหาร O (log n) ด้วยค่าคงที่แล้วอัลกอริทึมจะต้องย่อขนาดของปัญหาให้เหลือ 0 หรือ 1 นี่คือเหตุผลที่ตัวอย่างเช่น การค้นหาแบบไบนารีมีความซับซ้อน O (log n)
ที่น่าสนใจคือมีวิธีการที่คล้ายกันในการย่อขนาดของปัญหาที่ทำให้เกิด runtimes ของรูปแบบ O (log log n) แทนที่จะแบ่งครึ่งอินพุตในแต่ละเลเยอร์จะเกิดอะไรขึ้นถ้าเราหารากที่สองของขนาดในแต่ละเลเยอร์
ตัวอย่างเช่นลองหาเลข 65,536 เราต้องหารมันด้วย 2 กี่ครั้งจนกว่าเราจะได้ 1? ถ้าเราทำเช่นนี้เราจะได้รับ
กระบวนการนี้ใช้เวลา 16 ขั้นตอนและมันยังเป็นกรณีที่ 65,536 = 2 16
แต่ถ้าเราหารากที่สองในแต่ละระดับเราจะได้
สังเกตว่าใช้เวลาเพียงสี่ขั้นตอนในการลงไปจนถึง 2 ทำไมจึงเป็นเช่นนี้
ประการแรกคำอธิบายที่เข้าใจง่าย n และ√nมีกี่หลัก? มีจำนวนประมาณ n หลักในจำนวน n และประมาณ log (√n) = log (n 1/2 ) = (1/2) log n หลักใน√n ซึ่งหมายความว่าทุกครั้งที่คุณหารากที่สองคุณจะลดจำนวนหลักลงครึ่งหนึ่งโดยประมาณ เนื่องจากคุณสามารถลดปริมาณ k O (log k) ลงครึ่งหนึ่งเท่านั้นก่อนที่จะลดลงเป็นค่าคงที่ (พูด 2) ซึ่งหมายความว่าคุณสามารถใช้รากที่สอง O (log log n) เท่าก่อนที่คุณจะลดจำนวนลง เป็นค่าคงที่ (พูด 2)
ตอนนี้เรามาทำคณิตศาสตร์เพื่อทำให้มันเข้มงวด ไม่ได้เขียนลำดับข้างต้นใหม่ในแง่ของพลังของสอง:
สังเกตว่าเราทำตามลำดับ 2 16 → 2 8 → 2 4 → 2 2 → 2 1 ในการวนซ้ำแต่ละครั้งเราตัดเลขชี้กำลังของกำลังสองลงครึ่งหนึ่ง มันน่าสนใจเพราะสิ่งนี้เชื่อมโยงกลับไปยังสิ่งที่เรารู้อยู่แล้ว - คุณหารจำนวน k ได้ครึ่งหนึ่ง O (log k) เท่าก่อนที่มันจะลดลงเหลือศูนย์
เพื่อใช้หมายเลขใด ๆ n และเขียนเป็น n = 2 k ทุกครั้งที่คุณหารากที่สองของ n คุณจะลดเลขชี้กำลังในสมการนี้ลงครึ่งหนึ่ง ดังนั้นจึงสามารถใช้ได้เฉพาะ O (log k) สแควร์รูทเท่านั้นก่อนที่ k จะลดลงถึง 1 หรือต่ำกว่า (ในกรณีที่ n ลดลงเหลือ 2 หรือต่ำกว่า) เนื่องจาก n = 2 kหมายความว่า k = log 2 n ดังนั้นจำนวนของรากที่สองที่นำมาคือ O (log k) = O (log log n) ดังนั้นหากมีอัลกอริทึมที่ทำงานโดยลดปัญหาซ้ำ ๆ ให้เป็นปัญหาย่อยของขนาดที่เป็นรากที่สองของขนาดปัญหาเดิมอัลกอริทึมนั้นจะสิ้นสุดหลังจากขั้นตอน O (log log n)
ตัวอย่างหนึ่งในโลกแห่งความเป็นจริงคือต้นไม้ van Emde Boas(vEB-tree) โครงสร้างข้อมูล vEB-tree เป็นโครงสร้างข้อมูลเฉพาะสำหรับการจัดเก็บจำนวนเต็มในช่วง 0 ... N - 1 ทำงานดังนี้โหนดรากของทรีมีพอยน์เตอร์√Nแยกช่วง 0 ... N - 1 ใน√Nที่เก็บข้อมูลแต่ละช่วงมีจำนวนเต็มประมาณ√N จากนั้นที่เก็บข้อมูลเหล่านี้จะถูกแบ่งย่อยภายในเป็น√ (√ N) ที่เก็บข้อมูลซึ่งแต่ละส่วนมีองค์ประกอบ√ (√ N) โดยประมาณ ในการสำรวจต้นไม้คุณเริ่มต้นที่รากกำหนดถังที่คุณเป็นสมาชิกจากนั้นดำเนินการต่อซ้ำในทรีย่อยที่เหมาะสม เนื่องจากวิธีการจัดโครงสร้าง vEB-tree คุณสามารถกำหนดได้ใน O (1) เวลาที่ทรีย่อยจะลงมาและหลังจากขั้นตอน O (บันทึกบันทึก N) คุณจะไปถึงด้านล่างของทรี ดังนั้นการค้นหาใน vEB-tree ใช้เวลาเพียง O (log log N)
อีกตัวอย่างหนึ่งคือHopcroft-ฟอร์จูนคู่ใกล้เคียงที่สุดของขั้นตอนวิธีการจุด อัลกอริทึมนี้พยายามค้นหาจุดที่ใกล้เคียงที่สุดสองจุดในชุดของจุด 2 มิติ ทำงานโดยการสร้างตารางของถังและกระจายคะแนนลงในถังเหล่านั้น หากพบที่เก็บข้อมูลที่จุดใดจุดหนึ่งในอัลกอริทึมที่มีจุดมากกว่า√Nอัลกอริทึมจะประมวลผลที่เก็บข้อมูลนั้นซ้ำ ดังนั้นความลึกสูงสุดของการเรียกซ้ำจึงเป็น O (log log n) และเมื่อใช้การวิเคราะห์โครงสร้างการเรียกซ้ำจะแสดงให้เห็นว่าแต่ละเลเยอร์ในต้นไม้ทำงาน O (n) ดังนั้นรันไทม์ทั้งหมดของอัลกอริทึมคือ O (n log log n)
มีอัลกอริทึมอื่น ๆ ที่บรรลุ O (log log n) runtimes โดยใช้อัลกอริทึมเช่นการค้นหาแบบไบนารีบนวัตถุขนาด O (log n) ตัวอย่างเช่นโครงสร้างข้อมูลx-fast trieทำการค้นหาแบบไบนารีบนเลเยอร์ของต้นไม้ที่มีความสูง O (log U) ดังนั้นรันไทม์สำหรับการดำเนินการบางอย่างจึงเป็น O (log log U) trie y-fastที่เกี่ยวข้องได้รับรันไทม์ O (log log U) บางส่วนโดยการรักษา BSTs ที่สมดุลของโหนด O (log U) แต่ละโหนดทำให้การค้นหาในต้นไม้เหล่านั้นทำงานในเวลา O (log log U) ต้นไม้เต้นจังหวะแทงโก้และที่เกี่ยวข้องต้นไม้ multisplayโครงสร้างข้อมูลท้ายด้วย O (บันทึกบันทึก n) ระยะในการวิเคราะห์ของพวกเขาเพราะพวกเขารักษาต้นไม้ที่มี O (log n) แต่ละรายการ
อัลกอริทึมอื่น ๆ บรรลุรันไทม์ O (log log n) ด้วยวิธีอื่น Interpolation searchคาดว่ารันไทม์ O (log log n) เพื่อค้นหาตัวเลขในอาร์เรย์ที่เรียงลำดับ แต่การวิเคราะห์มีส่วนเกี่ยวข้องพอสมควร ในท้ายที่สุดการวิเคราะห์จะทำงานโดยแสดงให้เห็นว่าจำนวนการทำซ้ำเท่ากับจำนวน k ดังกล่าวซึ่ง n 2 -k ≤ 2 ซึ่งบันทึกบันทึก n เป็นวิธีแก้ปัญหาที่ถูกต้อง อัลกอริทึมบางอย่างเช่นอัลกอริทึมCheriton-Tarjan MSTมาถึงรันไทม์ที่เกี่ยวข้องกับ O (log log n) โดยการแก้ปัญหาการเพิ่มประสิทธิภาพที่มีข้อ จำกัด ที่ซับซ้อน
หวังว่านี่จะช่วยได้!
วิธีหนึ่งในการดูปัจจัยของ O (log log n) ในความซับซ้อนของเวลาคือการหารเหมือนกับสิ่งที่อธิบายไว้ในคำตอบอื่น ๆ แต่มีอีกวิธีหนึ่งในการดูปัจจัยนี้เมื่อเราต้องการทำการแลกเปลี่ยนระหว่างเวลากับช่องว่าง / เวลา และการประมาณ / เวลาและความแข็ง / ... ของอัลกอริทึมและเรามีการทำซ้ำเทียมในอัลกอริทึมของเรา
ตัวอย่างเช่น SSSP (เส้นทางที่สั้นที่สุดของแหล่งเดียว) มีอัลกอริทึม O (n) บนกราฟระนาบ แต่ก่อนหน้านี้อัลกอริทึมที่ซับซ้อนนั้นมีอัลกอริทึมที่ง่ายกว่ามาก (แต่ก็ค่อนข้างยาก) ด้วยเวลาทำงาน O (n บันทึกบันทึก n) ฐานของอัลกอริทึมมีดังต่อไปนี้ (เป็นเพียงคำอธิบายที่หยาบมากและฉันขอเสนอให้ข้ามการทำความเข้าใจส่วนนี้และอ่านส่วนอื่น ๆ ของคำตอบ):
แต่ประเด็นของฉันคือที่นี่เราเลือกการหารให้มีขนาด O (log n / (log log n)) หากเราเลือกหน่วยงานอื่นเช่น O (log n / (log n / (log n) ^ 2) ซึ่งอาจทำงานได้เร็วขึ้นและให้ผลลัพธ์อื่น ฉันหมายถึงในหลาย ๆ กรณี (เช่นในอัลกอริธึมการประมาณค่าหรืออัลกอริทึมแบบสุ่มหรืออัลกอริทึมเช่น SSSP ตามด้านบน) เมื่อเราวนซ้ำบางสิ่งบางอย่าง (ปัญหาย่อยวิธีแก้ปัญหาที่เป็นไปได้ ... ) เราจะเลือกจำนวนการทำซ้ำที่สอดคล้องกับการแลกเปลี่ยนนั้น เรามี (เวลา / ช่องว่าง / ความซับซ้อนของอัลกอริทึม / ปัจจัยคงที่ของอัลกอริทึม ... ) ดังนั้นเราอาจเห็นสิ่งที่ซับซ้อนมากกว่า "log log n" ในอัลกอริทึมการทำงานจริง