ขั้นตอนวิธี(Algorithm)
ขั้นตอนวิธี มีชื่อมาจากนักคณิตศาสตร์ชาวเปอร์เซีย al-Khowarizmi ในทางวิทยาการคอมพิวเตอร์
หมายถึง การเขียนคำสั่งหรือประโยคที่แสดงลำดับการทำงานที่ชัดเจนและไม่คลุมเครือ โดยสามารถนำไปใช้กับเครื่องกล
ในการประมวลผลได้ โดยมีข้อมูลเข้า (input) และข้อมูลออกชัดเจน โดยปกติขั้นตอนวิธีจะถูกเขียนขึ้นเพื่อใช้กับการคำนวณที่เกิดขึ้น
ในเครื่องคอมพิวเตอร์
ประโยคและคำสั่งที่ใช้ในการเขียนขั้นตอนวิธี เราสามารถใช้
- คำสั่ง จากโปรแกรมภาษาระดับสูง เช่น C, FORTRAN, PASCAL เป็นต้น
- คำอธิบายที่ชัดเจนโดยไม่พึ่งโปรแกรมภาษาใด ๆ เรามักเรียกการเขียนแบบนี้ว่า รหัสเทียมหรือรหัสลำลอง (Pseudo code)
เรานิยามขั้นตอนวิธีมาเพื่อใช้แก้ปัญหาทางการคำนวณ (Computational problem) สำหรับกรณีของตัวอย่างของปัญหาขนาดเล็ก
(instances) เราอาจไม่ต้องการ การเขียนขั้นตอนวิธีที่ยุ่งยาก อย่างไรก็ตามถ้าเราต้องการแก้กรณีตัวอย่างที่เป็นไปได้ของปัญหาทั้งหมด
การพัฒนาขั้นตอนวิธีก็ถือว่าเป็นส่วนสำคัญ
นิยามอย่างหนึ่งของขั้นตอนวิธี คือ ขั้นตอนวิธีต้องแก้ปัญหาได้ถูกต้องทุกตัวอย่าง ดังนั้นในการแสดงว่า ขั้นตอนวิธีใดใช้ไม่ได้
เราเพียงเลือกตัวอย่างที่หลังจากการทำงานของขั้นตอนวิธีแล้วได้คำตอบที่ไม่เป็นไปตามที่ต้องการ อย่างไรก็ดี การทำงานจริงของ
โปรแกรมจะมีขอบเขตอันเนื่องมาจากขนาดของข้อมูลเข้า หรือจำนวนของข้อมูลที่มากเกินไป เราจะไม่ถือว่าตัวอย่างประเภทนี้
เป็นส่วนหนึ่งของข้อผิดพลาดของขั้นตอนวิธี
ข้อสำคัญอีกประการหนึ่งของการพัฒนาขั้นตอนวิธี คือ การเขียนหมายเหตุ (comment) การเขียนเอกสารประกอบขั้นตอนวิธีที่ดี
(good documentation) จะช่วยให้การปรับเปลี่ยนขั้นตอนวิธีนั้นง่ายและสามารถนำไปเขียนโปรแกรมได้ง่ายอย่างมีประสิทธิภาพ
ประสิทธิภาพของขั้นตอนวิธี
เมื่อเราต้องการแก้ปัญหา ปัญหาหนึ่ง เราอาจพบว่าเรามีขั้นตอนวิธีในการแก้ปัญหานี้หลายวิธี
ซึ่งเราก็ต้องการที่จะเลือกวิธีที่ดีที่สุด
ทำให้เราต้องตอบคำถามที่ว่าเราจะเลือกอย่างไร ถ้าปัญหา (Problem) นั้นมีตัวอย่างเพียงจำนวนน้อย (instance) เราอาจไม่ต้องคำนึงถึง
ขั้นตอนวิธีที่ดีที่สุด เราเพียงเขียนโปรแกรมเพื่อให้คำนวณคำตอบในกรณีนี้โดยไม่สนใจประสิทธิภาพ
สำหรับการเปรียบเทียบประสิทธิภาพของขั้นตอนวิธี
- วิธีการหนึ่งก็คือ การเปรียบเทียบโดยการทดลอง นั่นคือเราเขียนโปรแกรมสำหรับขั้นตอนวิธีต่าง ๆ
แล้วทำการประมวลผลกับตัวอย่างจำนวนมาก
- อีกวิธีการหนึ่งก็คือ การเปรียบเทียบทางทฤษฎี นั่นคือเราตัดสินจำนวนทรัพยากรที่ใช้ของขั้นตอนวิธี โดยพิจารณา
ขั้นตอนวิธีว่าเป็นฟังก์ชันของขนาดของข้อมูลเข้าในตัวอย่างหนึ่ง ๆ ของปัญหา
ทรัพยากรที่เรากล่าวถึง มักจะหมายถึง เวลาที่ใช้ หรือเนื้อที่ที่ต้องการ แต่เราจะเน้นถึงเวลามากกว่า
โดยเราจะคำนวณเวลานี้จากขนาดของข้อมูล
- นิยามของ ขนาดของข้อมูล อาจจะขึ้นอยู่กับ จำนวนบิตของตัวแปรที่ใช้ จำนวนข้อมูลที่ต้องการนำมาเรียง
หรืออื่น ๆ เรานิยมพูดถึงขนาดโดยใช้ตัวเลขจำนวนเต็มบวกเป็นหลัก
- ประโยชน์ที่สำคัญของ การเปรียบเทียบทางทฤษฎี คือผลลัพธ์ที่ได้จะไม่ขึ้นอยู่กับ คอมพิวเตอร์ที่ประมวลผล หรือ
ภาษาที่ใช้ หรือแม้แต่ทักษะความชำนาญของผู้เขียนโปรแกรม
- นอกจากนี้ เรายังสามารถวัดประสิทธิภาพของตัวอย่างขนาดใด ก็ได้โดยไม่จำเป็นต้องพิจารณาการเขียนโปรแกรมที่แก้ปัญหา
ขนาดใหญ่
- สำหรับการเปรียบเทียบประสิทธิภาพของขั้นตอนวิธี แบบเดียวกันที่เขียนบนเครื่องที่ต่างกัน เราใช้ principle of invariance
นั่นคือ เราได้ว่า ผลต่างของทรัพยากรที่ใช้สำหรับขั้นตอนวิธีแบบเดียวกัน ที่เขียนต่างกัน จะไม่มากไปกว่าค่าคงตัวคูณทรัพยากรที่ใช้
จากอีกตัวหนึ่ง
ดังนั้นเรามักพิจารณาหน่วยที่ใช้ในทางทฤษฎีเพียงหนึ่งหน่วย โดยละค่าคงตัวทั้งหมดออกไป
นิยามปัญหาการเรียงลำดับตัวเลข Sorting algorithm
กำหนด
ข้อมูลเข้า ข้อมูลตัวเลขจำนวน n ค่า (a1, a2, ..., an)
ข้อมูลออก การจัดเรียงของข้อมูลตัวเลขจำนวน n ตัว
(1, a'2, ..., a'n) โดยที่
a'1 a'2 ...
a'n
ตัวอย่างเช่น (31,41,59,26,41,58) เป็นตัวอย่างของปัญหา (instance) ซึ่งเมื่อผ่านขั้นตอนวิธีการเรียงตัวเลขแล้ว จะได้ผลลัพธ์คือ (26,31,41,41,58,59)
เราเรียกขั้นตอนวิธีว่าถูกต้อง (correct) ถ้าสำหรับทุก instance ของปัญหา ขั้นตอนวิธีนั้นหยุดด้วยผลลัพธ์ที่ถูกต้อง
และเรากล่าวว่า ขั้นตอนวิธีนั้นแก้ปัญหา (solve) ที่ต้องการ ตัวอย่างเช่น
ขั้นตอนวิธี insertion sort
Insertion sort เป็นวิธีการจัดเรียงข้อมูลที่เหมาะกับข้อมูลขนาดเล็ก เรากำหนดการเรียกใช้
ขั้นตอนวิธีดังนี้ INSERTION-SORT(A) เมื่อ A เป็น array
ข้อมูลเข้า A เป็นข้อมูลแบบแถวลำดับและ nA ซึ่งแทนขนาดของ A
ข้อมูลออก การจัดเรียงของข้อมูลจำนวน n ตัวใน A โดยที่
A[0] A[1] A[2]
. . . A[nA-1] (sorted in place)
- INSERTION-SORT(A, nA)
- for j = 2 to nA
- key = A[j]; i = j -1;
- while i > 0 and A[i] > key
- A[i+1] = A[i]; i = i - 1;
- endwhile
- A[i+1] = key;
- endfor
ข้อกำหนดในการใช้รหัสลำลอง (Pseudocode convention)
- เราใช้การเว้นช่องไฟในบรรทัดเพื่อกำหนดประโยคคำสั่งที่อยู่ใน scope เดียวกัน
- เราใช้ while, for, do-while สำหรับการวนซ้ำ และ if-then-else สำหรับการทดสอบ
- เราใช้การกำหนดค่าด้วยเครื่องหมาย = ซึ่งสามารถใช้ต่อเนื่องกันได้เช่น j = i = e
- ตัวแปรทุกตัวนิยามให้มีขอบเขตแบบ local scope
- การอ้างถึงตัวแปรแถวลำดับ A เราใช้ชื่อตัวแปร A แล้วตามด้วย [ ] และดรรชนีเป็นจำนวนเต็มที่ไม่ใช่ลบอยู่ภายใน [ ]
การวิเคราะห์ขั้นตอนวิธี (Analyzing algorithm)
การวิเคราะห์ขั้นตอนวิธีในทางวิทยาการคอมพิวเตอร์ หมายถึงการพิจารณาทรัพยากร (resource) ที่ขั้นตอนวิธีนั้นใช้
เช่น ขนาดของหน่วยความจำ หรือเวลาที่ใช้ในการประมวลผล ซึ่งเราจะมุ่งความสนใจไปที่เวลาเป็นส่วนใหญ่
เรากำหนดตัวแบบในการประมวลผลคือเครื่อง RAM (Random-access machine) ซึ่งเป็นการคำนวณแบบเรียงลำดับโดยใช้ processor
เพียงหนึ่งตัว คำสั่งแต่ละคำสั่งจะถูกใช้งานเรียกตามลำดับ ทำให้ได้ว่าเวลารวมของขั้นตอนวิธี คือการบวกกันของเวลาในการประมวลผลทุกคำสั่ง
และในหลายกรณี การวนซ้ำจะเกิดขึ้น โดยขึ้นกับขนาดของ input
หลักเกณฑ์ข้อหนึ่งที่สำหรับ running time คือเราต้องการกำหนด running time ที่ไม่ขึ้นกับการคำนวณเครื่องใดเครื่องหนึ่งโดยเฉพาะ
โดยเราจะมองว่าการคำนวณ คำสั่งหนึ่งคำสั่งมีขอบเขตซึ่งใช้เวลาเป็นค่าคงตัว แม้ว่าคำสั่งแต่ละคำสั่งอาจใช้เวลาในการประมวลผลต่างกัน
เพื่อการง่ายต่อการเปรียบเทียบเราจะสนใจเฉพาะในส่วนที่มีผลต่อการทำงานที่ขึ้นกับขนาดของข้อมูลเข้าเป็นสำคัญ
การวิเคราะห์ขั้นตอนวิธี insertion sort
- INSERTION-SORT(A, nA)
for j = 2 to nA | |
c1 | |
nA-1 |
- key = A[j]; i = j - 1;
| |
c2 | |
nA-1 |
- while i > 0 and A[i] > key
| |
c3 | |
nA - 1 |
- A[i+1] = A[i]; i = i - 1;
|
| c4 | |
t2 + . . . + tnA-1 |
- endwhile
| |
c5 | |
nA-1 |
- A[i+1] = key;
| |
c6 | |
nA-1 |
- endfor
| |
c7 | |
|
เมื่อ tj คือจำนวนเวลาที่ใช้ของ while loop สำหรับแต่ละค่าของ j
กำหนดให้ T(n) เป็น running time ของขั้นตอนวิธี insertion-sort เราจะได้ว่า
- T(n) = c1(nA-1) + c2(nA-1) + c3(nA-1) + c4(t2 + ... + tnA-1) + c5(nA-1) + c6(nA-1) + c7
สำหรับในกรณีที่ข้อมูลเรียงกันอยู่แล้วจะได้
- T(n) = c1(nA-1) + c2(nA-1) + c3(nA-1) + c5(nA-1) + c6(nA-1) + c7 = a n + b
ซึ่งเป็นฟังก์ชันเชิงเส้นของ n
สำหรับกรณีที่ข้อมูลเรียงกลับกันจะได้
- T(n) = c1(nA-1) + c2(nA-1) + c3(nA-1) + c4(nA(nA-1)/2) + c5(nA-1) + c6(nA-1) + c7 = a n2 + b n + c
ซึ่งเป็นฟังก์ชันกำลังสองของ n
การวิเคราะห์กรณีที่ไม่ดีที่สุด และกรณีเฉลี่ย (Worst-case and Average-case analysis)
เราพิจารณา Worst-case analysis ด้วยเหตุผลสามข้อ
- Worst-case running time เป็นการคำนวณค่าของ running time สำหรับกรณีที่เราต้องทำงานในทุกรอบของการวนซ้ำ
ซึ่งเปรียบเสมือนค่าขอบเขตด้านบนของเวลาที่ใช้ สำหรับตัวอย่างทั้งหมดที่เป็นไปได้
- มีปัญหาที่กรณีของ Worst case เกิดขึ้นบ่อย ๆ เช่นการหาค่าในฐานข้อมูล database ซึ่งข้อมูลไม่อยู่ในฐานข้อมูล นั้น Worst-case
มักจะเกิดขึ้นบ่อย ๆ
- โดยทั่วไปแล้วกรณีเฉลี่ยมักจะใช้เวลานานพอ ๆ กับ Worst-case analysis
และสำหรับขั้นตอนวิธีแล้ว เราจะสนใจอัตราการเพิ่มของ running time เป็น rate of growth หรือ อันดับของการขยาย order of growth
ขั้นตอนวิธี merge sort
Merge sort เป็นวิธีการจัดเรียงข้อมูลที่ใช้หลักการ divide-and-conquer นั่นคือปัญหาจะถูกแบ่งออกเป็น
ปัญหาย่อยขนาดเล็ก โดยใช้นิยามเวียนเกิดในการแก้ปัญหาย่อยเหล่านี้ ปัญหาหลังจากถูกแบ่งให้มีขนาดเล็กพอเหมาะแล้ว
เราสามารถหาคำตอบได้โดยง่าย เรากำหนดการเรียกใช้ ขั้นตอนวิธีดังนี้ MERGE-SORT(A, nA, p, r) เมื่อ A เป็นข้อมูลแบบแถวลำดับ
nA เป็นขนาดของข้อมูลแถวลำดับ A และ p, r เป็นดัชนีที่บอกการเรียงใน A ที่ต้องการให้ทำงาน
ข้อมูลเข้า A, nA, p, r
ข้อมูลออก การจัดเรียงของข้อมูลใน A โดยที่
A[p] A[p+1] A[p+2]
. . . A[r] (sorted in place)
- MERGE-SORT(A, nA, p, r)
- if p < r then
- q = floor((p+r)/2)
- MERGE-SORT(A, nA, p, q)
- MERGE-SORT(A, nA, q+1, r)
- MERGE(A, nA, p, q, r)
และขั้นตอนวิธีการ MERGE คือ
- MERGE(A, nA, p, q, r)
- i = p; j = q+1; k = 0;
- while i q and j r
- if A[i] A[j]
- temp[k++] = A[i++]
- else
- temp[k++] = A[j++]
- endwhile
- if i q
- while j r
- temp[k++] = A[j++]
- endwhile
- else
- while i q
- temp[k++] = A[i++]
- endwhile
- i = r
- while k 0
- A[i--] = temp[k--]
- endwhile
การวิเคราะห์ขั้นตอนวิธี merge sort
MERGE-SORT(A, nA, p, r)
- if p < r
| |
c1 |
- q = floor((p+r)/2)
| |
c2 |
- MERGE-SORT(A, nA, p, q)
| |
T(n/b1) |
- MERGE-SORT(A, nA, q+1, r)
| |
T(n/b2) |
- MERGE(A, nA, p, q, r)
| |
C(n) |
เมื่อ C(n) คือ running time ในการ merge ข้อมูลแถวลำดับที่เรียงกันจาก p ถึง q และ จาก q+1 ถึง r
กำหนดให้ T(n) เป็น running time สำหรับขั้นตอนวิธี merge sort เราจะได้ว่า
- T(n) = a T(n/b) + C(n) + c1 + c2
|