การสร้าง heap จะเป็นความซับซ้อนของเวลา O (n) ได้อย่างไร


494

ใครช่วยอธิบายได้ว่าการสร้างกองเป็นความซับซ้อน O (n) ได้อย่างไร

การแทรกรายการลงในกองคือO(log n)และแทรกซ้ำแล้วซ้ำอีก n / 2 ครั้ง (ส่วนที่เหลือเป็นใบไม้และไม่สามารถละเมิดคุณสมบัติกองได้) ดังนั้นนี่หมายถึงความซับซ้อนที่O(n log n)ฉันควรจะคิด

กล่าวอีกนัยหนึ่งสำหรับแต่ละรายการที่เรา "heapify" มีความเป็นไปได้ที่จะกรองลงหนึ่งครั้งสำหรับแต่ละระดับสำหรับ heap จนถึงตอนนี้ (ซึ่งเป็นระดับ log n)

ฉันพลาดอะไรไป


คุณหมายถึงอะไรโดย "สร้าง" กอง?
mfrankli

ตามที่คุณต้องการในกองให้ใช้อาร์เรย์ที่ไม่เรียงลำดับและกรองแต่ละองค์ประกอบครึ่งบนจนกว่าจะสอดคล้องกับกฎของกอง
GBa

2
สิ่งเดียวที่ฉันสามารถหาได้คือลิงค์นี้: ความซับซ้อนของ Buildheap ดูเหมือนจะเป็นΘ (n lg n) - การเรียกใช้ Heapify ในราคาΘ (lg n) ต่อการโทร แต่ผลลัพธ์นี้สามารถปรับปรุงเป็นΘ (n) cs.txstate.edu/~ch04/webtest/teaching/courses/5329/lectures/…
GBa

2
@Gba ดูวิดีโอนี้จาก MIT: เขาอธิบายได้ดีว่าเราจะได้ O (n) ได้อย่างไรด้วยคณิตศาสตร์youtube.com/watch?v=B7hVxCmfPtM
CodeShadow

2
ลิงก์โดยตรงไปยังคำอธิบาย @CodeShadow พูดถึง: youtu.be/B7hVxCmfPtM?t=41m21s
sha1

คำตอบ:


435

ฉันคิดว่ามีคำถามหลายข้อฝังอยู่ในหัวข้อนี้:

  • คุณใช้งานอย่างไร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)ในกรณีที่เลวร้ายที่สุดในฮีปมีเพียงโหนดเดียวเท่านั้นที่อยู่ด้านบนขณะที่ครึ่งโหนดอยู่ในเลเยอร์ด้านล่าง ดังนั้นมันไม่ควรจะเป็นที่น่าแปลกใจมากเกินไปว่าถ้าเราต้องใช้การดำเนินการทุกโหนดเราต้องการมากกว่าsiftDownsiftUp

buildHeapฟังก์ชั่นใช้เวลาอาร์เรย์ของรายการที่ไม่ได้เรียงลำดับและย้ายพวกเขาจนกว่าพวกเขาจะตอบสนองทุกสถานที่ให้บริการกองดังนั้นการผลิตเป็นกองที่ถูกต้อง มีสองวิธีที่อาจใช้สำหรับการbuildHeapใช้งานsiftUpและsiftDownการดำเนินงานที่เราได้อธิบายไว้

  1. เริ่มที่ด้านบนของฮีป (จุดเริ่มต้นของอาเรย์) และโทรหาsiftUpแต่ละรายการ ในแต่ละขั้นตอนรายการที่ร่อนก่อนหน้านี้ (รายการก่อนหน้ารายการปัจจุบันในอาร์เรย์) จะสร้างฮีปที่ถูกต้องและการคัดแยกไอเท็มถัดไปขึ้นไปวางไว้ในตำแหน่งที่ถูกต้องในฮีป หลังจากกรองแต่ละโหนดแล้วไอเท็มทั้งหมดจะเป็นไปตามคุณสมบัติฮีพ

  2. หรือไปในทิศทางตรงกันข้าม: เริ่มต้นที่ส่วนท้ายของอาร์เรย์แล้วเลื่อนไปทางด้านหน้า ในการวนซ้ำแต่ละครั้งคุณคัดแยกรายการลงจนกว่าจะอยู่ในตำแหน่งที่ถูกต้อง

การดำเนินการใดที่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) ?

