830 likes | 919 Views
418115: การโปรแกรมเชิงโครงสร้าง โครงสร้างข้อมูล 1. ประมุข ขันเงิน. Abstract Data Type. Abstract Data Type. ชนิดข้อมูลนามธรรม (Abstract Data Type ย่อว่า ADT) ประกอบด้วย กลุ่มของข้อมูล วิธีการจัดการกับกลุ่มของข้อมูลนี้ เน้นว่าเรา สามารถทำอะไรได้กับกลุ่มข้อมูล ได้บ้าง
E N D
418115: การโปรแกรมเชิงโครงสร้างโครงสร้างข้อมูล 1 ประมุข ขันเงิน
Abstract Data Type • ชนิดข้อมูลนามธรรม(Abstract Data Type ย่อว่า ADT) ประกอบด้วย • กลุ่มของข้อมูล • วิธีการจัดการกับกลุ่มของข้อมูลนี้ • เน้นว่าเราสามารถทำอะไรได้กับกลุ่มข้อมูลได้บ้าง • ไม่ได้เน้นว่าเราจะเขียนโปรแกรมเพื่อให้จัดการข้อมูลนี้ได้อย่างไร
ตัวอย่าง: เซต • สัญลักษณ์: S • ข้อมูลที่เก็บ: • ของชนิดเดียวกัน (int, float, char, ฯลฯ) หลายๆ ตัว • ข้อมูลที่แตกต่างกันมีได้เพียงตัวเดียว • ตัวอย่าง: {1, 2, 5} • ความสามารถ: • add(S, x): เพิ่ม x เข้าใน S • remove(S, x): ลบ x ออกจาก S • contains(S, x): ถามว่า x เป็นสมาชิกของ S หรือไม่?
ตัวอย่าง: เซต • สมมติเราสนใจเซต S = {1,2,3} • contains(S, 3) true • contains(S, 4) false • add( S, 4) • ได้ S = {1,2,3,4} • add(S, 5) • ได้ S = {1,2,3,4,5} • remove(S, 2) • ได้ S = {1,3,4,5} • contains(S, 2) false
ตัวอย่าง: ลิสต์ (List) • ใช้แทนลำดับ (a0, a1, a2, …, an-1) ของข้อมูล • สมาชิกลำดับสามารถซ้ำกันได้ • ความสามารถ: • get(L, i): คืนสมาชิกตัวที่ i • set(L, i, x): ทำให้สมาชิกตัวที่ iกลายเป็น x • find(L, x): หาตำแหน่งของ x ใน L ถ้าไม่เจอคืน -1 ถ้ามีหลายตัวคืนตัวหน้าสุด • insert(L, i, x): เพิ่ม x เข้าไปใน iโดยทำให้มันเป็นสมาชิกตัวที่ iหลังจากแทนเสร็จแล้ว • remove(i): ลบสมาชิกตัวที่ iออก
ตัวอย่าง: ลิสต์ (List) • สมมติว่า L = (7,2,1,3,9) • get(L, 0) 7 และ get(L, 4) 9 • set(L, 1, 10) • ได้ L = (7, 10, 1, 3, 9) • insert(L, 2, 9) • ได้ L = (7, 10, 9, 1, 3, 9) • remove(L, 4) • ได้ L = (7, 10, 9, 1, 9) • find(L, 10) 1 และ find(L, 9) 2 และ find(L, 2) -1
ตัวอย่าง: สแตก (Stack) • ความสามารถ • push(S, x): ใส่ข้อมูลเข้าไปวางไว้บน “หัว” ของ stack • pop(S): เอาเข้ามูลที่อยู่ที่ “หัว” ออกมา • จำได้ไหม? • stack เป็นหน่วยความจำชนิดหนึ่ง (stack segment) • มีวิธีการจัดการข้อมูลเหมือนกับ stack เลย
ตัวอย่าง: สแตก (Stack) • สมมติตอนแรก S = () stack ว่าง • push(S, 5) • ได้ S = (5) • push(S, 3) • ได้ S = (5, 3) • push(S, 7) • ได้ S = (5, 3, 7) • pop(S) 7 • ได้ S = (5, 3) • push(S, 20) • ได้ S = (5, 3, 20)
ตัวอย่าง: คิว (Queue) • ความสามารถ • enqueue(Q, x): ใส่ข้อมูลเข้าไปวางไว้บน “ท้าย” ของ queue • dequeue(Q): เอาเข้ามูลที่อยู่ที่ “หัว” queue ออกมา • พบกันทั่วไปในชิวิตประจำวัน • แต่ไร้คุณค่าทางคณิตศาสตร์
ตัวอย่าง: คิว (Queue) • สมมติ Q = () คิวว่าง • enqueue(Q, 5) • Q = (5) • enqueue(Q, 3) • Q = (5, 3) • enqueue(Q, 7) • Q = (5, 3, 7) • dequeue(Q) 5 • Q = (3, 7) • enqueue(Q, 20) • Q = (3, 7, 20) • dequeue(Q) 3 • Q = (7, 20)
ทำไมเราต้องพูดถึง ADT? • เรามักจะเจอรูปแบบ(pattern) ในการจัดการข้อมูล • รูปแบบที่เจอบ่อยๆ เราจะสรุปมาเป็น ADT • ประโยชน์ • มีคำศัพท์เวลาเอาไปใช้คุยกับคนอื่น • เวลาอธิบายอัลกอริทึมจะได้อธิบายสั้นๆ • เปิดโอกาสให้เราเขียนโปรแกรมได้หลายๆ แบบ • เพราะ ADT ไม่พูดถึงวิธีการเขียนโปแกรม • เขียนยังไงก็ได้ให้มันทำงานได้ตามนั้นพอ
โครงสร้างข้อมูล (Data Structure) • โครงสร้างข้อมูล คือ วิธีการเขียนโปรแกรมให้สอดคล้องกับความต้องการของ ADT • เวลาพูดถึงโครงสร้างข้อมูล เราจะพูดถึง • วิธีการก็บข้อมูลในหน่วยความจำ • วิธีการทำปฏิบัติการต่างๆ ของ ADT
โครงสร้างข้อมูลและ ADT • ADT หนึ่งมีโครงสร้างข้อมูลหลายตัวที่ทำงานเป็นมันได้ • โครงสร้างข้อมูลหนึ่งก็สามารถทำงานเป็น ADT หลายตัวได้ • โครงสร้างข้อมูลแต่ละตัวมีความแตกต่างกัน • ความเร็วของปฏิบัติการต่างๆ • ขนาดหน่วยความจำที่ใช้
อะเรย์และอะเรย์ปรับขนาดได้อะเรย์และอะเรย์ปรับขนาดได้
อะเรย์ • อะเรย์ คือ ข้อมูลที่ถูกเรียงติดกันเป็นพืดๆ ในหน่วยความจำ • ในภาษา C เราเข้าถึงสมาชิกตัวที่ iของอะเรย์ A ด้วยนิพจน์ A[i] • การเข้าถึง A[i] ทำได้อย่างรวดเร็ว (เสียเวลาหนึ่งหน่วย) • ขนาดของอะเรย์ในภาษา C มักจะถูกกำหนดไว้ตายตัว
อะเรย์ที่ปรับขนาดได้ • อะเรย์ที่เราใช้ที่ผ่านมาต้องกำหนดขนาดล่วงหน้าไว้ใน code • ไม่สามารถเปลี่ยนแปลงขนาดได้ตอนโปรแกรมรันไปแล้ว • ด้วย dynamic memory allocation เราสามารถสร้างอะเรย์ที่เราสามารถยืดหรือหดขนาดมันได้ • อย่างไรก็ดีการเข้าถึงสมาชิกต่างๆ ในอะเรย์ เราจะทำผ่านฟังก์ชัน • เข้าถึงตรงๆ ก็ได้ แต่ไม่ปลอดภัย
IntArray • เราจะสร้างอะเรย์สำหรับเก็บค่าประเภท intที่เราสามารถกำหนดขนาดเองเวลาโปรแกรมรัน • อะเรย์นี้เก็บอยู่ใน structต่อไปนี้ typedefstruct{ intcapacity; int*data; }IntArray;
IntArray • data คือ pointer ไปยังสมาชิกตัวแรกในอะเรย์ • capacity คือขนาดของอะเรย์ที่จองไว้ • ข้อมูลของอะเรย์จะอยู่ที่ data[0], data[1], …, data[capacity-1]
IntArray Functions • voidIntArray_alloc(IntArray*a,intcapacity) • ทำให้อะเรย์มีขนาดเท่ากับ capacity • intIntArray_get(IntArray*a,inti) • คืนสมาชิกตัวที่ iของอะเรย์ a • voidIntArray_set(IntArray*a,inti,intx) • ทำให้สมาชิกตัวที่ i ของอะเรย์ a มีค่าเท่ากับ x • voidIntArray_clear(IntArray*a) • ยกเลิกการจองพื้นที่ใน heap ของอะเรย์ • voidIntArray_init(IntArray*a) • ตั้งแต่เริ่มต้นให้อะเรย์ a กล่าวคือเซตค่า size = capacity = 0 และ data = NULL
IntArray_init voidIntArray_init(IntArray*a) { a->size=0; a->capacity=0; a->data=NULL; }
IntArray_get intIntArray_get(IntArray*a,inti) { returna->data[i]; }
IntArray_set voidIntArray_set(IntArray*a,inti,intx) { a->data[i]=x; }
IntArray_alloc voidIntArray_alloc(IntArray*a,intcapacity) { if(capacity==0){ if(a->data!=NULL){ free(a->data); a->data=NULL; } a->capacity=0; } elseif(capacity>0){ int*new_data; if(a->data!=NULL) new_data=(int*)realloc(a->data,sizeof(int)*capacity); else new_data=(int*)malloc(sizeof(int)*capacity); if(new_data!=NULL) a->data=new_data; else exit(1); a->capacity=capacity; } }
IntArray_set voidIntArray_clear(IntArray*a) { IntArray_alloc(a,0); }
ตัวอย่างการใช้งาน intmain() { IntArrayA; IntArray_alloc(&A,3); IntArray_set(&A,1,5); IntArray_set(&A,1,3); IntArray_set(&A,1,7); printf("A[1] = %d\n",IntArray_get(&A,1)); IntArray_set(&A,1,10); printf("A[1] = %d\n",IntArray_get(&A,1)); IntArray_clear(&A); return0; }
ทวนความจำ: ลิสต์ • ใช้แทนลำดับ (a0, a1, a2, …, an-1) ของข้อมูล • สมาชิกลำดับสามารถซ้ำกันได้ • ความสามารถ: • get(L, i): คืนสมาชิกตัวที่ i • set(L, i, x): ทำให้สมาชิกตัวที่ iกลายเป็น x • find(L, x): หาตำแหน่งของ x ใน L ถ้าไม่เจอคืน -1 ถ้ามีหลายตัวคืนตัวหน้าสุด • insert(L, i, x): เพิ่ม x เข้าไปใน iโดยทำให้มันเป็นสมาชิกตัวที่ iหลังจากแทนเสร็จแล้ว • remove(i): ลบสมาชิกตัวที่ iออก
ในภาษา C • ต้องการ user-defined data type ชื่อ IntListสำหรับเก็บ int • ต้องการฟังก์ชันต่อไปนี้ • intIntList_get(IntList*L,inti) • voidIntList_set(IntList*L,inti,intx) • intIntList_find(IntList*L,intx) • voidIntList_insert(IntList*L,inti,intx) • voidIntList_remove(IntList*L,inti) • voidIntList_clear(IntList*L) • ลบสมาชิกทั้งหมดและคืนหน่วยความจำที่จองไว้ให้ระบบ • voidIntList_init(IntList*L) • ตั้งค่าเริ่มต้นให้กับลิสต์
ข้อมูลแบบ IntList • ข้างในมีอะเรย์ยืดหดได้อยู่หนึ่งตัว (แต่เราจะยืดอย่างเดียว) • มีฟีลด์ size เพื่อบอกว่าตอนนี้ array มีสมาชิกกี่ตัว • เวลาทำงาน เราจะทำให้ capacity ของอะเรย์ไม่น้อยกว่า size เสมอ • capacity = จำนวนสมาชิกที่สามารถเก็บได้ • size = สมาชิกที่เก็บไว้จริง typedefstruct { IntArrayarr; intsize; }IntList;
กฎการเก็บข้อมูล (Representation Invariant) • เทคนิคหนึ่งที่ใช้ในการเขียนโครงสร้างข้อมูล • กำหนดข้อความหนึ่งที่จะเป็นจริงตลอดชีวิตการทำงานของโครงสร้างข้อมูล • แล้วใช้ข้อความนี้เป็นไกด์ในการเขียนฟังก์ชันต่างๆ • ในที่นี้เราจะตั้งกฎสองกฎ • ฟีลด์ size มีค่าเท่ากับจำนวนสมาชิกในลิสต์ • สมาชิกตัวที่ iในอะเรย์ arrคือสมาชิกตัวที่ iของลิสต์
IntList_get • get(L, i): คืนสมาชิกตัวที่ i • กฎ: สมาชิกตัวที่ iในอะเรย์ arrคือสมาชิกตัวที่ iของลิสต์ • ใช้IntArray_getทำงาน intIntList_get(IntList*L,inti) { returnIntArray_get(&L->arr,i); }
IntList_set • set(L, i, x): ทำให้สมาชิกตัวที่ iกลายเป็น x • กฎ: สมาชิกตัวที่ iในอะเรย์ arrคือสมาชิกตัวที่ iของลิสต์ • ก็ใช้ IntArray_setทำงานเช่นกัน voidIntList_set(IntList*L,inti,intx) { IntArray_set(&L->arr,i,x); }
IntList_find • find(L, x): หาตำแหน่งของ x ใน L ถ้าไม่เจอคืน -1 ถ้ามีหลายตัวคืนตัวหน้าสุด • วนดูสมาชิกข้างในอะเรย์ทีละตัว แล้วตอบตำแหน่งของตัวแรกที่เจอ intIntList_find(IntList*L,intx) { inti; for(i=0;i<L->size;i++) if(x==IntArray_get(&L->arr,i)) returni; return-1; }
IntList_insert • insert(L, i, x): เพิ่ม x เข้าไปใน iโดยทำให้มันเป็นสมาชิกตัวที่ iหลังจากแทนเสร็จแล้ว • กฎ: • ฟีลด์ size มีค่าเท่ากับจำนวนสมาชิกในลิสต์ • สมาชิกตัวที่ iในอะเรย์ arrคือสมาชิกตัวที่ iของลิสต์
Before & After IntList IntArray L= size = 5 arr= capacity = 7 data= IntList_insert(&L, 2, 4) IntList IntArray L= size = 6 arr= capacity = 7 data=
Before & After IntList IntArray L= size = 5 arr= capacity = 7 data= IntList_insert(&L, 2, 4) IntList IntArray L= size = 6 arr= capacity = 7 data=
Before & After • จากกฎ เราต้อง • เลื่อนข้อมูลที่อยู่ทางด้านขวาของช่อง iไปทางขวาหนึ่งช่อง • เอาค่าใหม่ไปใส่ช่อง i • เพิ่ม size ขึ้น 1 IntList IntArray L= size = 5 arr= capacity = 7 data= IntList IntArray L= size = 6 arr= capacity = 7 data=
กรณีอะเรย์เต็ม IntList IntArray L= size = 3 arr= capacity = 3 data= IntList_insert(&L, 0, 11) ???
กรณีอะเรย์เต็ม • เราต้องเอาเลข 7 ไปใส่ช่องที่ 3 • แต่มันไม่มีช่องที่ 3 • ฉะนั้นเราต้องยึดอะเรย์ • แต่จะยืดเท่าไหร่ดี?
กรณีอะเรย์เต็ม • ยืดเพิ่ม 1 ช่อง • หมายความว่าเพิ่มแต่ละครั้งต้องยืดหนึ่งครั้ง • แต่การยืดอะเรย์แต่ละครั้งจะมีการจองหน่วยความจำใหม่ • การจองหน่วยความจำใหม่เป็นปฏิบัติการที่ช้า • ยืดเพิ่ม 2 เท่าของขนาดเดิม • จองให้ capacity ใหม่เท่ากับ size*2 • ไม่ต้องจองหน่วยความจำบ่อยมาก • ถ้าข้อมูลใหญ่ นานๆ จะจองครั้งหนึ่ง เร็ว • แต่เปลืองหน่วยความจำมากถึง 2 เท่า • ปัญหา: ถ้า size = 0 มันจะทำให้ capacity ใหม่เท่ากับ 0 • แทนที่จะมีช่องให้เก็บข้อมูลกลับไม่มี
กรณีอะเรย์เต็ม • ยืดเป็น 2*size+1 • ใช้หน่วยความจำพอๆ กับการยืดสองเท่า • แต่ไม่มีปัญหากรณี size = 0 • ถ้า size = 0 จะจองให้ capacity = 1 • ถ้า size = 1 จะจองให้ capacity = 3 • ถ้า size = 3 จะจองให้ capacity = 7 • ถ้า size = 7 จะจองให้ capacity = 15 • เช่นนี้ไปเรื่อยๆ
กรณีอะเรย์เต็ม IntList IntArray L= size = 3 arr= capacity = 3 data= IntList_insert(&L, 0, 11) IntList IntArray L= size = 4 arr= capacity = 7 data=
IntList_insert voidIntList_insert(IntList*L,inti,intx) { intk; if(i<0||i>L->size) { fprintf(stderr,"index out of range!"); return; } if(L->size+1>L->arr.capacity) IntArray_alloc(&L->arr,(L->size)*2+1); for(k=L->size;k>i;k--) IntArray_set(&L->arr,k,IntArray_get(&L->arr,k-1)); IntArray_set(&L->arr,i,x); L->size++; }
IntList_insert voidIntList_insert(IntList*L,inti,intx) { intk; if(i<0||i>L->size) { fprintf(stderr,"index out of range!"); return; } if(L->size+1>L->arr.capacity) IntArray_alloc(&L->arr,(L->size)*2+1); for(k=L->size;k>i;k--) IntArray_set(&L->arr,k,IntArray_get(&L->arr,k-1)); IntArray_set(&L->arr,i,x); L->size++; } เช็คว่า iอยู่ในช่วงที่ ถูกต้องหรือไม่
IntList_insert voidIntList_insert(IntList*L,inti,intx) { intk; if(i<0||i>L->size) { fprintf(stderr,"index out of range!"); return; } if(L->size+1>L->arr.capacity) IntArray_alloc(&L->arr,(L->size)*2+1); for(k=L->size;k>i;k--) IntArray_set(&L->arr,k,IntArray_get(&L->arr,k-1)); IntArray_set(&L->arr,i,x); L->size++; } ยืดอะเรย์ถ้าเต็ม
IntList_insert voidIntList_insert(IntList*L,inti,intx) { intk; if(i<0||i>L->size) { fprintf(stderr,"index out of range!"); return; } if(L->size+1>L->arr.capacity) IntArray_alloc(&L->arr,(L->size)*2+1); for(k=L->size;k>i;k--) IntArray_set(&L->arr,k,IntArray_get(&L->arr,k-1)); IntArray_set(&L->arr,i,x); L->size++; } เลื่อนสมาชิก
IntList_insert voidIntList_insert(IntList*L,inti,intx) { intk; if(i<0||i>L->size) { fprintf(stderr,"index out of range!"); return; } if(L->size+1>L->arr.capacity) IntArray_alloc(&L->arr,(L->size)*2+1); for(k=L->size;k>i;k--) IntArray_set(&L->arr,k,IntArray_get(&L->arr,k-1)); IntArray_set(&L->arr,i,x); L->size++; } เอาค่าใหม่แทรก แล้วเพิ่ม size
IntList_remove • remove(i): ลบสมาชิกตัวที่ iออก • กฎ: • ฟีลด์ size มีค่าเท่ากับจำนวนสมาชิกในลิสต์ • สมาชิกตัวที่ iในอะเรย์ arrคือสมาชิกตัวที่ iของลิสต์
Before & After IntList IntArray L= size = 5 arr= capacity = 7 data= IntList_remove (&L, 1) IntList IntArray L= size = 4 arr= capacity = 7 data=