快速排序
随机化快速排序
- 基本思想:
 
通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
- 运行效率分析:
 
快速排序是一种比较快速的排序算法,它的平均运行时间是O(nlogn) ,之所以特别快速,是由于其非常精练和高度优化的内部循环,当然,最坏情形下,性能为O(n^2) 。像归并一样,快速排序也是一种体现分治思想的递归算法,空间性能上来看,快排只需要一个元素的辅助空间,但是弊端却是,需要一个栈空间来实现递归,空间复杂度也是O(nlogn)。
3.图解理解
我们在一个数组中,选择一个基点,比如第一个位置的6,然后将6挪动到正确位置,让在这个基点之前的子数组中数据都小于6,之后的子数组中数据都大于6,然后逐渐递归下去完成整个排序

显而易见的,最关键的就是要将基点数据挪动到正确位置上,这是快速排序的核心,我们称之为Partition。
过程如下所示,我们用 i 来记录当前遍历比较的元素位置:

Partition过程,让我们用代码来实现一下
int类型的一般情形:
void swap(int* arr, int i, int j) {
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}
int partition(int arr[], int l, int r) {
    int v = arr[l]; // 选择第一个元素作为基准元素
    int j = l; // 初始化左边界指针
    for (int i = l + 1; i <= r; i++) { // 从基准元素的下一个位置开始遍历
        if (arr[i] < v) { // 如果当前元素小于基准元素
            j++; // 左边界指针向右移动
            // 交换当前元素和左边界元素
            swap(arr, j, i);
        }
    }
    // 将基准元素放到它应该在的位置
    swap(arr, l, j);
    return j; // 返回基准元素的位置
}
        通用情形:
// 通用交换函数
void swap(void* arr, size_t i, size_t j, size_t element_size) {
    void* temp = malloc(element_size);
    if (temp == NULL) {
        perror("Failed to allocate memory for swap");
        exit(EXIT_FAILURE);
    }
   
    memcpy(temp, (char*)arr + i * element_size, element_size);
    memcpy((char*)arr + i * element_size, (char*)arr + j * element_size, element_size);
    memcpy((char*)arr + j * element_size, temp, element_size);
   
    free(temp);
}
// 通用比较函数的类型
typedef int (*compare_fn)(const void*, const void*);
// 分区函数
size_t partition(void* arr, size_t l, size_t r, size_t element_size, compare_fn cmp) {
    void* pivot = (char*)arr + l * element_size; // 选择第一个元素作为基准元素
    size_t j = l; // 初始化左边界指针
    for (size_t i = l + 1; i <= r; i++) { // 从基准元素的下一个位置开始遍历
        void* current = (char*)arr + i * element_size;
        if (cmp(current, pivot) < 0) { // 如果当前元素小于基准元素
            j++; // 左边界指针向右移动
            // 交换当前元素和左边界元素
            swap(arr, j, i, element_size);
        }
    }
    // 将基准元素放到它应该在的位置
    swap(arr, l, j, element_size);
    return j; // 返回基准元素的位置
}
// 示例比较函数(用于整数数组)
int compare_int(const void* a, const void* b) {
    return (*(int*)a - *(int*)b);
}
        我们可以发现,如果我们对近乎有序的数组进行快排,每次partition分区后前后两个子数组的大小极其不平衡,容易性能衰减到**O(n^2)**的时间复杂度算法,我们需要对上面代码进行优化,我们可以随机选择一个基点作为比较基点,这种方法被称为随机快速排序算法,我们在选择比较数据的时候做一个随机数处理,随机选择数组中一数据和基点数据进行交换。
