Processing math: 100%

7.4 Shellsort

Shellsort ตั้งตามชื่อของผู้คิดค้นการจัดเรียงแบบนี้ คือ Donald Shell (ในปี 1959) และเป็นหนึ่งในอัลกอริทึมแรก ๆ ที่ทำลายขอบเขตเวลาที่เป็น quadratic ถึงแม้ว่าจะพิสูจน์ได้ว่าอัลกอริทึมนี้มี running time ต่ำกว่า quadratic หลังจากที่มีการนำเสนอครั้งแรกในหลายปีต่อมาก็ตาม ในขณะทำงานแต่ละ phase นั้น shell sort ใช้การเปรียบเทียบค่าที่อยู่ในตำแหน่งที่ห่างกัน ระยะห่างดังกล่าวนี้จะลดลงลงเรื่อย ๆ จนกระทั่งถึงขั้นตอนสุดท้ายที่เป็นการเปรียบเทียบค่าที่อยู่ติดกัน ด้วยเหตุที่ระยะห่างของค่าที่นำมาเปรียบเทียบกันลดลงไปในระหว่างการทำงานของอัลกอริทึมนี้เอง จึงเรียก Shellsort อีกอย่างว่า diminishing increment sort

Shellsort ใช้ลำดับ h1,h2,...,ht ซึ่งเรียกว่า increment sequence และลำดับที่ใช้จะมีค่าลักษณะใดก็ได้เพียงแต่มีเงื่อนไขว่า h1=1 เท่านั้น แต่แน่นอนว่าบางลำดับจะทำงานได้ดีกว่าบางลำดับ (จะกล่าวถึงอีกครั้ง) หลังการทำงานแต่ละ phase ที่ใช้ลำดับการเพิ่ม hk ผลที่ได้ คือ สำหรับแต่ละค่า i เราจะได้ว่า a[i]a[i+hk] กล่าวคือสมาชิกทุกตัวที่มีระยะห่างกัน hk จะอยู่ในลำดับที่มีการจัดเรียงอย่างถูกต้อง ซึ่งเรียกว่า hk-sorted file รูปที่ 7.3 แสดงอะเรย์หลังการทำงานของเฟสต่าง ๆ ของ Shellsort คุณสมบัติที่สำคัญประการหนึ่งของ Shellsort คือ การทำ hk-sorted แล้วตามด้วย hk1-sorted นั้นยังคงสภาพของ hk-sorted เอาไว้ได้เช่นเดิม

Original 81 94 11 96 12 35 17 95 28 58 41 75 15
After 5-sort 35 17 11 28 12 41 75 15 96 58 81 94 95
After 3-sort 28 12 11 35 15 41 58 17 94 75 81 96 95
After 1-sort 11 12 15 17 28 35 41 58 75 81 94 95 96

รูปที่ 7.3 อะเรย์หลังการทำงานของเฟสต่าง ๆ ของ Shellsort

วิธีการทั่วไปในการทำ hk-sort ก็คือ สำหรับแต่ละตำแหน่ง i ใน hk , hk + 1, hk + 2, . . . , N – 1 ให้ทำการจัดวางสมาชิกลงในตำแหน่งที่ถูกต้องภายในระหว่างตำแหน่ง i, i - hk , i - 2hk etc. สิ่งสำคัญคือ การทำงานของ hk-sort หนึ่ง ๆ นั้นจะเป็นการทำ insertion sort กับอะเรย์ย่อย hk ที่เป็นอิสระกัน

ลำดับการเพิ่มที่นิยมใช้ (แต่ไม่ดี) คือลำดับการเพิ่มที่เสนอโดย Shell คือลำดับ ht=N/2 และ hk=hk+1/2 รูปที่ 7.4 เป็นโปรแกรมที่ใช้ลำดับการเพิ่มนี้ ในปัจจุบันมีการคิดค้นลำดับการเพิ่มที่แตกต่างออกไปที่ทำให้อัลกอริทึมมีประสิทธิภาพที่ดีกว่านี้

        public static void shellsort( Comparable [ ] a )
        {
            int j;
/* 1*/      for( int gap = a.length / 2; gap > 0; gap /= 2 )
/* 2*/          for( int i = gap; i < a.length; i++ )
                {
/* 3*/              Comparable tmp = a[ i ];
/* 4*/              for( j = i; j >= gap &&
				 tmp.compareTo( a[ j - gap ] ) < 0; j -= gap )
/* 5*/                  a[ j ] = a[ j - gap ];
/* 6*/              a[ j ] = tmp;
                }
        }

รูปที่ 7.4 Shellsort ที่ใช้ลำดับการเพิ่มที่เสนอโดย Shell

7.4.1. การวิเคราะห์กรณี Worst-Case ของ Shellsort

การวิเคราะห์หาร running time ของ Shellsort จะมีความยุ่งยากอยู่มาก ทั้งนี้เนื่องจาก running time ของมันขึ้นอยู่กับการเลือกใช้ลำดับการเพิ่ม และการพิสูจน์ก็มีความยุ่งยากมาก การพิสูจน์กรณีเฉลี่ยของ Shellsort ก็ยังเป็นที่ถกเถียงกันอยู่ยกเว้นกรณีที่ใช้ลำดับการเพิ่มพื้น ๆ เท่านั้น เราจะกล่าวถึงขอบเขตของกรณี worst-case สำหรับลำดับการเพิ่มที่เสนอโดย Shell

THEOREM 7.3

running time กรณี worst-case ของ Shellsort ที่ใช้ลำดับการเพิ่มของ Shell คือ Θ(N2)

PROOF:

ในการพิสูจน์ไม่เพียงแต่ต้องแสดงให้เห็นถึงขอบเขตบนของ running time กรณี worst-case เท่านั้น แต่ยังต้องแสดงว่ามีบางอินพุตที่ต้องใช้เวลาในการทำงานเป็น Ω(N2) ด้วย ในตอนแรกจะพิสูจน์ขอบเขตล่างก่อน (lower bound) โดยการสร้างกรณีข้อมูลไม่ดีขึ้น ก่อนอื่นเลือกค่า N ให้มีค่าเป็นเลขยกกำลังของ 2 ซึ่งจะทำให้ลำดับการเพิ่มทั้งหมดเป็นเลขคู่ ยกเว้นลำดับการเพิ่มสุดท้ายที่มีค่าเท่ากับ 1 และกำหนดให้อินพุตเป็นอะเรย์ input_data ที่มีกลุ่มที่มีค่ามากที่สุดจำนวน N/2 ตัวอยู่ในตำแหน่งคู่และกลุ่มที่มีค่าน้อยที่สุดอีกจำนวน N/2 ตัวอยู่ในตำแหน่งเลขคี่ เมื่อการทำงานในรอบก่อนรอบสุดท้ายจบลงจะพบว่ากลุ่มตัวเลขที่มากที่สุด N/2 ตัวก็ยังคงอยู่ในตำแหน่งเลขคู่ และกลุ่มตัวเลขที่น้อยที่สุดจำนวน N/2 ตัวก็ยังคงอยู่ในตำแหน่งคี่ ดังนั้นก่อนการเริ่มทำงานในรอบสุดท้ายตัวเลขที่มีค่าน้อยที่สุดอันดับที่ i ซึ่ง i ⇐ N/2 มีอยู่ในตำแหน่ง 2i – 1 เพื่อที่จะทำให้สมาชิกตัวที่ i นี้ไปอยู่ในตำแหน่งที่ถูกต้องของมันเราต้องเคลื่อนย้ายมันเป็นระยะ i – 1 ตำแหน่งภายในอะเรย์ ดังนั้น เพื่อที่จะจัดวางให้ตัวเลขที่น้อยที่สุดจำนวน N/2 ตัวไปอยู่ในตำแหน่งที่ถูกต้องของมัน เราต้องใช้การทำงานอย่างน้อย N/2i=1i1=Ω(N2) รูปที่ 7.5 แสดงอินพุตที่ไม่ดี (แต่ไม่ใช่เป็นกรณี worst case) เมื่อ N = 16 จำนวน inversion ยังคงมีอยู่ภายหลัง 2-sort คือ 1+2+3+4+5+6+7 = 28 ซึ่งในการทำงานรอบสุดท้ายจะยังคงใช้เวลามากทีเดียว

สุดท้ายจะแสดงให้เห็นว่าขอบเขตบน คือ O(N2) ดังที่ได้กล่าวแล้วว่า การทำงานในรอบที่มีลำดับการเพิ่มเป็น hk นั้นต้องใช้การทำงาน insertion sort ที่ทำกับสมาชิกจำนวนประมาณ N/hk ตัว เป็นจำนวน hk ครั้ง เนื่องจาก insertion sort เป็น quadratic ดังนั้นในแต่ละรอบจึงใช้เวลาทั้งหมดเป็น O(hk(N/hk)2)=O(N2/hk) ดังนั้นเมื่อรวมทุกรอบการทำงานทั้งหมดแล้วจะได้ Ο(ti=1N2/hi)=Ο(N2ti=11/hi) และเนื่องจากรูปแบบลำดับการเพิ่มเป็น geometric series ที่มี common ratio เป็น 2 และเทอมที่มากที่สุดคือ h1 = 1 และ ti=11/hi<2 ดังนั้นจึงได้ O(N2)

Start 1 9 2 10 3 11 4 12 5 13 6 14 7 15 8 16
After 8-sort 1 9 2 10 3 11 4 12 5 13 6 14 7 15 8 16
After 4-sort 1 9 2 10 3 11 4 12 5 13 6 14 7 15 8 16
After 2-sort 1 9 2 10 3 11 4 12 5 13 6 14 7 15 8 16
After 1-sort 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

รูปที่ 7.5 อินพุตที่ไม่ดีสำหรับ Shellsort ที่ใช้การเพิ่มของ Shell เมื่อ N = 16

Hibbard เสนอลำดับการเพิ่มที่แตกต่างไปเล็กน้อยซึ่งให้ผลในทางปฏิบัติที่ดีกว่าเล็กน้อยลำดับการเพิ่มดังกล่าว คือ 1,3,7,...,2k1 และ running time สำหรับกรณี worst-case ของ Shellsort ที่ใช้ลำดับการเพิ่มของ Hibbard คือ Ω(N3/2) นอกจากนี้ยังมีผู้เสนอลำดับการเพิ่มแบบอื่น ๆ ซึ่งให้ผล running time ที่แตกต่างกันออกไปมากบ้างน้อยบ้าง อีกทั้งการวิเคราะห์ก็มีความซับซ้อนมากต้องใช้ทฤษฎีทางคณิตศาสตร์ ขั้นสูง เช่น ลำดับการเพิ่มที่เสนอโดย Sedgewick (4k+3·2k1+1) มี running time เป็น O(N4/3) ในกรณี worst-case ประสิทธิภาพการทำงานของ Shellsort เป็นที่ยอมรับกันในทางปฏิบัติ ถึงแม้ขนาดอินพุตจะมีจำนวนหลายหมื่นหน่วยข้อมูล การที่ Shellsort เขียนโปรแกรมง่ายทำให้เป็นที่นิยมใช้เพื่อจัดเรียงข้อมูลที่มีขนาดไม่ใหญ่มากนัก