โดยสังเขปคุณสามารถนึกถึงต้นไม้ที่มีการทำดัชนีแบบไบนารีเป็นตัวแทนที่ถูกบีบอัดของต้นไม้แบบไบนารีที่เป็นการเพิ่มประสิทธิภาพของการเป็นตัวแทนอาร์เรย์มาตรฐานเอง คำตอบนี้จะเป็นไปได้หนึ่งที่มา
ตัวอย่างเช่นสมมติว่าคุณต้องการเก็บความถี่สะสมสำหรับองค์ประกอบต่าง ๆ ทั้งหมด 7 รายการ คุณสามารถเริ่มต้นด้วยการเขียนเจ็ดถังลงในหมายเลขที่จะกระจาย:
[ ] [ ] [ ] [ ] [ ] [ ] [ ]
1 2 3 4 5 6 7
ทีนี้สมมติว่าความถี่สะสมมีลักษณะดังนี้:
[ 5 ] [ 6 ] [14 ] [25 ] [77 ] [105] [105]
1 2 3 4 5 6 7
การใช้อาเรย์รุ่นนี้คุณสามารถเพิ่มความถี่สะสมขององค์ประกอบใด ๆ โดยการเพิ่มมูลค่าของจำนวนที่เก็บไว้ ณ จุดนั้นจากนั้นเพิ่มความถี่ของทุกสิ่งที่เกิดขึ้นภายหลัง ตัวอย่างเช่นในการเพิ่มความถี่สะสมของ 3 ด้วย 7 เราสามารถเพิ่ม 7 ให้กับแต่ละองค์ประกอบในอาร์เรย์ที่หรือหลังตำแหน่ง 3 ดังที่แสดงที่นี่:
[ 5 ] [ 6 ] [21 ] [32 ] [84 ] [112] [112]
1 2 3 4 5 6 7
ปัญหาของเรื่องนี้คือมันใช้เวลา O (n) ในการทำสิ่งนี้ซึ่งค่อนข้างช้าถ้า n มีขนาดใหญ่
วิธีหนึ่งที่เราสามารถคิดเกี่ยวกับการปรับปรุงการดำเนินการนี้คือการเปลี่ยนแปลงสิ่งที่เราเก็บไว้ในถัง แทนที่จะเก็บความถี่สะสมจนถึงจุดที่กำหนดคุณสามารถนึกถึงเพียงแค่เก็บจำนวนที่ความถี่ปัจจุบันเพิ่มขึ้นเมื่อเทียบกับที่เก็บข้อมูลชุดก่อนหน้า ตัวอย่างเช่นในกรณีของเราเราจะเขียนถังด้านบนอีกครั้งดังนี้:
Before:
[ 5 ] [ 6 ] [21 ] [32 ] [84 ] [112] [112]
1 2 3 4 5 6 7
After:
[ +5] [ +1] [+15] [+11] [+52] [+28] [ +0]
1 2 3 4 5 6 7
ตอนนี้เราสามารถเพิ่มความถี่ภายในที่เก็บข้อมูลในเวลา O (1) โดยเพิ่มจำนวนที่เหมาะสมลงในที่เก็บข้อมูลนั้น อย่างไรก็ตามค่าใช้จ่ายโดยรวมในการค้นหาตอนนี้กลายเป็น O (n) เนื่องจากเราต้องคำนวณผลรวมทั้งหมดในที่เก็บข้อมูลอีกครั้งโดยการสรุปค่าในถังเล็กทั้งหมด
ความเข้าใจที่สำคัญครั้งแรกที่เราจำเป็นต้องได้รับจากที่นี่ไปยังแผนภูมิดัชนีแบบไบนารีคือต่อไปนี้: แทนที่จะคำนวณผลรวมขององค์ประกอบอาร์เรย์ที่นำหน้าองค์ประกอบเฉพาะอย่างต่อเนื่องจะเกิดอะไรขึ้นถ้าเราต้องคำนวณผลรวมทั้งหมดขององค์ประกอบทั้งหมดก่อน คะแนนในลำดับ? หากเราสามารถทำเช่นนั้นได้เราสามารถหาผลรวมสะสม ณ จุดหนึ่งเพียงแค่สรุปการรวมกันที่ถูกต้องของผลรวมที่คำนวณไว้ล่วงหน้าเหล่านี้
วิธีหนึ่งในการทำเช่นนี้คือการเปลี่ยนการแสดงจากการเป็นอาร์เรย์ของที่เก็บข้อมูลเป็นแบบต้นไม้ไบนารีของโหนด แต่ละโหนดจะถูกเพิ่มความคิดเห็นด้วยค่าที่แสดงถึงผลรวมสะสมของโหนดทั้งหมดทางด้านซ้ายของโหนดที่กำหนด ตัวอย่างเช่นสมมติว่าเราสร้างต้นไม้ไบนารีต่อไปนี้จากโหนดเหล่านี้:
4
/ \
2 6
/ \ / \
1 3 5 7
ตอนนี้เราสามารถเพิ่มแต่ละโหนดโดยการเก็บผลรวมสะสมของค่าทั้งหมดรวมถึงโหนดนั้นและทรีย่อยด้านซ้าย ตัวอย่างเช่นด้วยค่านิยมของเราเราจะจัดเก็บสิ่งต่อไปนี้:
Before:
[ +5] [ +1] [+15] [+11] [+52] [+28] [ +0]
1 2 3 4 5 6 7
After:
4
[+32]
/ \
2 6
[ +6] [+80]
/ \ / \
1 3 5 7
[ +5] [+15] [+52] [ +0]
ด้วยโครงสร้างต้นไม้นี้มันง่ายที่จะหาผลรวมสะสมจนถึงจุด แนวคิดมีดังต่อไปนี้: เรารักษาตัวนับ 0 เริ่มแรกจากนั้นทำการค้นหาไบนารีตามปกติจนกระทั่งเราพบโหนดที่มีปัญหา ในขณะที่เราทำเช่นนั้นเราก็มีสิ่งต่อไปนี้เช่นกันทุกครั้งที่เราย้ายไปทางขวาเรายังเพิ่มมูลค่าปัจจุบันไปยังตัวนับ
ตัวอย่างเช่นสมมติว่าเราต้องการค้นหาผลรวมของ 3. เมื่อต้องการทำเช่นนั้นเราจะทำสิ่งต่อไปนี้:
- เริ่มต้นที่รูท (4) ตัวนับคือ 0
- ไปทางซ้ายเพื่อโหนด (2) ตัวนับคือ 0
- ไปทางขวาเพื่อโหนด (3) ตัวนับคือ 0 + 6 = 6
- ค้นหาโหนด (3) ตัวนับคือ 6 + 15 = 21
คุณอาจจินตนาการได้ว่ากำลังรันกระบวนการนี้ในสิ่งที่ตรงกันข้าม: เริ่มต้นที่โหนดที่กำหนดเริ่มต้นตัวนับให้เป็นค่าของโหนดนั้นจากนั้นเดินขึ้นต้นไม้ไปที่รูท เมื่อใดก็ตามที่คุณติดตามลิงก์ลูกที่เหมาะสมขึ้นให้เพิ่มมูลค่าที่โหนดที่คุณไปถึง ตัวอย่างเช่นเพื่อค้นหาความถี่สำหรับ 3 เราสามารถทำสิ่งต่อไปนี้:
- เริ่มต้นที่โหนด (3) ตัวนับคือ 15
- ขึ้นไปที่โหนด (2) ตัวนับคือ 15 + 6 = 21
- ขึ้นไปที่โหนด (4) ตัวนับคือ 21
ในการเพิ่มความถี่ของโหนด (และโดยปริยายความถี่ของโหนดทั้งหมดที่มาหลังจากนั้น) เราจำเป็นต้องอัปเดตชุดของโหนดในต้นไม้ที่รวมโหนดนั้นไว้ในทรีย่อยทางซ้าย ในการทำเช่นนี้เราทำสิ่งต่อไปนี้: เพิ่มความถี่สำหรับโหนดนั้นจากนั้นเริ่มเดินขึ้นไปที่รากของต้นไม้ เมื่อใดก็ตามที่คุณติดตามลิงก์จะนำคุณไปสู่การเป็นลูกซ้ายเพิ่มความถี่ของโหนดที่คุณพบโดยการเพิ่มค่าปัจจุบัน
ตัวอย่างเช่นหากต้องการเพิ่มความถี่ของโหนด 1 ถึงห้าเราจะทำสิ่งต่อไปนี้:
4
[+32]
/ \
2 6
[ +6] [+80]
/ \ / \
> 1 3 5 7
[ +5] [+15] [+52] [ +0]
เริ่มต้นที่โหนด 1 เพิ่มความถี่โดย 5 เพื่อรับ
4
[+32]
/ \
2 6
[ +6] [+80]
/ \ / \
> 1 3 5 7
[+10] [+15] [+52] [ +0]
ตอนนี้ไปที่แม่ของมัน:
4
[+32]
/ \
> 2 6
[ +6] [+80]
/ \ / \
1 3 5 7
[+10] [+15] [+52] [ +0]
เราติดตามลิงก์ลูกด้านซ้ายขึ้นด้านบนดังนั้นเราจึงเพิ่มความถี่ของโหนดนี้เช่นกัน:
4
[+32]
/ \
> 2 6
[+11] [+80]
/ \ / \
1 3 5 7
[+10] [+15] [+52] [ +0]
ตอนนี้เราไปที่แม่ของมัน:
> 4
[+32]
/ \
2 6
[+11] [+80]
/ \ / \
1 3 5 7
[+10] [+15] [+52] [ +0]
นั่นคือลิงก์ลูกซ้ายดังนั้นเราจึงเพิ่มโหนดนี้เช่นกัน:
4
[+37]
/ \
2 6
[+11] [+80]
/ \ / \
1 3 5 7
[+10] [+15] [+52] [ +0]
และตอนนี้เราเสร็จแล้ว!
ขั้นตอนสุดท้ายคือการแปลงจากนี้เป็นต้นไม้ที่ทำดัชนีแบบไบนารีและนี่คือที่ที่เราได้ทำสิ่งที่สนุกกับตัวเลขไบนารี ลองเขียนดัชนีบุ๊กมาร์กแต่ละอันในทรีนี้ด้วยไบนารี:
100
[+37]
/ \
010 110
[+11] [+80]
/ \ / \
001 011 101 111
[+10] [+15] [+52] [ +0]
ที่นี่เราสามารถสังเกตได้อย่างยอดเยี่ยมมาก รับเลขฐานสองใด ๆ เหล่านี้แล้วหา 1 ตัวสุดท้ายที่ตั้งไว้ในจำนวนนั้นจากนั้นดรอปบิตนั้นพร้อมกับบิตทั้งหมดที่ตามหลังมา ตอนนี้คุณจะเหลือดังนี้:
(empty)
[+37]
/ \
0 1
[+11] [+80]
/ \ / \
00 01 10 11
[+10] [+15] [+52] [ +0]
นี่คือข้อสังเกตที่เจ๋งจริง ๆ : ถ้าคุณให้ 0 หมายถึง "ซ้าย" และ 1 หมายถึง "ถูกต้อง" บิตที่เหลืออยู่ในแต่ละตัวเลขจะสะกดวิธีการเริ่มต้นที่รูทแล้วจึงเดินลงไปที่หมายเลขนั้น ตัวอย่างเช่นโหนด 5 มีรูปแบบไบนารี 101 1 อันสุดท้ายคือบิตสุดท้ายดังนั้นเราจึงลดลงมาเป็น 10 แน่นอนถ้าคุณเริ่มที่รูตไปทางขวา (1) จากนั้นไปทางซ้าย (0) คุณสิ้นสุด ขึ้นที่โหนด 5!
เหตุผลที่สำคัญคือการดำเนินการค้นหาและอัปเดตของเรานั้นขึ้นอยู่กับเส้นทางการเข้าถึงจากโหนดกลับสู่รูทและไม่ว่าเราจะติดตามลิงก์ลูกซ้ายหรือขวา ตัวอย่างเช่นระหว่างการค้นหาเราแค่ใส่ใจในลิงก์ที่ถูกต้องที่เราติดตาม ในระหว่างการอัปเดตเราแค่แคร์ลิงก์ด้านซ้ายที่เราติดตาม ทรีดัชนีแบบไบนารีนี้ทำสุดยอดทั้งหมดนี้ได้อย่างมีประสิทธิภาพเพียงแค่ใช้บิตในดัชนี
เคล็ดลับที่สำคัญคือคุณสมบัติต่อไปนี้ของต้นไม้ไบนารีที่สมบูรณ์แบบนี้:
รับโหนด n, โหนดถัดไปบนเส้นทางการเข้าถึงกลับไปยังรากที่เราไปทางขวาจะได้รับโดยการเป็นตัวแทนไบนารีของ n และลบ 1 สุดท้าย
ตัวอย่างเช่นลองดูที่เส้นทางการเข้าถึงสำหรับโหนด 7 ซึ่งเป็น 111 โหนดบนเส้นทางการเข้าถึงไปยังรากที่เราใช้ที่เกี่ยวข้องกับการติดตามตัวชี้ขวาขึ้นไปคือ
- โหนด 7: 111
- โหนด 6: 110
- โหนด 4: 100
ทั้งหมดนี้คือลิงก์ที่ถูกต้อง หากเราใช้เส้นทางการเข้าถึงสำหรับโหนด 3 ซึ่งเป็น 011 และดูที่โหนดที่เราไปทางขวาเราจะได้รับ
- โหนด 3: 011
- โหนด 2: 010
- (โหนด 4: 100 ซึ่งตามลิงค์ด้านซ้าย)
ซึ่งหมายความว่าเราสามารถคำนวณผลรวมสะสมได้อย่างมีประสิทธิภาพมากถึงโหนดดังนี้
- เขียนโหนด n ในไบนารี
- ตั้งค่าตัวนับเป็น 0
- ทำซ้ำต่อไปนี้ในขณะที่ n ≠ 0:
- เพิ่มค่าที่โหนด n
- ล้างขวาสุด 1 บิตจาก n
ลองคิดดูว่าเราจะทำขั้นตอนการอัพเดทอย่างไร ในการทำเช่นนี้เราจะต้องติดตามเส้นทางการเข้าถึงกลับไปที่รูทอัปเดตโหนดทั้งหมดที่เราติดตามลิงค์ด้านซ้ายขึ้น เราสามารถทำได้โดยการทำอัลกอริทึมด้านบน แต่การสลับ 1 ทั้งหมดเป็น 0 และ 0 เป็น 1
ขั้นตอนสุดท้ายในทรีดัชนีแบบไบนารีคือให้สังเกตว่าเนื่องจากกลอุบาย bitwise นี้เราไม่จำเป็นต้องมีต้นไม้ที่เก็บไว้อย่างชัดเจนอีกต่อไป เราสามารถเก็บโหนดทั้งหมดไว้ในอาร์เรย์ที่มีความยาว n จากนั้นใช้เทคนิคการบิดสองบิตเพื่อนำทางต้นไม้โดยปริยาย ในความเป็นจริงนั่นคือสิ่งที่ต้นไม้ทำดัชนี bitwise - มันเก็บโหนดในอาร์เรย์จากนั้นใช้เทคนิค bitwise เหล่านี้เพื่อจำลองการเดินขึ้นบนต้นไม้นี้ได้อย่างมีประสิทธิภาพ
หวังว่านี่จะช่วยได้!