การจัดเรียงข้อมูล (sorting) ผู้สอน อาจารย์ ยืนยง กันทะเนตร สาขาวิชาเทคโนโลยีคอมพิวเตอร์เคลื่อนที่ คณะเทคโนโลยีสารสนเทศและการสื่อสาร Website : ict.up.ac.th/yeunyong
หัวข้อวันนี้ ขั้นตอนวิธี (Algorithm) การจัดเรียงแบบต่างๆ Insertion sort (เรียนแล้ว) Selection sort (เรียนแล้ว) Bubble sort (เรียนแล้ว) Merge sort (ครั้งนี้) Quick sort (ครั้งนี้)
merge sort มักใช้ในการจัดเรียงข้อมูลที่มีขนาดใหญ่มากๆ ใช้วิธีการแบ่งแยกและเอาชนะ (Divide and Conquer Algorithm) ใช้การเรียงข้อมูลขนาดเล็กหลายๆ ส่วนแทนการเรียงข้อมูลขนาดใหญ่เพียงส่วนเดียว เช่น เรียงข้อมูล 1,000 ตัวด้วย bubble sort 1,0002 = 1,000,000 เรียงข้อมูล 500 ตัว 2 ชุด ด้วย bubble sort 5002 + 5002 + 1,000 =501,000
เรียงข้อมูล 250 ตัว 4 ชุด ด้วย bubble sort 4*(2502) + 1,000 =251,000 ------------------------------------------------------------------------------------------ ใช้วิธีการแบ่งข้อมูลเริ่มต้นออกเป็น 2 ส่วนที่เท่ากัน จากข้อมูลทั้ง 2 ส่วน แบ่งแต่ละส่วนออกเป็น 2 ส่วนที่เท่ากัน ทำการแบ่งไปเรื่อยๆ จนไม่สามารถแบ่งได้อีก (แต่ละส่วนมีข้อมูลอยู่ตัวเดียว) นำข้อมูลมาประสานพร้อมจัดเรียงไล่กลับขึ้นไปทีละคู่ตามลำดับ
(5 , 4 , 2 , 7 , 1 , 3 , 6) (5 , 4 , 2) (7 , 1 , 3 , 6) (5) (4 , 2) (7 , 1) (3 , 6) 4 2 7 1 3 6 (2 , 4) (1 , 7) (3 , 6) (2 , 4 , 5) (1 , 3 , 6 , 7) (1 , 2 , 3 , 4 , 5 , 6 , 7)
pseudo code ของ merge sort function Mergesort (data A, int Lb, int Ub) { if Lb < Ub then { Middle = (Lb + Ub)/2 MergeSort (A, Lb, Middle) MergeSort (A, Middle + 1, Ub) Merge(A, Lb, Middle, Ub) } function Merge (data A, int Lb, int Middle, int Ub) { allocate tmp1 for A[Lb...Middle] allocate tmp2 for A[middle+1...Ub] merge tmp1 and tmp2 back to A by order
การวิเคราะห์อัลกอริทึม ต้องวิเคราะห์เป็นกรณีต่างๆ หรือไม่ เพราะอะไร ? ไม่ต้อง ไม่ว่า “ค่า” ของข้อมูลเป็นอย่างไร ก็ต้อง “แยก” จนไม่สามารถแยกได้ แล้วจึง “ประสาน” คืน f(n)merge = n (จำนวนข้อมูลจาก Lb – Ub ขณะนั้น) f(n)mergesort = ?
f(n)merge number of merge n 1 n/2 2 ... ... ... 8 n/8 ... 4 n/4 2 n/2 f(n)mergesort = n(1) + n/2(2) + … + 8(n/8) + 4(n/4) + 2(n/2) = n log2n O (n log n)
Quick sort เหมาะกับข้อมูลที่มีขนาดใหญ่ จัดเรียงโดยแบ่งข้อมูลออกเป็นสองส่วนเพื่อใช้ในการจัดเรียง คล้ายกับ merge sort จัดอยู่ในกลุ่ม Divide and Conquer เช่นเดียวกัน ได้ชื่อว่าเร็ว เพราะส่วนใหญ่จะทำงานได้เร็วที่สุด
Quick sort – Basic quick sort โดยจะสุ่มเลือกข้อมูลขึ้นมา 1 ตัว เรียกว่า pivot ส่วนมากเลือกจากข้อมูลตัวแรกหรือกึ่งกลางของกลุ่ม จัดข้อมูลให้ด้านซ้ายเป็นข้อมูลที่มีค่าน้อยกว่า pivot ด้านขวาเป็นข้อมูลที่มีค่ามากกว่า pivot จะได้ข้อมูล 2 ส่วนซึ่งแยกกันด้วย pivot นำแต่ละส่วนมาทำการ quick sort ไปเรื่อยๆ จนไม่สามารถแบ่งได้อีก
MERGE SORT แยกจนถึงที่สุด แล้วจึงประสานพร้อมกับจัดเรียง QUICK SORT แยกพร้อมกับจัดเรียง
(a[1] , a[2] , … , a[p] … , a[p-1] , a[n]) quick sort กลุ่มของข้อมูลที่มีค่าน้อยกว่า pivot pivot ( a[p] ) กลุ่มของข้อมูลที่มีค่ามากกว่า pivot quick sort quick sort … …
(4 , 10 , 2 , 6 , 5 , 8 , 7 , 3 , 9 , 1) (4 , 2 , 3, 1) (5) ( 10 , 6 , 8 , 7 , 9) (1) (2) (4 , 3) (6 , 7) (8) (10 , 9) (3) (4) (-) (-) (6) (7) (9) (10) (-) (1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10)
MERGE SORT แยกออกเป็น 2 ส่วนที่เท่ากัน QUICK SORT แยกออกเป็น 2 ส่วน แต่ไม่จำเป็นต้องเท่ากัน
pseudo code ของ quick sort function QuickSort (data A, int Lb, int Ub) { if Lb < Ub then { pivot = Partition (A, Lb, Ub) QuickSort (A, Lb, pivot - 1) QuickSort (A, pivot + 1, Ub) } function Partition (data A, int Lb, int Ub) { select a pivot from A[Lb]...A[Ub] reorder A[Lb]...A[Ub] such that : all values to the left of the pivot are < pivot all values to the right of the pivot are >= pivot return pivot position
Quick sort : Choose the pivot เป็นการสุ่มอย่างหนึ่ง ถ้าสุ่มเลือก pivot ได้ดี -> quick sort ทำงานได้เร็วมาก (เลือก pivot ที่แบ่งข้อมูลออกเป็น 2 ส่วนที่เท่ากัน) ถ้าสุ่มเลือก pivot ได้ไม่ดี -> quick sort จะทำงานได้ช้า (เลือก pivot ที่แบ่งข้อมูลแล้วส่วนใดส่วนหนึ่งไม่มีข้อมูล – pivot เป็นข้อมูลที่มีค่ามากหรือน้อยที่สุด) pivot กำหนดความเร็วของอัลกอริทึม
ตัวอย่างการเลือก pivot ที่ไม่ดี ( ข้อมูลเริ่มต้น 10 ตัว ) ทำ quick sort 9 ครั้ง pivot (9 ตัว) (8 ตัว) pivot pivot (...) pivot (1 ตัว) เลือก pivot ที่แบ่งข้อมูลแล้วส่วนใดส่วนหนึ่งไม่มีข้อมูล – pivot เป็นข้อมูลที่มีค่ามากหรือน้อยที่สุด
ตัวอย่างการเลือก pivot ที่ดี ( ข้อมูลเริ่มต้น 10 ตัว ) (4 ตัว) pivot (5 ตัว) (1ตัว) pivot (2 ตัว) (2ตัว) pivot (2 ตัว) (1ตัว) pivot pivot (1ตัว) (1ตัว) pivot ทำ quick sort 6 ครั้ง เลือก pivot ที่แบ่งข้อมูลออกเป็น 2 ส่วนที่เท่ากัน, เกือบเท่ากัน
การวิเคราะห์อัลกอริทึม ต้องวิเคราะห์เป็นกรณีต่างๆ หรือไม่ เพราะอะไร ? ต้อง เพราะฟังก์ชัน Quicksort จะแบ่งข้อมูลออกเป็นกลุ่มตามผลการเลือก pivot ถ้าเป็นกรณีที่ดีที่สุด จะได้รูปร่างคล้าย tree ที่มีความสูง log2n (complete binary tree) กรณีแย่ที่สุด จะได้รูปร่างคล้าย tree ที่มีความสูง n เนื่องจากการวิเคราะห์ค่อนข้างซับซ้อน จึงไม่พูดถึง best = O (n log2n) , average = O (n log2n) , worst = O(n2)
Quick sort : with in-place partition การจัดเรียงข้อมูลในฟังก์ชัน partition นิยมใช้วิธีการคล้ายกับ insertion sort (แทรกข้อมูล ณ ตำแหน่งที่ถูก) โดยขั้นแรกจะทำการเลือก pivot ก่อน จากนั้นจึงอ่านค่าที่เหลือของพาร์ทิชันและนำมาเปรียบเทียบกับ pivot ทีละตัว ถ้าตัวใดมีค่าน้อยกว่า pivot จะถูกสลับที่มาอยู่ฝั่งซ้าย ถ้าตัวใดมีค่ามากกว่า pivot จะถูกสลับที่มาอยู่ฝั่งขวา การจัดเรียงที่เกิดขึ้นทั้งหมด ไม่ต้องมีการจองหน่วยความจำเพิ่มเติม -> มีประสิทธิภาพสูง, เร็ว
ในกรณีที่เลือกค่ากึ่งกลางพาร์ทิชันเป็น pivot ตัวอย่าง function Partition (data A, int Lb, int Ub) { // p_pos = (Lb+Ub) / 2 // swap (a[Lb],a[p_pos]) p_pos = Lb pivot = a[Lb] for ( i = Lb + 1 ; i <= Ub ; i++ ) { if (a[i] < pivot) { p_pos++ swap (a[i] , a[p_pos]) } swap (a[Lb] , a[p_pos]) return p_pos ในกรณีที่เลือกค่ากึ่งกลางพาร์ทิชันเป็น pivot