swap( arr, l , (int)(Math.random()*(r-l+1))+l );
代码实例
// 交换函数
void swap(int* arr, int i, int j) {
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}
// 分区函数
int partition(int* arr, int l, int r) {
    // 随机选择一个基准元素
    int random_index = l + rand() % (r - l + 1);
    swap(arr, l, random_index);
    int pivot = arr[l];
   
    // 初始化指针
    int j = l;
    for (int i = l + 1; i <= r; i++) {
        if (arr[i] < pivot) {
            j++;
            swap(arr, j, i);
        }
    }
    swap(arr, l, j);
    return j;
}
// 快速排序函数
void quicksort(int* arr, int l, int r) {
    if (l >= r) {
        return;
    }
    int p = partition(arr, l, r);
    quicksort(arr, l, p - 1);
    quicksort(arr, p + 1, r);
}
// 辅助函数:打印数组
void printArray(int* arr, int n) {
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}
// 辅助函数:生成随机数组
int* generateRandomArray(int n, int rangeL, int rangeR) {
    int* arr = (int*)malloc(n * sizeof(int));
    if (arr == NULL) {
        perror("Failed to allocate memory");
        exit(EXIT_FAILURE);
    }
    for (int i = 0; i < n; i++) {
        arr[i] = rand() % (rangeR - rangeL + 1) + rangeL;
    }
    return arr;
}
// 主函数
int main() {
    srand(time(NULL)); // 初始化随机种子
    // Quick Sort也是一个O(nlogn)复杂度的算法
    // 可以在1秒之内轻松处理100万数量级的数据
    int N = 1000000;
    int* arr = generateRandomArray(N, 0, 100000);
    quicksort(arr, 0, N - 1);
    printArray(arr, 100); // 打印前100个元素以验证排序结果
   
    free(arr); // 释放内存
    return 0;
}
        Java 代码:
            
            
              java
              
              
            
          
          /**
 * 随机化快速排序
 */
public class QuickSort {
    // 对arr[l...r]部分进行partition操作
    // 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]
    private static int partition(Comparable[] arr, int l, int r){
        // 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot
        swap( arr, l , (int)(Math.random()*(r-l+1))+l );
        Comparable v = arr[l];
        // arr[l+1...j] < v ; arr[j+1...i) > v
        int j = l;
        for( int i = l + 1 ; i <= r ; i ++ )
            if( arr[i].compareTo(v) < 0 ){
                j ++;
                swap(arr, j, i);
            }
        swap(arr, l, j);
        return j;
    }
    // 递归使用快速排序,对arr[l...r]的范围进行排序
    private static void sort(Comparable[] arr, int l, int r){
        if (l >= r) {
            return;
        }
        int p = partition(arr, l, r);
        sort(arr, l, p-1 );
        sort(arr, p+1, r);
    }
    public static void sort(Comparable[] arr){
        int n = arr.length;
        sort(arr, 0, n-1);
    }
    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }
    // 测试 QuickSort
    public static void main(String[] args) {
        // Quick Sort也是一个O(nlogn)复杂度的算法
        // 可以在1秒之内轻松处理100万数量级的数据
        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        sort(arr);
        SortTestHelper.printArray(arr);
    }
}
        双路快速排序
- 基本思想:
 
双路快速排序算法是随机化快速排序的改进版本,partition 过程使用两个索引值(i、j)用来遍历数组,将 <v 的元素放在索引i所指向位置的左边,而将 >v 的元素放在索引j所指向位置的右边,v 代表标定值。
2.运行效率分析:
时间和空间复杂度同随机化快速排序。
对于有大量重复元素的数组,如果使用上一节随机化快速排序效率是非常低的,导致 partition 后大于基点或者小于基点数据的子数组长度会极度不平衡,甚至会退化成 O(n*2) 时间复杂度的算法,对这种情况可以使用双路快速排序算法。
3.过程图解
我们用两个索引值( i , j )来遍历我们的序列,将 <= v 的元素放在索引为 i 所指向位置的左边,而将 >= v 的元素放在索引 j 所指向的位置的右边,以此来平衡左右两边子数组的数据个数。

同样,让我们分别用Java和C来实现一下
先来看C ;
            
            
              java
              
              
            
          
          // 交换函数
