ได้เวลาย้อนเวลากลับไปเพื่อบทเรียน แม้ว่าเราจะไม่คิดเกี่ยวกับสิ่งเหล่านี้ในภาษาที่มีการจัดการแฟนซีของเราในวันนี้ แต่พวกมันถูกสร้างบนพื้นฐานเดียวกันดังนั้นเรามาดูกันว่าการจัดการหน่วยความจำในซีเป็นอย่างไร
ก่อนที่ฉันจะดำน้ำคำอธิบายอย่างรวดเร็วของคำว่า " ตัวชี้ " หมายถึงอะไร ตัวชี้เป็นเพียงตัวแปรที่ "ชี้" ไปยังตำแหน่งในหน่วยความจำ มันไม่ได้มีค่าจริงในพื้นที่หน่วยความจำนี้ แต่ก็มีที่อยู่หน่วยความจำของมัน คิดว่าบล็อกหน่วยความจำเป็นกล่องจดหมาย ตัวชี้จะเป็นที่อยู่ไปยังกล่องจดหมายนั้น
ใน C อาร์เรย์เป็นเพียงตัวชี้ที่มีอ็อฟเซ็ตอ็อฟเซ็ตระบุระยะเวลาในการมองของหน่วยความจำ ให้เวลาเข้าถึงO (1)
MyArray [5]
^ ^
Pointer Offset
โครงสร้างข้อมูลอื่น ๆ ทั้งหมดสร้างขึ้นบนสิ่งนี้หรือไม่ใช้หน่วยความจำที่อยู่ติดกันเพื่อเก็บข้อมูลทำให้เวลาในการค้นหาแบบสุ่มเข้าถึงไม่ดี (แม้ว่าจะมีประโยชน์อื่น ๆ ที่จะไม่ใช้หน่วยความจำตามลำดับ)
ตัวอย่างเช่นสมมติว่าเรามีอาร์เรย์ที่มีตัวเลข 6 ตัว (6,4,2,3,1,5) ในนั้นในหน่วยความจำมันจะมีลักษณะเช่นนี้:
=====================================
| 6 | 4 | 2 | 3 | 1 | 5 |
=====================================
ในอาร์เรย์เรารู้ว่าแต่ละองค์ประกอบอยู่ติดกันในหน่วยความจำ AC array (เรียกว่าMyArray
ที่นี่) เป็นเพียงตัวชี้ไปยังองค์ประกอบแรก:
=====================================
| 6 | 4 | 2 | 3 | 1 | 5 |
=====================================
^
MyArray
หากเราต้องการค้นหาMyArray[4]
ภายในจะสามารถเข้าถึงได้เช่นนี้:
0 1 2 3 4
=====================================
| 6 | 4 | 2 | 3 | 1 | 5 |
=====================================
^
MyArray + 4 ---------------/
(Pointer + Offset)
เนื่องจากเราสามารถเข้าถึงองค์ประกอบใด ๆ ในอาเรย์ได้โดยตรงโดยการเพิ่มออฟเซ็ตไปยังตัวชี้เราจึงสามารถค้นหาองค์ประกอบใด ๆ ในเวลาเดียวกันโดยไม่คำนึงถึงขนาดของอาเรย์ ซึ่งหมายความว่าการจะใช้เวลาเท่ากันเวลาที่ได้รับMyArray[1000]
MyArray[5]
โครงสร้างข้อมูลทางเลือกเป็นรายการที่เชื่อมโยง นี่เป็นรายการเชิงเส้นของพอยน์เตอร์แต่ละตัวชี้ไปที่โหนดถัดไป
======== ======== ======== ======== ========
| Data | | Data | | Data | | Data | | Data |
| | -> | | -> | | -> | | -> | |
| P1 | | P2 | | P3 | | P4 | | P5 |
======== ======== ======== ======== ========
P(X) stands for Pointer to next node.
โปรดสังเกตว่าฉันสร้าง "โหนด" แต่ละอันลงในบล็อกของตัวเอง นี่เป็นเพราะพวกเขาไม่รับประกันว่าจะ (และส่วนใหญ่จะไม่ติดกัน) ในหน่วยความจำ
ถ้าฉันต้องการเข้าถึง P3 ฉันไม่สามารถเข้าถึงได้โดยตรงเพราะฉันไม่รู้ว่ามันอยู่ที่ไหนในหน่วยความจำ ทั้งหมดที่ฉันรู้คือที่ที่รูท (P1) อยู่ดังนั้นฉันต้องเริ่มต้นที่ P1 และทำตามตัวชี้แต่ละตัวไปยังโหนดที่ต้องการ
นี่คือเวลาค้นหา O (N) (ค่าใช้จ่ายการค้นหาเพิ่มขึ้นเมื่อมีการเพิ่มองค์ประกอบแต่ละรายการ) มันแพงกว่ามากที่จะได้รับ P1000 เทียบกับการไปที่ P4
โครงสร้างข้อมูลระดับสูงเช่น hashtables สแต็คและคิวทั้งหมดอาจใช้อาร์เรย์ (หรือหลายอาร์เรย์) ภายในขณะที่รายการที่เชื่อมโยงและต้นไม้ไบนารีมักจะใช้โหนดและพอยน์เตอร์
คุณอาจสงสัยว่าทำไมทุกคนจะใช้โครงสร้างข้อมูลที่ต้องใช้การสำรวจเส้นทางเชิงเส้นเพื่อค้นหาค่าแทนที่จะใช้แค่อาร์เรย์ แต่มีประโยชน์
รับอาร์เรย์ของเราอีกครั้ง เวลานี้ฉันต้องการค้นหาองค์ประกอบอาร์เรย์ที่เก็บค่า '5'
=====================================
| 6 | 4 | 2 | 3 | 1 | 5 |
=====================================
^ ^ ^ ^ ^ FOUND!
ในสถานการณ์นี้ฉันไม่รู้ว่าจะเพิ่มออฟเซ็ตลงในพอยเตอร์เพื่อหาอะไรดังนั้นฉันจึงต้องเริ่มต้นที่ 0 และทำงานต่อจนเจอ หมายความว่าฉันต้องทำการตรวจสอบ 6 ครั้ง
ด้วยเหตุนี้การค้นหาค่าในอาร์เรย์จึงถือเป็น O (N) ค่าใช้จ่ายในการค้นหาเพิ่มขึ้นเมื่ออาร์เรย์มีขนาดใหญ่ขึ้น
จำไว้ข้างต้นที่ฉันบอกว่าบางครั้งการใช้โครงสร้างข้อมูลที่ไม่ต่อเนื่องอาจมีข้อดี การค้นหาข้อมูลเป็นหนึ่งในข้อดีเหล่านี้และหนึ่งในตัวอย่างที่ดีที่สุดคือต้นไม้ไบนารี
ต้นไม้ไบนารีเป็นโครงสร้างข้อมูลที่คล้ายกับรายการที่เชื่อมโยงอย่างไรก็ตามแทนที่จะเชื่อมโยงกับโหนดเดียวแต่ละโหนดสามารถเชื่อมโยงกับโหนดลูกสองโหนด
==========
| Root |
==========
/ \
========= =========
| Child | | Child |
========= =========
/ \
========= =========
| Child | | Child |
========= =========
Assume that each connector is really a Pointer
เมื่อข้อมูลถูกแทรกลงในแผนผังต้นไม้จะใช้กฎหลายข้อในการตัดสินใจว่าจะวางโหนดใหม่ แนวคิดพื้นฐานคือถ้าค่าใหม่มากกว่าผู้ปกครองก็แทรกไปทางซ้ายถ้ามันต่ำกว่าก็แทรกไปทางขวา
ซึ่งหมายความว่าค่าในต้นไม้ไบนารีอาจมีลักษณะเช่นนี้:
==========
| 100 |
==========
/ \
========= =========
| 200 | | 50 |
========= =========
/ \
========= =========
| 75 | | 25 |
========= =========
เมื่อค้นหาต้นไม้ไบนารีสำหรับค่า 75 เราจะต้องไป 3 โหนด (O (log N)) เนื่องจากโครงสร้างนี้:
- 75 น้อยกว่า 100 หรือไม่ ดูที่โหนดขวา
- 75 มากกว่า 50 หรือไม่? ดูโหนดซ้าย
- มี 75!
แม้ว่าจะมี 5 โหนดในต้นไม้ของเราเราไม่จำเป็นต้องดูสองส่วนที่เหลือเพราะเรารู้ว่าพวกเขา (และลูก ๆ ของพวกเขา) อาจไม่มีค่าที่เรากำลังมองหา สิ่งนี้ทำให้เรามีเวลาค้นหาว่าในกรณีที่เลวร้ายที่สุดหมายความว่าเราต้องไปทุกโหนด แต่ในกรณีที่ดีที่สุดเราต้องไปที่โหนดเพียงส่วนเล็ก ๆ เท่านั้น
นั่นคือที่อาร์เรย์ได้รับการตีพวกเขาให้เวลาการค้นหาเชิงเส้น O (N) แม้จะมีเวลาเข้าถึง O (1)
นี่เป็นภาพรวมระดับสูงอย่างไม่น่าเชื่อในโครงสร้างข้อมูลในหน่วยความจำข้ามรายละเอียดมากมาย แต่หวังว่ามันจะแสดงให้เห็นถึงความแข็งแกร่งและจุดอ่อนของอาเรย์เมื่อเทียบกับโครงสร้างข้อมูลอื่น ๆ