เหตุใดเราจึงใช้อาร์เรย์แทนโครงสร้างข้อมูลอื่น


196

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

ดังนั้นโดยพื้นฐานแล้วประเด็นของการใช้อาร์เรย์คืออะไร?

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


2
ทำไมไม่ลองพิจารณาว่าคอมพิวเตอร์ทำอะไรกับอาเรย์ล่ะ? เรามีระบบบ้านเลขเพราะเรามีตรงถนน ดังนั้นสำหรับอาร์เรย์
lcn

" โครงสร้างข้อมูลอื่น " หรือ " รูปแบบอื่น " หมายถึงอะไร และมีวัตถุประสงค์อะไร?
tevemadar

คำตอบ:


771

ได้เวลาย้อนเวลากลับไปเพื่อบทเรียน แม้ว่าเราจะไม่คิดเกี่ยวกับสิ่งเหล่านี้ในภาษาที่มีการจัดการแฟนซีของเราในวันนี้ แต่พวกมันถูกสร้างบนพื้นฐานเดียวกันดังนั้นเรามาดูกันว่าการจัดการหน่วยความจำในซีเป็นอย่างไร

ก่อนที่ฉันจะดำน้ำคำอธิบายอย่างรวดเร็วของคำว่า " ตัวชี้ " หมายถึงอะไร ตัวชี้เป็นเพียงตัวแปรที่ "ชี้" ไปยังตำแหน่งในหน่วยความจำ มันไม่ได้มีค่าจริงในพื้นที่หน่วยความจำนี้ แต่ก็มีที่อยู่หน่วยความจำของมัน คิดว่าบล็อกหน่วยความจำเป็นกล่องจดหมาย ตัวชี้จะเป็นที่อยู่ไปยังกล่องจดหมายนั้น

ใน 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)

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


1
@Jonathan: คุณปรับปรุงไดอะแกรมให้ชี้ไปที่องค์ประกอบที่ 5 แต่คุณเปลี่ยน MyArray [4] เป็น MyArray [5] ดังนั้นจึงยังไม่ถูกต้องเปลี่ยนดัชนีกลับเป็น 4 และเก็บแผนภาพตามเดิมและคุณควรจะดี .
Robert Gamble

54
นี่คือสิ่งที่ฉันเกี่ยวกับข้อบกพร่อง "ชุมชนวิกิพีเดีย" โพสต์นี้มีมูลค่า "เหมาะสม" ตัวแทน
Quibblesome

8
คำตอบที่ดี แต่ต้นไม้ที่คุณอธิบายเป็นต้นไม้ค้นหาแบบทวิภาค - ต้นไม้แบบไบนารีเป็นเพียงต้นไม้ที่ทุกโหนดมีลูกสองคนมากที่สุด คุณสามารถมีต้นไม้ไบนารีพร้อมองค์ประกอบในลำดับใดก็ได้ แผนผังการค้นหาแบบไบนารีถูกจัดระเบียบตามที่คุณอธิบาย
gnud

1
คำอธิบายที่ดี แต่ฉันไม่สามารถช่วย nitpick ... ถ้าคุณได้รับอนุญาตให้เรียงลำดับรายการในแผนผังการค้นหาแบบไบนารี่ทำไมคุณไม่สามารถเรียงลำดับองค์ประกอบในอาร์เรย์อีกครั้งเพื่อให้การค้นหาแบบไบนารี่ทำงานด้วย คุณอาจเข้าไปดูรายละเอียดเพิ่มเติมเกี่ยวกับ O (n) insert / delete สำหรับ tree แต่ O (n) สำหรับ array
ตลาด

2
แผนภูมิต้นไม้ไบนารีแสดง O (log n) ไม่ใช่หรือเพราะเวลาในการเข้าถึงเพิ่มขึ้นแบบลอการิทึมโดยสัมพันธ์กับขนาดของชุดข้อมูลหรือไม่
Evan Plaice

73

สำหรับการเข้าถึงแบบสุ่ม O (1) ซึ่งไม่สามารถเอาชนะได้


6
จุดไหน O (1) คืออะไร การเข้าถึงแบบสุ่มคืออะไร ทำไมมันไม่ถูกตี ประเด็นอื่น ๆ ?
jason