void swap(int* arr, int i, int j) {
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}
// 分区函数
int partition(int* arr, int l, int r) {
    // 随机选择一个基准元素
    int random_index = l + rand() % (r - l + 1);
    swap(arr, l, random_index);
    int v = arr[l];
   
    // 初始化指针
    int i = l + 1, j = r;
    while (1) {
        while (i <= r && arr[i] < v) {
            i++;
        }
        while (j >= l + 1 && arr[j] > v) {
            j--;
        }
        if (i > j) {
            break;
        }
        swap(arr, i, j);
        i++;
        j--;
    }
    swap(arr, l, j);
    return j;
}
// 快速排序函数
void quicksort(int* arr, int l, int r) {
    if (l >= r) {
        return;
    }
    int p = partition(arr, l, r);
    quicksort(arr, l, p - 1);
    quicksort(arr, p + 1, r);
}
// 辅助函数:打印数组
void printArray(int* arr, int n) {
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}
// 辅助函数:生成随机数组
int* generateRandomArray(int n, int rangeL, int rangeR) {
    int* arr = (int*)malloc(n * sizeof(int));
    if (arr == NULL) {
        perror("Failed to allocate memory");
        exit(EXIT_FAILURE);
    }
    for (int i = 0; i < n; i++) {
        arr[i] = rand() % (rangeR - rangeL + 1) + rangeL;
    }
    return arr;
}
// 主函数
int main() {
    srand(time(NULL)); // 初始化随机种子
    int N = 1000000;
    int* arr = generateRandomArray(N, 0, 100000);
    quicksort(arr, 0, N - 1);
    printArray(arr, 100); // 打印前100个元素以验证排序结果
   
    free(arr); // 释放内存
    return 0;
}
        再来看Java;
            
            
              java
              
              
            
          
          /**
 * 双路快速排序
 */
public class QuickSort2Ways {
    //核心代码---开始
    private static int partition(Comparable[] arr, int l, int r){
        // 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot
        swap( arr, l , (int)(Math.random()*(r-l+1))+l );
        Comparable v = arr[l];
        // arr[l+1...i) <= v; arr(j...r] >= v
        int i = l+1, j = r;
        while( true ){
            while( i <= r && arr[i].compareTo(v) < 0 )
                i ++;
            while( j >= l+1 && arr[j].compareTo(v) > 0 )
                j --;
            if( i > j )
                break;
            swap( arr, i, j );
            i ++;
            j --;
        }
        swap(arr, l, j);
        return j;
    }
    //核心代码---结束
    // 递归使用快速排序,对arr[l...r]的范围进行排序
    private static void sort(Comparable[] arr, int l, int r){
        if (l >= r) {
            return;
        }
        int p = partition(arr, l, r);
        sort(arr, l, p-1 );
        sort(arr, p+1, r);
    }
    public static void sort(Comparable[] arr){
        int n = arr.length;
        sort(arr, 0, n-1);
    }
    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }
    // 测试 QuickSort
    public static void main(String[] args) {
        // 双路快速排序算法也是一个O(nlogn)复杂度的算法
        // 可以在1秒之内轻松处理100万数量级的数据
        // Quick Sort也是一个O(nlogn)复杂度的算法
        // 可以在1秒之内轻松处理100万数量级的数据
        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        sort(arr);
        SortTestHelper.printArray(arr);
    }
}
        三路排序算法
最后,进入这种排序的最优方案
- 基本思想
 
三路快速排序是双路快速排序的进一步改进版本,三路快排将排序的数据分为三部分,分别小于v,等于v,大于v,v为标定值,在这样的三部分数据中,等于v的数据在下一次递归中不再需要排序,这样很好的降低了递归的深度,另一方面也减少了栈溢出的可能性,小于v和大于v的数据也不会出现哪一个特别多的情形,此方法可以让三路快速排序算法的性能更优。
2.性能分析
时间和空间复杂度同随机化快速排序。
三路快速排序算法是使用三路划分策略对数组进行划分,对处理大量重复元素的数组有非常高效提高快排 的过程。它添加了 处理等于划分元素值的逻辑,将所有等于划分元素的值集中在一起。
3.过程图解

