Quicksort
แบ่งแยกและพิชิต
Quicksort เป็นบทเรียน C Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน C Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส C Academy มีบทเรียนทั้งหมด 4 บทเรียน
แบ่งและพิชิต
การเรียงลำดับแบบเร็วเป็นการเรียงลำดับแบบแบ่งและพิชิต โดยเลือกจุดหมุน แบ่งอาร์เรย์ให้สมาชิกที่เล็กกว่าไปทางซ้ายและสมาชิกที่ใหญ่กว่าไปทางขวา จากนั้นเรียงลำดับแต่ละฝั่งแบบเรียกซ้ำ
เวลาเฉลี่ยคือ O(n log n)
ขั้นตอนการแบ่งส่วน
แนวคิดสำคัญคือการแบ่งส่วน: จัดเรียงอาร์เรย์ใหม่รอบจุดหมุน เพื่อให้ทุกสิ่งทางซ้ายของจุดหมุนมีค่าน้อยกว่า และทุกสิ่งทางขวามีค่ามากกว่า จากนั้นจุดหมุนจะอยู่ในตำแหน่งสุดท้ายตามลำดับ
รูปแบบการแบ่งส่วนแบบ Lomuto
รูปแบบ Lomutoใช้สมาชิกตัวสุดท้ายเป็นจุดหมุน โดยเก็บดัชนี i เป็นขอบเขตของสมาชิกที่มีค่าน้อยกว่า และสลับสมาชิกขณะเดินตรวจสอบ
#include <stdio.h>
int partition(int a[], int lo, int hi) {
int pivot = a[hi], i = lo - 1;
for (int j = lo; j < hi; j++)
if (a[j] < pivot) {
i++;
int t = a[i]; a[i] = a[j]; a[j] = t;
}
int t = a[i+1]; a[i+1] = a[hi]; a[hi] = t;
return i + 1;
}
int main(void) {
int a[] = {5, 2, 9, 1, 3};
int p = partition(a, 0, 4);
printf("pivot index = %d\n", p);
for (int i = 0; i < 5; i++) printf("%d ", a[i]);
printf("\n");
return 0;
}การเรียงลำดับแบบเรียกซ้ำ
การเรียงลำดับแบบเร็วจะเรียกการแบ่งส่วน แล้วเรียกซ้ำกับอาร์เรย์ย่อยสองส่วนรอบจุดหมุน กรณีฐานคืออาร์เรย์ย่อยที่มีขนาด 0 หรือ 1
#include <stdio.h>
int partition(int a[], int lo, int hi) {
int pivot = a[hi], i = lo - 1;
for (int j = lo; j < hi; j++)
if (a[j] < pivot) { i++; int t=a[i];a[i]=a[j];a[j]=t; }
int t = a[i+1]; a[i+1] = a[hi]; a[hi] = t;
return i + 1;
}
void quicksort(int a[], int lo, int hi) {
if (lo < hi) {
int p = partition(a, lo, hi);
quicksort(a, lo, p - 1);
quicksort(a, p + 1, hi);
}
}
int main(void) {
int a[] = {9, 3, 7, 1, 8, 2, 5};
quicksort(a, 0, 6);
for (int i = 0; i < 7; i++) printf("%d ", a[i]);
printf("\n");
return 0;
}การเลือกจุดหมุนที่ดี
จุดหมุนที่ไม่ดี (เช่น เลือกสมาชิกตัวสุดท้ายเสมอเมื่อข้อมูลเรียงลำดับแล้ว) ทำให้มีพฤติกรรม O(n ยกกำลังสอง) ตัวเลือกที่ดีกว่าจะกระจายส่วนต่าง ๆ ได้สม่ำเสมอยิ่งขึ้น
- ค่ามัธยฐานจากสามตัว
- จุดหมุนแบบสุ่ม
ค่ามัธยฐานจากสามตัว
ค่ามัธยฐานจากสามตัวจะเลือกค่ามัธยฐานของสมาชิกตัวแรก ตัวกลาง และตัวสุดท้ายเป็นจุดหมุน เพื่อหลีกเลี่ยงพฤติกรรมกรณีเลวร้ายที่สุดกับข้อมูลที่เรียงลำดับอยู่แล้ว
#include <stdio.h>
int median_of_three(int a[], int lo, int hi) {
int mid = lo + (hi - lo) / 2;
if (a[mid] < a[lo]) { int t=a[mid];a[mid]=a[lo];a[lo]=t; }
if (a[hi] < a[lo]) { int t=a[hi];a[hi]=a[lo];a[lo]=t; }
if (a[hi] < a[mid]) { int t=a[hi];a[hi]=a[mid];a[mid]=t; }
return mid;
}
int main(void) {
int a[] = {7, 1, 5, 3, 9};
int m = median_of_three(a, 0, 4);
printf("median value = %d\n", a[m]);
return 0;
}การวิเคราะห์กรณีเลวร้ายที่สุด
หากทุกการแบ่งส่วนแยกสมาชิกออกมาได้เพียงหนึ่งตัว ความลึกของการเรียกซ้ำจะกลายเป็น n และต้นทุนเป็น O(n ยกกำลังสอง) เหตุการณ์นี้เกิดขึ้นเมื่อใช้จุดหมุนคงที่กับข้อมูลที่เรียงลำดับหรือเรียงย้อนกลับ
การสุ่มทำให้กรณีเลวร้ายที่สุดเกิดขึ้นได้ยากมาก
จุดหมุนแบบสุ่ม
การสลับสมาชิกแบบสุ่มไปยังตำแหน่งจุดหมุนก่อนแบ่งส่วนช่วยป้องกันข้อมูลนำเข้าที่ออกแบบมาเพื่อทำให้การทำงานแย่ลง
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int a[] = {1, 2, 3, 4, 5};
int lo = 0, hi = 4;
srand(42);
int r = lo + rand() % (hi - lo + 1);
int t = a[r]; a[r] = a[hi]; a[hi] = t; /* move random to pivot slot */
printf("chosen pivot = %d\n", a[hi]);
return 0;
}ทำงานในตำแหน่งเดิมและไม่คงเสถียร
การเรียงลำดับแบบเร็วจะเรียงข้อมูลในตำแหน่งเดิม โดยใช้พื้นที่สแตกเพียง O(log n) โดยเฉลี่ย อย่างไรก็ตาม วิธีนี้ไม่คงเสถียร: สมาชิกที่เท่ากันอาจถูกสลับลำดับจากการสลับระหว่างแบ่งส่วน
การปรับปรุงการเรียกส่วนท้าย
การเรียกซ้ำกับครึ่งที่เล็กกว่าก่อน แล้วใช้ลูปกับครึ่งที่ใหญ่กว่า จะจำกัดความลึกของสแตกไว้ที่ O(log n) และป้องกันสแตกล้นกับอาร์เรย์ขนาดใหญ่
การเรียงลำดับสตริง
โครงสร้างเดียวกันนี้ใช้เรียงลำดับชนิดข้อมูลใด ๆ ที่เปรียบเทียบได้ ในที่นี้การเรียงลำดับแบบเร็วจัดลำดับอาร์เรย์จำนวนเต็ม แต่การเปลี่ยนการเปรียบเทียบก็รองรับชนิดข้อมูลอื่นได้
#include <stdio.h>
int partition(int a[], int lo, int hi) {
int pivot = a[hi], i = lo - 1;
for (int j = lo; j < hi; j++)
if (a[j] < pivot) { i++; int t=a[i];a[i]=a[j];a[j]=t; }
int t = a[i+1]; a[i+1] = a[hi]; a[hi] = t; return i + 1;
}
void quicksort(int a[], int lo, int hi) {
if (lo < hi) { int p = partition(a, lo, hi); quicksort(a, lo, p-1); quicksort(a, p+1, hi); }
}
int main(void) {
int a[] = {42, -7, 0, 100, 13, 13};
quicksort(a, 0, 5);
for (int i = 0; i < 6; i++) printf("%d ", a[i]);
printf("\n");
return 0;
}ตรวจสอบความเข้าใจ
ทดสอบความเข้าใจของคุณเกี่ยวกับการเรียงลำดับแบบเร็ว
สรุปทบทวน
คุณได้เรียนรู้การเรียงลำดับแบบเร็ว
- แบ่งส่วนรอบจุดหมุน แล้วเรียกซ้ำกับแต่ละฝั่ง
- ค่าเฉลี่ย O(n log n) และกรณีเลวร้ายที่สุด O(n ยกกำลังสอง)
- จุดหมุนแบบค่ามัธยฐานจากสามตัวหรือแบบสุ่มช่วยหลีกเลี่ยงกรณีเลวร้ายที่สุด
- ทำงานในตำแหน่งเดิมแต่ไม่คงเสถียร
คำถามที่พบบ่อย
บทเรียน “Quicksort” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “Quicksort” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส C Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส C Academy มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “Quicksort”
แบ่งแยกและพิชิต คุณปฏิบัติ C Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน C Academy หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน C Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน
บทเรียน “Quicksort” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน C Academy นี้ได้ไหม
ได้ บทเรียน C Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