วิธีการหนึ่ง (มีการวิเคราะห์อื่น ๆ ที่ใช้งานได้) คือการเปลี่ยนผลรวมอัน จำกัด ให้เป็นอนุกรมที่ไม่มีที่สิ้นสุดแล้วใช้ชุดอนุกรม เราอาจเพิกเฉยในเทอมแรกซึ่งเป็นศูนย์:

ชุด Taylor สำหรับ buildHeap ซับซ้อน

หากคุณไม่แน่ใจว่าทำไมแต่ละขั้นตอนเหล่านี้ทำงานได้นี่เป็นเหตุผลสำหรับกระบวนการในคำพูด:

  • เงื่อนไขเป็นบวกทั้งหมดดังนั้นผลรวมที่แน่นอนต้องมีขนาดเล็กกว่าผลรวมอนันต์
  • ชุดเท่ากับชุดไฟประเมินที่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ทำ


2
ฉันแก้ไขคำตอบของฉันเพื่อใช้ heap สูงสุดเนื่องจากดูเหมือนว่าคนอื่น ๆ ส่วนใหญ่อ้างถึงสิ่งนั้นและเป็นตัวเลือกที่ดีที่สุดสำหรับการเรียงลำดับฮีป
Jeremy West

28
นี่คือสิ่งที่ทำให้ฉันเห็นอย่างชัดเจนโดยสังหรณ์ใจ: "มีเพียงโหนดเดียวเท่านั้นที่อยู่ด้านบนในขณะที่ครึ่งโหนดอยู่ในชั้นล่างสุดดังนั้นจึงไม่น่าแปลกใจที่ถ้าเราต้องใช้การดำเนินการกับทุกโหนดเราจะ ชอบ siftDown มากกว่า siftUp "
Vicky Chijwani

3
@JeremyWest "วิธีแรกคือเริ่มที่ด้านบนสุดของฮีป (จุดเริ่มต้นของอาร์เรย์) และเรียก siftUp ในแต่ละรายการ" - คุณหมายถึงเริ่มที่ด้านล่างของกองเหรอ?
aste123

4
@ aste123 ไม่ถูกต้องตามที่เขียนไว้ แนวคิดคือการรักษาสิ่งกีดขวางระหว่างส่วนของอาร์เรย์ที่เป็นไปตามคุณสมบัติฮีปและส่วนที่ไม่เรียงลำดับของอาร์เรย์ คุณจะเริ่มต้นที่จุดเริ่มต้นก้าวไปข้างหน้าและเรียกในแต่ละรายการหรือเริ่มต้นที่ปลายเลื่อนไปข้างหลังและโทรsiftUp siftDownไม่ว่าคุณจะเลือกวิธีใดคุณกำลังเลือกรายการถัดไปในส่วนที่ไม่เรียงลำดับของอาร์เรย์และดำเนินการตามความเหมาะสมเพื่อย้ายไปไว้ในตำแหน่งที่ถูกต้องในส่วนที่สั่งซื้อของอาร์เรย์ ข้อแตกต่างคือประสิทธิภาพ
Jeremy West

2
นี่คือคำตอบที่ดีที่สุดที่ฉันเคยเห็นสำหรับคำถามใด ๆ ในโลก มันอธิบายได้ดีมากฉันก็เหมือนเป็นไปได้จริง ๆ ... ขอบคุณมาก
HARSHIL JAIN

314

การวิเคราะห์ของคุณถูกต้อง อย่างไรก็ตามมันไม่แน่น

มันไม่ง่ายเลยที่จะอธิบายว่าทำไมการสร้างฮีปเป็นการดำเนินการเชิงเส้นคุณควรอ่านให้ดีขึ้น

การวิเคราะห์ที่ดีของอัลกอริทึมที่สามารถมองเห็นที่นี่


แนวคิดหลักคือในbuild_heapอัลกอริทึมheapifyต้นทุนจริงไม่ใช่O(log n)องค์ประกอบทั้งหมด

เมื่อheapifyเรียกว่าเวลาทำงานขึ้นอยู่กับองค์ประกอบที่อาจเลื่อนลงในต้นไม้ก่อนที่กระบวนการจะสิ้นสุดลง กล่าวอีกนัยหนึ่งมันขึ้นอยู่กับความสูงขององค์ประกอบในกอง ในกรณีที่เลวร้ายที่สุดองค์ประกอบอาจลงไปจนถึงระดับใบไม้

ให้เรานับงานที่ทำตามลำดับ