我们分三种情况来讨论partition过程,i用以表示遍历的当前索引位置;
当前处理的元素e = V,元素e直接纳入蓝色区间,同时 i 向后移一位。

当前处理元素e < v, e和等于V区间的第一个数值进行交换,同时gt索引向前移动一位

当前处理元素 e>v,e 和 gt-1 索引位置的数值进行交换,同时 gt 索引向前移动一位。

最后当 i=gt 时,结束遍历,同时需要把 v 和索引 lt 指向的数值进行交换,这样这个排序过程就完成了,然后对 <V 和 >V 的数组部分用同样的方法再进行递归排序。
最后我们用代码来完整实现一下三路快速排序算法;
C语言:
            
            
              java
              
              
            
          
          // 交换函数
void swap(int* arr, int i, int j) {
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}
// 三路分区函数
void threeWayPartition(int* arr, int l, int r, int* lt, int* gt) {
    int v = arr[l];
    *lt = l;
    *gt = r + 1;
    int i = l + 1;
    while (i < *gt) {
        if (arr[i] < v) {
            swap(arr, i, *lt + 1);
            i++;
            (*lt)++;
        } else if (arr[i] > v) {
            (*gt)--;
            swap(arr, i, *gt);
        } else {
            i++;
        }
    }
    swap(arr, l, *lt);
}
// 快速排序函数
void quicksort(int* arr, int l, int r) {
    if (l >= r) {
        return;
    }
    // 随机选择一个基准元素
    int random_index = l + rand() % (r - l + 1);
    swap(arr, l, random_index);
   
    int lt, gt;
    threeWayPartition(arr, l, r, <, >);
   
    quicksort(arr, l, lt - 1);
    quicksort(arr, gt, r);
}
// 辅助函数:打印数组
void printArray(int* arr, int n) {
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}
// 辅助函数:生成随机数组
int* generateRandomArray(int n, int rangeL, int rangeR) {
    int* arr = (int*)malloc(n * sizeof(int));
    if (arr == NULL) {
        perror("Failed to allocate memory");
        exit(EXIT_FAILURE);
    }
    for (int i = 0; i < n; i++) {
        arr[i] = rand() % (rangeR - rangeL + 1) + rangeL;
    }
    return arr;
}
// 主函数
int main() {
    srand(time(NULL)); // 初始化随机种子
    int N = 1000000;
    int* arr = generateRandomArray(N, 0, 100000);
    quicksort(arr, 0, N - 1);
    printArray(arr, 100); // 打印前100个元素以验证排序结果
   
    free(arr); // 释放内存
    return 0;
}
        Java:
            
            
              java
              
              
            
          
          /**
 * 三路快速排序
 */
public class QuickSort3Ways {
    //核心代码---开始
    // 递归使用快速排序,对arr[l...r]的范围进行排序
    private static void sort(Comparable[] arr, int l, int r){
        if (l >= r) {
            return;
        }
        // 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot
        swap( arr, l, (int)(Math.random()*(r-l+1)) + l );
        Comparable v = arr[l];
        int lt = l;     // arr[l+1...lt] < v
        int gt = r + 1; // arr[gt...r] > v
        int i = l+1;    // arr[lt+1...i) == v
        while( i < gt ){
            if( arr[i].compareTo(v) < 0 ){
                swap( arr, i, lt+1);
                i ++;
                lt ++;
            }
            else if( arr[i].compareTo(v) > 0 ){
                swap( arr, i, gt-1);
                gt --;
            }
            else{ // arr[i] == v
                i ++;
            }
        }
        swap( arr, l, lt );
        sort(arr, l, lt-1);
        sort(arr, gt, r);
    }
    //核心代码---结束
    public static void sort(Comparable[] arr){
        int n = arr.length;
        sort(arr, 0, n-1);
    }
    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }
    // 测试 QuickSort3Ways
    public static void main(String[] args) {
        // 三路快速排序算法也是一个O(nlogn)复杂度的算法
        // 可以在1秒之内轻松处理100万数量级的数据
        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        sort(arr);
        SortTestHelper.printArray(arr);
    }
}