Quick Sort (מיון מהיר)
עודכן לאחרונה
Quicksort הוא אלגוריתם מסוג הפרד ומשול שממיין סביב "ציר" (pivot). הוא בוחר איבר ציר, ואז מחלק את המערך כך שכל מה שקטן ממנו בא לפניו וכל מה שגדול ממנו בא אחריו, וזה נועל את הציר במקומו הממוין הסופי. אחר כך הוא ממשיך ברקורסיה על החלק השמאלי והימני. ההדמיה הזו משתמשת בשיטת Lomuto עם האיבר האחרון כציר. לחצו על הפעלה כדי לראות את החלוקה ואת הצבת הציר.
Quicksort הוא בדרך כלל המיון הכללי המהיר ביותר בפועל, בזכות התנהגות טובה מול המטמון וחלוקה במקום, עם ממוצע של O(n log n). המקרה הגרוע שלו הוא O(n²) (למשל מערך שכבר ממוין עם בחירת ציר גרועה), ואסטרטגיות ציר טובות כמו חציון של שלושה או אקראיות נמנעות ממנו.
סיבוכיות זמן וזיכרון
| מקרה | סיבוכיות | הערות |
|---|---|---|
| המקרה הטוב | O(n log n) | חלוקות מאוזנות |
| המקרה הממוצע | O(n log n) | סדר אקראי |
| המקרה הגרוע | O(n²) | צירים לא מאוזנים באופן עקבי |
| זיכרון | O(log n) | מחסנית רקורסיה (חלוקה במקום) |
| יציב | לא | החלפות החלוקה משנות את הסדר של איברים שווים |
צעד אחר צעד
| צעד | מה קורה |
|---|---|
| 1 | בוחרים ציר (כאן, האיבר האחרון בטווח). |
| 2 | חלוקה: מעבירים את כל האיברים שקטנים מהציר לצד שמאל שלו. |
| 3 | מחליפים את הציר אל הגבול: הוא נמצא עכשיו במקומו הסופי. |
| 4 | ממיינים ברקורסיה את החלק השמאלי ב-quicksort. |
| 5 | ממיינים ברקורסיה את החלק הימני ב-quicksort. |
דוגמה מפורטת
מיון של [5, 2, 4, 1] בשיטת Lomuto (האיבר האחרון כציר):
| מעבר | מערך | פעולה |
|---|---|---|
| התחלה | [5, 2, 4, 1] | מחלקים את הטווח כולו; הציר הוא 1 (האיבר האחרון). |
| 1 | [1, 2, 4, 5] | שום דבר לא קטן מ-1, ולכן מחליפים את 1 לאינדקס 0; הציר 1 סופי עכשיו. ממשיכים ברקורסיה ימינה על [2, 4, 5]. |
| 2 | [1, 2, 4, 5] | מחלקים את [2, 4, 5] עם הציר 5; גם 2 וגם 4 קטנים יותר, ולכן 5 נשאר בסוף והוא סופי. ממשיכים ברקורסיה שמאלה על [2, 4]. |
| 3 | [1, 2, 4, 5] | מחלקים את [2, 4] עם הציר 4; 2 קטן יותר, ולכן 4 נשאר במקומו והוא סופי. 2 הוא איבר יחיד, ולכן הוא כבר ממוין. |
| סיום | [1, 2, 4, 5] | כל ציר נעול במקומו; המערך ממוין. |
מתי להשתמש ב-quicksort
| השתמשו בו כאשר | הימנעו ממנו כאשר |
|---|---|
| אתם צריכים מיון כללי ומהיר בזיכרון עם מקדמים קבועים קטנים. | אתם צריכים זמן מובטח של O(n log n) במקרה הגרוע (השתמשו במיון ערימה או במיון מיזוג). |
הזיכרון מוגבל: החלוקה מתבצעת במקום וצריכה רק O(log n) זיכרון מחסנית. | אתם צריכים מיון יציב ששומר על הסדר של מפתחות שווים. |
| הנתונים בסדר אקראי או לא ידוע ואתם משתמשים בציר אקראי או בחציון של שלושה. | הקלט כבר ממוין או כמעט ממוין והציר קבוע, מה שמוביל ל-O(n²). |
| מקומיות מטמון טובה חשובה, כי quicksort ניגש לזיכרון באופן סדרתי. | אתם ממיינים רשימה מקושרת, שבה מיון מיזוג נמנע מהגישה האקראית ש-quicksort נשען עליה. |
קוד Quick Sort
מימוש נקי של Quick Sort שאפשר להריץ, ב-Python, JavaScript, Java, C++, C, Pseudocode. בחרו שפה, העתיקו את הקוד, או פתחו אותו טעון מראש בעורך האונליין של Coddy.
קוד Quick Sort ב-Python
1def quick_sort(a, low=0, high=None):2 if high is None:3 high = len(a) - 14 if low < high:5 p = partition(a, low, high)6 quick_sort(a, low, p - 1)7 quick_sort(a, p + 1, high)8 return a9
10
11def partition(a, low, high):12 # Lomuto partition: everything < pivot moves left of it13 pivot = a[high]14 i = low15 for j in range(low, high):16 if a[j] < pivot:17 a[i], a[j] = a[j], a[i]18 i += 119 a[i], a[high] = a[high], a[i]20 return i21
22
23nums = [10, 7, 8, 9, 1, 5]24print("Before:", nums)25quick_sort(nums)26print("After: ", nums)קוד Quick Sort ב-JavaScript
1function quickSort(a, lo = 0, hi = a.length - 1) {2 if (lo >= hi) return a;3 const p = partition(a, lo, hi);4 quickSort(a, lo, p - 1);5 quickSort(a, p + 1, hi);6 return a;7}8
9// Lomuto partition: last element is the pivot10function partition(a, lo, hi) {11 const pivot = a[hi];12 let i = lo;13 for (let j = lo; j < hi; j++) {14 if (a[j] < pivot) {15 [a[i], a[j]] = [a[j], a[i]];16 i++;17 }18 }19 [a[i], a[hi]] = [a[hi], a[i]];20 return i;21}22
23const data = [5, 2, 9, 1, 7, 3];24console.log("Before:", data);25console.log("Sorted:", quickSort([...data]));קוד Quick Sort ב-Java
1import java.util.Arrays;2
3public class Main {4 static void quickSort(int[] arr, int low, int high) {5 if (low >= high) return;6 int p = partition(arr, low, high);7 quickSort(arr, low, p - 1);8 quickSort(arr, p + 1, high);9 }10
11 // Lomuto partition: last element is the pivot12 static int partition(int[] arr, int low, int high) {13 int pivot = arr[high];14 int i = low - 1;15 for (int j = low; j < high; j++) {16 if (arr[j] < pivot) swap(arr, ++i, j);17 }18 swap(arr, i + 1, high);19 return i + 1;20 }21
22 static void swap(int[] arr, int a, int b) {23 int tmp = arr[a];24 arr[a] = arr[b];25 arr[b] = tmp;26 }27
28 public static void main(String[] args) {29 int[] arr = {10, 7, 8, 9, 1, 5};30 System.out.println("Before: " + Arrays.toString(arr));31 quickSort(arr, 0, arr.length - 1);32 System.out.println("After: " + Arrays.toString(arr));33 }34}קוד Quick Sort ב-C++
1#include <iostream>2#include <utility>3#include <vector>4
5void printVec(const std::vector<int>& a) {6 for (int x : a) std::cout << x << " ";7 std::cout << "\n";8}9
10// Lomuto partition: place the pivot in its final position11int partition(std::vector<int>& a, int lo, int hi) {12 int pivot = a[hi];13 int i = lo;14 for (int j = lo; j < hi; ++j) {15 if (a[j] < pivot) std::swap(a[i++], a[j]);16 }17 std::swap(a[i], a[hi]);18 return i;19}20
21void quickSort(std::vector<int>& a, int lo, int hi) {22 if (lo >= hi) return;23 int p = partition(a, lo, hi);24 quickSort(a, lo, p - 1);25 quickSort(a, p + 1, hi);26}27
28int main() {29 std::vector<int> data = {10, 7, 8, 9, 1, 5};30 std::cout << "Before: ";31 printVec(data);32 quickSort(data, 0, static_cast<int>(data.size()) - 1);33 std::cout << "After: ";34 printVec(data);35 return 0;36}קוד Quick Sort ב-C
1#include <stdio.h>2
3void printArr(const int a[], int n) {4 for (int i = 0; i < n; i++) printf("%d ", a[i]);5 printf("\n");6}7
8void swap(int* x, int* y) {9 int tmp = *x;10 *x = *y;11 *y = tmp;12}13
14// Lomuto partition: place the pivot in its final position15int partition(int a[], int lo, int hi) {16 int pivot = a[hi];17 int i = lo;18 for (int j = lo; j < hi; j++) {19 if (a[j] < pivot) swap(&a[i++], &a[j]);20 }21 swap(&a[i], &a[hi]);22 return i;23}24
25void quickSort(int a[], int lo, int hi) {26 if (lo >= hi) return;27 int p = partition(a, lo, hi);28 quickSort(a, lo, p - 1);29 quickSort(a, p + 1, hi);30}31
32int main(void) {33 int data[] = {10, 7, 8, 9, 1, 5};34 int n = sizeof(data) / sizeof(data[0]);35 printf("Before: ");36 printArr(data, n);37 quickSort(data, 0, n - 1);38 printf("After: ");39 printArr(data, n);40 return 0;41}קוד Quick Sort ב-Pseudocode
1DECLARE nums : ARRAY[1:7] OF INTEGER2DECLARE n : INTEGER3n ← 74nums[1] ← 75nums[2] ← 36nums[3] ← 97nums[4] ← 18nums[5] ← 59nums[6] ← 810nums[7] ← 211DECLARE i : INTEGER12
13FUNCTION partition(lo : INTEGER, hi : INTEGER) RETURNS INTEGER14 DECLARE pivot : INTEGER15 DECLARE a : INTEGER16 DECLARE b : INTEGER17 DECLARE temp : INTEGER18 pivot ← nums[hi]19 a ← lo - 120 FOR b ← lo TO hi - 121 IF nums[b] <= pivot THEN22 a ← a + 123 temp ← nums[a]24 nums[a] ← nums[b]25 nums[b] ← temp26 ENDIF27 NEXT b28 temp ← nums[a + 1]29 nums[a + 1] ← nums[hi]30 nums[hi] ← temp31 RETURN a + 132ENDFUNCTION33
34PROCEDURE quickSort(lo : INTEGER, hi : INTEGER)35 DECLARE p : INTEGER36 IF lo < hi THEN37 // Place the pivot, then sort each side of it38 p ← partition(lo, hi)39 CALL quickSort(lo, p - 1)40 CALL quickSort(p + 1, hi)41 ENDIF42ENDPROCEDURE43
44CALL quickSort(1, n)45
46FOR i ← 1 TO n47 OUTPUT nums[i]48NEXT iשאלות נפוצות על quick sort
מהי סיבוכיות הזמן של quicksort?
O(n log n) בממוצע, וגם O(n log n) במקרה הטוב, אבל מידרדר ל-O(n²) במקרה הגרוע, כשהחלוקות לא מאוזנות באופן עקבי. צירים אקראיים או חציון של שלושה הופכים את המקרה הגרוע לבלתי סביר מאוד.האם quicksort יציב?
למה quicksort לעתים קרובות מהיר יותר ממיון מיזוג?
O(n log n) שלו, אבל משלם על חוצץ של O(n) ועל יותר העברת נתונים.Quicksort או מיון מיזוג: במה להשתמש?
O(n log n), או כשאתם ממיינים רשימות מקושרות או נתונים חיצוניים שלא נכנסים ל-RAM.למה quicksort הופך ל-O(n²) על מערך ממוין?
n רמות רקורסיה במקום log n. בחירת ציר אקראי או לפי חציון של שלושה שוברת את הדפוס הזה ומחזירה התנהגות של O(n log n).