ที่ระดับล่างสุดมี2^(h)โหนด แต่เราไม่เรียกheapifyสิ่งเหล่านี้ดังนั้นการทำงานคือ 0 ที่ระดับถัดไปจะมี2^(h − 1)โหนดและแต่ละโหนดอาจเลื่อนลง 1 ระดับ ที่ระดับ 3 จากด้านล่างจะมี2^(h − 2)โหนดและแต่ละโหนดอาจเลื่อนลง 2 ระดับ

ที่คุณสามารถดูไม่ได้การดำเนินงานทั้งหมดจะ heapify นี้คือเหตุผลที่คุณจะได้รับO(log n)O(n)


17
นี่เป็นคำอธิบายที่ดี ... แต่ทำไมมันถึงเป็นเช่นนั้นว่า heap-sort จะทำงานใน O (n log n) เหตุใดจึงไม่ใช้เหตุผลเดียวกันกับการจัดเรียงฮีป
hba

49
@hba ผมคิดว่าคำตอบของคำถามของคุณอยู่ในการทำความเข้าใจภาพนี้จากบทความนี้ Heapifyเป็นO(n)เมื่อทำด้วยsiftDownแต่เมื่อทำกับO(n log n) siftUpการเรียงลำดับที่เกิดขึ้นจริง (รายการจากกองหนึ่งโดยหนึ่งดึง) จะสามารถทำได้ด้วยดังนั้นจึงเป็นsiftUp O(n log n)
The111

3
ฉันชอบคำอธิบายที่เข้าใจง่ายของเอกสารภายนอกของคุณที่ด้านล่าง
Lukas Greblikas

1
@hba คำตอบด้านล่างโดย Jeremy West ตอบคำถามของคุณในรายละเอียดที่ละเอียดและเข้าใจง่ายยิ่งขึ้นอธิบายเพิ่มเติมความคิดเห็นของ The111 ได้ที่นี่
cellepo

คำถาม. สำหรับฉันแล้วดูเหมือนว่า # การเปรียบเทียบที่สร้างขึ้นสำหรับโหนดที่ความสูงiจากด้านล่างของต้นไม้ความสูง h ต้องทำการ2* log(h-i)เปรียบเทียบเช่นกันและควรนำมาพิจารณาด้วยเช่นกัน @ The111 คุณคิดอย่างไร?
ซิด

94

สังหรณ์ใจ:

"ความซับซ้อนควรเป็น O (nLog n) ... สำหรับแต่ละรายการที่เรา" heapify "มีความเป็นไปได้ที่จะกรองลงหนึ่งครั้งสำหรับแต่ละระดับสำหรับ heap จนถึงตอนนี้ (ซึ่งเป็นระดับ log n)"

ไม่มาก ตรรกะของคุณไม่ก่อให้เกิดข้อ จำกัด - มันประเมินความซับซ้อนของแต่ละกอง หากสร้างจากล่างขึ้นบนการแทรก (heapify) อาจน้อยกว่าO(log(n))มาก กระบวนการดังต่อไปนี้:

(ขั้นตอนที่ 1) องค์ประกอบแรกn/2ไปที่แถวล่างสุดของกอง h=0ดังนั้นจึงไม่จำเป็นต้องเพิ่ม heapify

(ขั้นตอนที่ 2) องค์ประกอบถัดไปจะอยู่ในแถวที่ 1 ขึ้นจากด้านล่าง , heapify ตัวกรองลดลง 1 ระดับn/22h=1

(ขั้นตอนที่i ) องค์ประกอบต่อไปจะเรียงกันเป็นแถวจากด้านล่าง กองกรองระดับลงn/2iih=ii

( บันทึกขั้นตอน(n) ) องค์ประกอบสุดท้ายจะเรียงกันเป็นแถวจากด้านล่าง กองกรองระดับลงn/2log2(n) = 1log(n)h=log(n)log(n)

ข้อสังเกต:หลังจาก1/2องค์ประกอบขั้นตอนที่หนึ่ง(n/2)มีอยู่แล้วในกองและเราไม่จำเป็นต้องเรียก heapify แม้แต่ครั้งเดียว นอกจากนี้ให้สังเกตว่ามีเพียงองค์ประกอบเดียวเท่านั้นที่รูทนั้นเกิดlog(n)ความซับซ้อนอย่างสมบูรณ์


ในทางทฤษฎี:

ขั้นตอนทั้งหมดNในการสร้างกองขนาดใหญ่nสามารถเขียนทางคณิตศาสตร์ได้

ที่ระดับความสูงiที่เราได้แสดงให้เห็น (เหนือ) ว่าจะมีองค์ประกอบที่จะต้องเรียก heapify และเรารู้ว่า heapify ที่ความสูงคือ สิ่งนี้ให้:n/2i+1iO(i)

ป้อนคำอธิบายรูปภาพที่นี่

วิธีแก้ปัญหาสำหรับการรวมครั้งสุดท้ายสามารถพบได้โดยการหาอนุพันธ์ของทั้งสองด้านของสมการทางเรขาคณิตที่รู้จักกันดี:

ป้อนคำอธิบายรูปภาพที่นี่

สุดท้ายเสียบลงในอัตราผลตอบแทนสมการข้างต้นx = 1/2 2การเสียบเข้ากับสมการแรกให้:

ป้อนคำอธิบายรูปภาพที่นี่

ดังนั้นจำนวนขั้นตอนทั้งหมดจึงมีขนาด O(n)


35

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

ดูhttp://en.wikipedia.org/wiki/Binary_heap "การสร้างกอง" สำหรับตัวอย่าง ในกรณีนี้คุณต้องทำงานจากระดับล่างสุดของต้นไม้โดยแลกเปลี่ยนโหนดพาเรนต์และโหนดลูกจนกว่าเงื่อนไขฮีปจะเป็นที่พอใจ


12

มีคำตอบที่ดีอยู่แล้ว แต่ฉันต้องการเพิ่มคำอธิบายภาพเล็กน้อย

ป้อนคำอธิบายรูปภาพที่นี่

ทีนี้ลองดูที่ภาพมี
n/2^1 โหนดสีเขียวที่มีความสูง 0 (ที่นี่ 23/2 = 12)
n/2^2 โหนดสีแดงที่มีความสูง 1 (ที่นี่ 23/4 = 6)
n/2^3 โหนดสีฟ้าที่มีความสูง 2 (ที่นี่ 23/8 = 3)
n/2^4 โหนดสีม่วงที่มีความสูง 3 (ที่นี่ 23/16 = 2)
ดังนั้นจึงมีn/2^(h+1)โหนดสำหรับความสูงh
ในการค้นหาความซับซ้อนของเวลาให้นับจำนวนของงานที่ทำหรือสูงสุดไม่มีการทำซ้ำโดยแต่ละโหนด
ตอนนี้มันสามารถสังเกตได้ว่าแต่ละโหนดสามารถ ดำเนินการซ้ำ (สูงสุด) == ความสูงของโหนด

Green  = n/2^1 * 0 (no iterations since no children)  
red    = n/2^2 * 1 (heapify will perform atmost one swap for each red node)  
blue   = n/2^3 * 2 (heapify will perform atmost two swaps for each blue node)  
purple = n/2^4 * 3 (heapify will perform atmost three swaps for each purple node)   

ดังนั้นสำหรับโหนดใด ๆ ที่มีความสูง h การทำงานสูงสุดที่ทำได้คือn / 2 ^ (h + 1) * h

ตอนนี้งานที่ทำเสร็จทั้งหมดคือ

->(n/2^1 * 0) + (n/2^2 * 1)+ (n/2^3 * 2) + (n/2^4 * 3) +...+ (n/2^(h+1) * h)  
-> n * ( 0 + 1/4 + 2/8 + 3/16 +...+ h/2^(h+1) ) 

ตอนนี้สำหรับค่าของhใด ๆลำดับ

-> ( 0 + 1/4 + 2/8 + 3/16 +...+ h/2^(h+1) ) 

จะไม่เกิน 1
ดังนั้นความซับซ้อนของเวลาจะไม่เกิน O (n)สำหรับการสร้างกอง


7

ดังที่เราทราบความสูงของฮีปคือlog (n)โดยที่ n คือจำนวนอิลิเมนต์ทั้งหมดให้แสดงเป็นh
   เมื่อเราทำการฮีปไลน์แล้วองค์ประกอบที่อยู่ในระดับสุดท้าย ( h ) จะไม่เคลื่อนไหวแม้แต่เดี่ยว ขั้นตอน
   จำนวนขององค์ประกอบที่ระดับสุดท้ายที่สอง ( h-1 ) คือ2 h-1และพวกเขาสามารถย้ายได้ที่ระดับสูงสุด1 (ในช่วง Heapify)
   ในทำนองเดียวกันสำหรับฉันTH ,ระดับเรามี2 ฉันองค์ประกอบซึ่งสามารถย้ายHiตำแหน่ง

