ฉันคิดว่ามีคำถามหลายข้อฝังอยู่ในหัวข้อนี้:
- คุณใช้งานอย่างไร
buildHeap
เพื่อให้มันทำงานในเวลาO (n) ?
- คุณแสดงให้เห็นว่า
buildHeap
ทำงานในเวลาO (n)เมื่อดำเนินการอย่างถูกต้องได้อย่างไร
- ทำไมไม่ได้ว่าการทำงานตรรกะเดียวกันที่จะทำให้กองเรียงทำงานในO (n)เวลามากกว่าO (n log n) ?
คุณใช้งานอย่างไรbuildHeap
เพื่อให้มันทำงานในเวลาO (n) ?
บ่อยครั้งที่คำตอบสำหรับคำถามเหล่านี้มุ่งเน้นไปที่ความแตกต่างระหว่างและsiftUp
siftDown
การเลือกที่ถูกต้องระหว่างsiftUp
และsiftDown
มีความสำคัญต่อการได้รับประสิทธิภาพO (n)สำหรับbuildHeap
แต่ไม่ทำอะไรเลยที่จะช่วยให้เราเข้าใจถึงความแตกต่างระหว่างbuildHeap
และheapSort
โดยทั่วไป อันที่จริงการใช้งานที่เหมาะสมของทั้งสองbuildHeap
และheapSort
จะเพียงsiftDown
ใช้ การsiftUp
ดำเนินการจำเป็นสำหรับการแทรกลงในฮีปที่มีอยู่เท่านั้นดังนั้นจึงจะใช้ในการสร้างคิวลำดับความสำคัญโดยใช้ฮีปไบนารีตัวอย่างเช่น
ฉันเขียนสิ่งนี้เพื่ออธิบายว่า heap สูงสุดทำงานอย่างไร นี่คือชนิดของฮีปโดยทั่วไปใช้สำหรับการเรียงลำดับฮีพหรือสำหรับคิวลำดับความสำคัญโดยที่ค่าที่สูงกว่าบ่งชี้ว่ามีลำดับความสำคัญสูงกว่า heap ขั้นต่ำก็มีประโยชน์เช่นกัน ตัวอย่างเช่นเมื่อดึงรายการที่มีคีย์จำนวนเต็มในลำดับจากน้อยไปมากหรือสตริงตามลำดับตัวอักษร หลักการเหมือนกันทุกประการ เพียงสลับลำดับการจัดเรียง
คุณสมบัติกองระบุว่าแต่ละโหนดในกองไบนารีจะต้องมีอย่างน้อยมีขนาดใหญ่เป็นสองด้านของเด็ก โดยเฉพาะอย่างยิ่งนี่ก็หมายความว่ารายการที่ใหญ่ที่สุดในกองอยู่ที่ราก การกลั่นกรองและการลอดขึ้นโดยพื้นฐานแล้วเป็นการดำเนินการแบบเดียวกันในทิศทางตรงกันข้าม: ย้ายโหนดที่มีการละเมิดจนกว่าจะตอบสนองคุณสมบัติฮีป:
siftDown
สลับโหนดที่เล็กเกินไปที่มีลูกที่ใหญ่ที่สุด (ดังนั้นจึงเลื่อนลง) จนกว่าอย่างน้อยใหญ่เท่าโหนดทั้งสองด้านล่าง
siftUp
สลับโหนดที่มีขนาดใหญ่เกินไปกับพาเรนต์ (ดังนั้นจึงเลื่อนขึ้น) จนกว่าจะไม่ใหญ่กว่าโหนดที่อยู่ด้านบน
จำนวนของการดำเนินการที่จำเป็นสำหรับsiftDown
และsiftUp
เป็นสัดส่วนกับระยะทางที่โหนดอาจต้องย้าย สำหรับsiftDown
มันเป็นระยะทางไปยังด้านล่างของต้นไม้ดังนั้นจึงsiftDown
มีราคาแพงสำหรับโหนดที่ด้านบนของต้นไม้ ด้วยsiftUp
การทำงานเป็นสัดส่วนกับระยะทางไปยังด้านบนของต้นไม้ดังนั้นจึงsiftUp
มีราคาแพงสำหรับโหนดที่ด้านล่างของต้นไม้ แม้ว่าการดำเนินการทั้งสองจะเป็นO (log n)ในกรณีที่เลวร้ายที่สุดในฮีปมีเพียงโหนดเดียวเท่านั้นที่อยู่ด้านบนขณะที่ครึ่งโหนดอยู่ในเลเยอร์ด้านล่าง ดังนั้นมันไม่ควรจะเป็นที่น่าแปลกใจมากเกินไปว่าถ้าเราต้องใช้การดำเนินการทุกโหนดเราต้องการมากกว่าsiftDown
siftUp
buildHeap
ฟังก์ชั่นใช้เวลาอาร์เรย์ของรายการที่ไม่ได้เรียงลำดับและย้ายพวกเขาจนกว่าพวกเขาจะตอบสนองทุกสถานที่ให้บริการกองดังนั้นการผลิตเป็นกองที่ถูกต้อง มีสองวิธีที่อาจใช้สำหรับการbuildHeap
ใช้งานsiftUp
และsiftDown
การดำเนินงานที่เราได้อธิบายไว้
เริ่มที่ด้านบนของฮีป (จุดเริ่มต้นของอาเรย์) และโทรหาsiftUp
แต่ละรายการ ในแต่ละขั้นตอนรายการที่ร่อนก่อนหน้านี้ (รายการก่อนหน้ารายการปัจจุบันในอาร์เรย์) จะสร้างฮีปที่ถูกต้องและการคัดแยกไอเท็มถัดไปขึ้นไปวางไว้ในตำแหน่งที่ถูกต้องในฮีป หลังจากกรองแต่ละโหนดแล้วไอเท็มทั้งหมดจะเป็นไปตามคุณสมบัติฮีพ
หรือไปในทิศทางตรงกันข้าม: เริ่มต้นที่ส่วนท้ายของอาร์เรย์แล้วเลื่อนไปทางด้านหน้า ในการวนซ้ำแต่ละครั้งคุณคัดแยกรายการลงจนกว่าจะอยู่ในตำแหน่งที่ถูกต้อง
การดำเนินการใดที่buildHeap
มีประสิทธิภาพมากกว่า
โซลูชันทั้งสองนี้จะสร้างฮีปที่ถูกต้อง siftDown
แปลกใจหนึ่งที่มีประสิทธิภาพมากขึ้นเป็นงานที่สองที่ใช้
ให้h = log nแทนความสูงของกอง ผลงานที่ต้องการสำหรับsiftDown
แนวทางจะได้รับจากผลรวม
(0 * n/2) + (1 * n/4) + (2 * n/8) + ... + (h * 1).
แต่ละคำในผลรวมมีระยะทางสูงสุดที่โหนดที่ความสูงที่กำหนดจะต้องย้าย (ศูนย์สำหรับชั้นล่าง h สำหรับรูต) คูณด้วยจำนวนโหนดที่ความสูงนั้น ในทางตรงกันข้ามผลรวมสำหรับการโทรsiftUp
ในแต่ละโหนดคือ
(h * n/2) + ((h-1) * n/4) + ((h-2)*n/8) + ... + (0 * 1).
ควรชัดเจนว่าผลรวมที่สองมีขนาดใหญ่กว่า ในระยะแรกอย่างเดียวHN / 2 = 1/2 n log nดังนั้นวิธีการนี้มีความซับซ้อนที่ดีที่สุดO (n log n)
เราจะพิสูจน์ว่าผลรวมสำหรับที่siftDown
วิธีการที่เป็นจริงO (n) ?
วิธีการหนึ่ง (มีการวิเคราะห์อื่น ๆ ที่ใช้งานได้) คือการเปลี่ยนผลรวมอัน จำกัด ให้เป็นอนุกรมที่ไม่มีที่สิ้นสุดแล้วใช้ชุดอนุกรม เราอาจเพิกเฉยในเทอมแรกซึ่งเป็นศูนย์:
หากคุณไม่แน่ใจว่าทำไมแต่ละขั้นตอนเหล่านี้ทำงานได้นี่เป็นเหตุผลสำหรับกระบวนการในคำพูด:
- เงื่อนไขเป็นบวกทั้งหมดดังนั้นผลรวมที่แน่นอนต้องมีขนาดเล็กกว่าผลรวมอนันต์
- ชุดเท่ากับชุดไฟประเมินที่x = 2/1
- ชุดไฟที่เท่ากับ (ครั้งคงที่) อนุพันธ์ของซีรีส์เทย์เลอร์f (x) = 1 / (1-x)
- x = 1/2อยู่ในช่วงเวลาของการบรรจบกันของซีรี่ส์ Taylor
- ดังนั้นเราสามารถแทนที่ซีรี่ส์ Taylor ด้วย1 / (1-x)แยกความแตกต่างและประเมินผลเพื่อหาค่าของอนุกรมไม่สิ้นสุด
เนื่องจากผลรวมอนันต์เป็นสิ่งnเราสรุปได้ว่าผลรวมจํากัดมีขนาดไม่ใหญ่และเป็นดังนั้นO (n)
กองทำไมต้องเรียงลำดับO (n log n)เวลาหรือไม่
ถ้ามันเป็นไปได้ที่จะทำงานbuildHeap
ในเส้นเวลาทำไมกองเรียงลำดับต้องO (n log n)เวลาหรือไม่ การเรียงลำดับของฮีปประกอบด้วยสองขั้นตอน อันดับแรกเราเรียกbuildHeap
ใช้อาร์เรย์ซึ่งต้องใช้เวลาO (n)หากปรับใช้อย่างเหมาะสม ขั้นตอนต่อไปคือการลบรายการที่ใหญ่ที่สุดในฮีปซ้ำ ๆ และวางไว้ที่ท้ายอาร์เรย์ เนื่องจากเราลบรายการออกจากฮีปมีจุดเปิดอยู่เสมอหลังจากสิ้นสุดฮีปที่เราสามารถจัดเก็บรายการได้ ดังนั้นการเรียงลำดับของฮีปจะประสบความสำเร็จในการเรียงลำดับโดยการลบไอเท็มที่ใหญ่ที่สุดถัดไปอย่างต่อเนื่องและวางลงในอาร์เรย์โดยเริ่มต้นที่ตำแหน่งสุดท้ายและเคลื่อนที่ไปข้างหน้า มันเป็นความซับซ้อนของส่วนสุดท้ายที่ครอบงำในกองเรียง ห่วงมีลักษณะเช่นนี้:
for (i = n - 1; i > 0; i--) {
arr[i] = deleteMax();
}
เห็นได้ชัดว่าลูปรัน O (n) คูณ ( n - 1เพื่อความแม่นยำรายการสุดท้ายมีอยู่แล้ว) ความซับซ้อนของdeleteMax
สำหรับกองคือO (log n) โดยทั่วไปแล้วจะถูกนำมาใช้โดยการลบรูท (ไอเท็มที่ใหญ่ที่สุดที่เหลืออยู่ในฮีป) และแทนที่ด้วยไอเท็มสุดท้ายในฮีปซึ่งเป็นใบไม้และดังนั้นหนึ่งในรายการที่เล็กที่สุด รูทใหม่นี้เกือบจะละเมิดคุณสมบัติฮีปดังนั้นคุณต้องโทรหาsiftDown
จนกว่าคุณจะย้ายกลับไปยังตำแหน่งที่ยอมรับได้ สิ่งนี้ยังมีผลในการย้ายไอเท็มที่ใหญ่ที่สุดถัดไปจนถึงรูท โปรดสังเกตว่าในทางตรงกันข้ามกับbuildHeap
ที่ส่วนใหญ่ของโหนดที่เราเรียกsiftDown
จากด้านล่างของต้นไม้ตอนนี้เรากำลังเรียกsiftDown
จากด้านบนของต้นไม้ในการทำซ้ำแต่ละครั้ง!แม้ว่าต้นไม้จะลดขนาดลง แต่ก็ไม่ได้ลดลงเร็วพอ : ความสูงของต้นไม้จะคงที่จนกว่าคุณจะลบโหนดในช่วงครึ่งแรก (เมื่อคุณล้างชั้นล่างออกจนหมด) แล้วสำหรับไตรมาสถัดไปสูงเป็นชั่วโมง - 1 ดังนั้นผลรวมทั้งหมดสำหรับสเตจที่สองนี้คือ
h*n/2 + (h-1)*n/4 + ... + 0 * 1.
สังเกตุสวิตช์: ตอนนี้กรณีงานที่เป็นศูนย์สอดคล้องกับโหนดเดียวและกรณีงานhสอดคล้องกับครึ่งโหนด ผลรวมนี้คือO (n log n)เช่นเดียวกับรุ่นที่ไม่มีประสิทธิภาพของbuildHeap
การดำเนินการโดยใช้ siftUp แต่ในกรณีนี้เราไม่มีทางเลือกเนื่องจากเราพยายามเรียงลำดับและเราต้องการให้ลบรายการที่ใหญ่ที่สุดถัดไป
ในการสรุปการทำงานของกองเรียงลำดับเป็นผลรวมของทั้งสองขั้นตอน: O (n) เวลาสำหรับ buildHeap และO (n log n) ที่จะเอาแต่ละโหนดในการสั่งซื้อเพื่อให้ความซับซ้อนเป็น O (n log n) คุณสามารถพิสูจน์ได้ (โดยใช้แนวคิดจากทฤษฎีสารสนเทศ) ว่าสำหรับการเรียงลำดับแบบเปรียบเทียบO (n log n)เป็นสิ่งที่ดีที่สุดที่คุณสามารถคาดหวังได้ดังนั้นจึงไม่มีเหตุผลที่จะผิดหวังในกรณีนี้ ระยะเวลา O (n) ที่buildHeap
ทำ