3
O (1) หมายถึงเวลาคงที่ตัวอย่างเช่นหากคุณต้องการรับองค์ประกอบ n-esim ของอาร์เรย์คุณเพียงแค่เข้าถึงมันโดยตรงผ่านตัวสร้างดัชนี (อาร์เรย์ [n-1]) พร้อมรายการเชื่อมโยงตัวอย่างเช่นคุณมี เพื่อค้นหาส่วนหัวจากนั้นไปที่โหนดถัดไปตามลำดับ n-1 ซึ่งก็คือ O (n), เวลาเชิงเส้น
CMS

8
สัญลักษณ์ Big-O อธิบายว่าความเร็วของอัลกอริทึมแตกต่างกันไปตามขนาดของอินพุตอย่างไร อัลกอริทึม O (n) จะใช้เวลานานเป็นสองเท่าในการรันด้วยไอเท็มจำนวนมากเป็นสองเท่าและ 8ish เป็นเวลานานในการรันด้วยไอเท็มจำนวนมาก 8 เท่า กล่าวอีกนัยหนึ่งความเร็วของอัลกอริทึม O (n) จะแตกต่างกันไปตาม [ต่อ ... ]
Gareth

8
ขนาดของมันอินพุต O (1) หมายความว่าขนาดของอินพุต ('n') ไม่ได้คำนึงถึงความเร็วของอัลกอริธึม แต่เป็นความเร็วคงที่โดยไม่คำนึงถึงขนาดอินพุต
Gareth

9
ฉันเห็น O (1) ของคุณและทำให้คุณสูงขึ้น O (0)
Chris Conway

23

ไม่ใช่ทุกโปรแกรมที่ทำสิ่งเดียวกันหรือทำงานบนฮาร์ดแวร์เดียวกัน

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

ในการเขียนโปรแกรมธุรกิจ (ซึ่งส่วนใหญ่เราทำ) เราสามารถกำหนดเป้าหมายฮาร์ดแวร์ที่ค่อนข้างทรงพลัง การใช้ List ใน C # หรือ Vector ใน Java เป็นตัวเลือกที่เหมาะสมในสถานการณ์เหล่านี้เนื่องจากโครงสร้างเหล่านี้ช่วยให้นักพัฒนาสามารถบรรลุเป้าหมายได้เร็วขึ้นซึ่งจะช่วยให้ซอฟต์แวร์ประเภทนี้โดดเด่นมากขึ้น

เมื่อเขียนซอฟต์แวร์ฝังตัวหรือระบบปฏิบัติการอาเรย์อาจเป็นตัวเลือกที่ดีกว่า ในขณะที่อาร์เรย์มีฟังก์ชั่นการใช้งานที่น้อยลงมันใช้ RAM น้อยลงและคอมไพเลอร์สามารถปรับรหัสให้มีประสิทธิภาพมากขึ้นสำหรับการค้นหาในอาร์เรย์

ฉันแน่ใจว่าฉันจะทิ้งผลประโยชน์มากมายสำหรับกรณีเหล่านี้ แต่ฉันหวังว่าคุณจะได้รับคะแนน


4
อย่างแดกดันใน Java คุณควรใช้ ArrayList (หรือ LinkedList) แทน Vector สิ่งนี้จะทำอย่างไรกับเวกเตอร์ที่ถูกซิงโครไนซ์ซึ่งโดยทั่วไปจะไม่จำเป็น
ashirley

0

วิธีดูข้อดีของอาเรย์คือการดูความสามารถในการเข้าถึง O (1) ของอาเรย์ที่ต้องใช้

  1. ในตาราง Look-up ของแอปพลิเคชั่นของคุณ (อาเรย์แบบสแตติกสำหรับการเข้าถึงการตอบกลับอย่างเด็ดขาด)

  2. การบันทึก (คำนวณผลการทำงานที่ซับซ้อนแล้วเพื่อให้คุณไม่คำนวณค่าฟังก์ชันอีกครั้งพูด log x)

  3. แอปพลิเคชันการมองเห็นด้วยคอมพิวเตอร์ความเร็วสูงที่ต้องการการประมวลผลภาพ ( https://en.wikipedia.org/wiki/Lookup_table#Lookup_tables_in_image_processing )

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