ดังนั้นจำนวนการเคลื่อนไหวทั้งหมด = S = 2 h * 0 + 2 h-1 * 1 + 2 h-2 * 2 + ... 2 0 * h

                                               S = 2 h {1/2 + 2/2 2 + 3/2 3 + ... h / 2 h } ----------------------- -------------------------- 1
นี่คือซีรีย์AGPเพื่อแก้ปัญหาหารทั้งสองข้างด้วย 2
                                               S / 2 = 2 ชั่วโมง {1/2 2 + 2/2 3 + ... h / 2 h + 1 } --------------------------------- ---------------- 2
สมการลบ2จาก1ให้
                                               S / 2 = 2 ชั่วโมง { 1/2 + 1/2 2 + 1/2 3 + ... + 1 / 2 h + h / 2 h + 1 }
                                               S = 2 H + 1 {1/2 + 1/2 2 + 1/2 3 + ... + 1/2 H + H / 2 H + 1 }
ตอน1/2 + 1/2 2 + 1/2 3 + ... + 1/2 hกำลังลดลงGPซึ่งผลรวมน้อยกว่า1 (เมื่อ h มีแนวโน้มที่จะไม่มีที่สิ้นสุดผลรวมมีแนวโน้มที่ 1) ในการวิเคราะห์เพิ่มเติมลองมาหาขอบเขตบนของผลรวมซึ่งก็คือ 1
นี่จะให้S = 2 h + 1 {1 + h / 2 h + 1 }
                    = 2 h + 1 + h
                    ~ 2 h + h
เป็นh = log (n) , 2 h = n

ดังนั้นS = n + log (n)
T (C) = O (n)


6

ในขณะที่สร้างกองอยู่สมมติว่าคุณกำลังทำกำไรจากล่างขึ้นบน

  1. คุณใช้แต่ละองค์ประกอบและเปรียบเทียบกับลูก ๆ ของมันเพื่อตรวจสอบว่าคู่นั้นสอดคล้องกับกฎของฮีปหรือไม่ ดังนั้นใบได้รวมอยู่ในกองฟรี นั่นเป็นเพราะพวกเขาไม่มีลูก
  2. การย้ายขึ้นไปข้างบนสถานการณ์กรณีที่เลวร้ายที่สุดสำหรับโหนดที่อยู่เหนือใบไม้จะเป็นการเปรียบเทียบ 1 ครั้ง (ที่สูงสุดพวกเขาจะถูกเปรียบเทียบกับเด็กรุ่นเดียวเท่านั้น)
  3. ผู้ปกครองที่ใกล้ชิดที่สุดของพวกเขาสามารถเปรียบเทียบได้กับเด็กสองรุ่น
  4. ต่อไปในทิศทางเดียวกันคุณจะมีการบันทึก (n) การเปรียบเทียบสำหรับรูทในสถานการณ์กรณีที่เลวร้ายที่สุด และ log (n) -1 สำหรับลูกของมันทันที log (n) -2 สำหรับลูก ๆ ของพวกเขาทันทีเป็นต้น
  5. ดังนั้นเมื่อรวมทั้งหมดแล้วคุณจะพบกับบางสิ่งเช่น log (n) + {log (n) -1} * 2 + {log (n) -2} * 4 + ..... + 1 * 2 ^ {( logn) -1} ซึ่งไม่มีค่าอะไรนอกจาก O (n)

2

ในกรณีของการสร้างฮีปเราเริ่มจากความสูง logn -1 (โดยที่ logn คือความสูงขององค์ประกอบต้นไม้ n) สำหรับองค์ประกอบแต่ละรายการที่ความสูง 'h' เราจะไปที่ความสูงสูงสุดไม่เกิน (logn -h)

    So total number of traversal would be:-
    T(n) = sigma((2^(logn-h))*h) where h varies from 1 to logn
    T(n) = n((1/2)+(2/4)+(3/8)+.....+(logn/(2^logn)))
    T(n) = n*(sigma(x/(2^x))) where x varies from 1 to logn
     and according to the [sources][1]
    function in the bracket approaches to 2 at infinity.
    Hence T(n) ~ O(n)

1

การแทรกต่อเนื่องสามารถอธิบายได้โดย:

T = O(log(1) + log(2) + .. + log(n)) = O(log(n!))

ด้วยการประมาณสตาร์ลิ่งn! =~ O(n^(n + O(1)))ดังนั้นT =~ O(nlog(n))

หวังว่านี่จะช่วยได้วิธีที่ดีที่สุดO(n)คือการใช้อัลกอริธึมการสร้างฮีปสำหรับชุดที่กำหนด (การสั่งซื้อไม่สำคัญ)


1

โดยทั่วไปแล้วงานจะทำเฉพาะในโหนดที่ไม่ใช่ใบไม้ในขณะที่สร้างกอง ... และงานที่ทำคือปริมาณของการแลกเปลี่ยนลงเพื่อตอบสนองเงื่อนไขกอง ... ในคำอื่น ๆ (ในกรณีที่เลวร้ายที่สุด) ปริมาณเป็นสัดส่วนกับความสูง ของโหนด ... ทั้งหมดในความซับซ้อนทั้งหมดของปัญหาเป็นสัดส่วนกับผลรวมของความสูงของโหนดที่ไม่ใช่ใบทั้งหมดซึ่งเป็น (2 ^ h + 1 - 1) -h-1 = nh-1 = บน)


1

@bcorso ได้แสดงให้เห็นถึงการพิสูจน์การวิเคราะห์ความซับซ้อนแล้ว แต่เพื่อประโยชน์ของผู้ที่ยังคงเรียนรู้การวิเคราะห์ความซับซ้อนฉันมีสิ่งนี้เพื่อเพิ่ม:

พื้นฐานของความผิดพลาดดั้งเดิมของคุณเกิดจากการตีความความหมายของคำว่า "การแทรกเข้าไปใน heap นั้นต้องใช้เวลา O (log n) เวลา" แทรกเข้าไปในกองแน่นอน O (log n) แต่คุณต้องรับรู้ว่า n คือขนาดของกองในช่วงแทรก

ในบริบทของการแทรกวัตถุ n ลงในฮีปความซับซ้อนของการแทรก ith คือ O (log n_i) โดยที่ n_i คือขนาดของฮีปเหมือนกับการแทรก i เฉพาะการแทรกครั้งล่าสุดเท่านั้นที่มีความซับซ้อนของ O (log n)


1

สมมติว่าคุณมีองค์ประกอบNอยู่ในกอง จากนั้นความสูงของมันจะเป็นล็อก (N)

ตอนนี้คุณต้องการแทรกองค์ประกอบอื่นแล้วความซับซ้อนจะเป็น: เข้าสู่ระบบ (N)เราต้องเปรียบเทียบตลอดทางขึ้นไปยังราก

ตอนนี้คุณมีองค์ประกอบN + 1และความสูง = บันทึก (N + 1)

โดยใช้การเหนี่ยวนำเทคนิคก็สามารถพิสูจน์ให้เห็นว่าความซับซ้อนของการแทรกจะΣlogi

กำลังใช้งาน

log a + log b = log ab

สิ่งนี้ทำให้ง่ายต่อการ: ∑logi = log (n!)

ซึ่งจริงๆแล้วO (NlogN)

แต่

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

การรับรู้นี้มาถึงฉันหลังจากรายละเอียดแม้ว่า & ทดลองใช้ Heaps


0

ฉันชอบคำอธิบายของ Jeremy west .... อีกวิธีที่ง่ายต่อการเข้าใจมีให้ที่นี่ http://courses.washington.edu/css343/zander/NotesProbs/heapcomplexity

ตั้งแต่ buildheap ขึ้นอยู่กับการใช้ขึ้นอยู่กับวิธี heapify และ shiftdown ซึ่งขึ้นอยู่กับผลรวมของความสูงของโหนดทั้งหมด ดังนั้นเมื่อต้องการหาผลรวมของความสูงของโหนดที่กำหนดโดย S = ผลรวมจาก i = 0 ถึง i = h ของ (2 ^ i * (hi)), โดยที่ h = logn คือความสูงของการแก้ต้นไม้ s = 2 ^ (h + 1) - 1 - (h + 1) ตั้งแต่, n = 2 ^ (h + 1) - 1 วินาที = n - h - 1 - n = logn - 1 s = O (n), และความซับซ้อนของ buildheap ก็คือ O (n)


0

"ขอบเขตเวลาเชิงเส้นของการสร้างฮีปสามารถแสดงได้โดยการคำนวณผลรวมของความสูงของโหนดทั้งหมดในฮีปซึ่งเป็นจำนวนสูงสุดของเส้นประ h + 1) - 1 โหนดผลรวมของความสูงของโหนดคือ N - H - 1 ดังนั้นจึงเป็น O (N) "


0

หลักฐานการ O (n)

การพิสูจน์ไม่ได้แฟนซีและตรงไปตรงมามากฉันพิสูจน์เฉพาะกรณีของต้นไม้ไบนารีเต็มผลที่ได้สามารถสรุปสำหรับต้นไม้ไบนารีที่สมบูรณ์


0

เราได้รับ runtime สำหรับ heap build โดยการหาจำนวนการย้ายสูงสุดที่แต่ละโหนดสามารถทำได้ ดังนั้นเราต้องรู้ว่ามีกี่โหนดในแต่ละแถวและแต่ละโหนดสามารถไปได้ไกลแค่ไหน

การเริ่มต้นจากโหนดรูทแต่ละแถวถัดไปจะมีโหนดเป็นสองเท่ากว่าแถวก่อนหน้าดังนั้นโดยการตอบว่าเราสามารถเพิ่มจำนวนโหนดเป็นสองเท่าได้จนกว่าเราจะไม่มีโหนดใด ๆ เหลือเราจึงได้ความสูงของต้นไม้ หรือในแง่คณิตศาสตร์ความสูงของต้นไม้คือ log2 (n) และ n คือความยาวของอาร์เรย์

ในการคำนวณโหนดในหนึ่งแถวที่เราเริ่มจากด้านหลังเรารู้ว่าโหนด n / 2 อยู่ที่ด้านล่างดังนั้นโดยการหารด้วย 2 เราจะได้แถวก่อนหน้าและต่อไป

จากนี้เราจะได้รับสูตรนี้สำหรับวิธี Siftdown: (0 * n / 2) + (1 * n / 4) + (2 * n / 8) + ... + (log2 (n) * 1)

คำใน paranthesis สุดท้ายคือความสูงของต้นไม้คูณด้วยโหนดเดียวที่อยู่ที่รากคำใน paranthesis แรกคือโหนดทั้งหมดในแถวล่างคูณด้วยความยาวที่พวกเขาสามารถเดินทางได้ 0 สูตรเดียวกันในสมาร์ท: ป้อนคำอธิบายรูปภาพที่นี่

คณิตศาสตร์

การนำ n กลับมาในเรามี 2 * n, 2 สามารถถูกละทิ้งได้เพราะค่าคงที่และธาดา


-6

คิดว่าคุณทำผิดพลาด ดูที่นี่: http://golang.org/pkg/container/heap/สร้างกอง isn'y O (n) อย่างไรก็ตามการแทรกคือ O (lg (n) ฉันถือว่าการเริ่มต้นเป็น O (n) ถ้าคุณตั้งค่าขนาดฮีป b / c ฮีปต้องจัดสรรพื้นที่และตั้งค่าโครงสร้างข้อมูลหากคุณมีรายการที่จะใส่ ในฮีปแล้วใช่แต่ละเม็ดมี lg (n) และมีไอเท็ม n รายการดังนั้นคุณจะได้ n * lg (n) ตามที่คุณระบุ


2
ไม่มันไม่แน่น การวิเคราะห์ที่เข้มงวดมากขึ้นของ build heap ให้ผลตอบแทน O (n)
emre nevayeshirazi

ดูเหมือนว่าเป็นการประมาณ คำพูดในห้องที่เขาอ้างถึงคือ "สัญชาตญาณคือส่วนใหญ่ของการเรียกร้องให้กองอยู่บนกองสั้นมาก" อย่างไรก็ตามสิ่งนี้ทำให้สมมติฐานบางอย่าง สมมุติว่าสำหรับกองใหญ่สถานการณ์กรณีที่เลวร้ายที่สุดจะยังคงเป็น O (n * lg (n)) แม้ว่าโดยปกติคุณจะเข้าใกล้ O (n) แต่ฉันอาจผิด
Mike Schachter

ใช่นั่นคือคำตอบที่ใช้งานง่ายของฉันเช่นกัน แต่การอ้างอิงเช่นวิกิพีเดียระบุว่า "กองที่มีองค์ประกอบ n สามารถสร้างจากล่างขึ้นบนใน O (n)"
GBa

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