文章目录
递归快速排序法是一种基于分治策略的高效排序算法,其核心思想是通过选择一个基准元素将数组划分为两个子数组,使得左子数组元素均小于基准,右子数组元素均大于基准,然后递归地对这两个子数组进行排序,最终实现整个数组的有序排列。
算法原理
1、选择基准元素(Pivot):从待排序的数组中选择一个元素作为基准元素。基准元素的选择可以影响排序的效率,常见的选择方法有选择第一个元素、最后一个元素、中间元素或随机元素等。为了优化性能,可以采用"三数取中"法,即选择数组首、中、尾三个元素的中位数作为基准。
2、分区操作(Partition):将数组划分为两个子数组,使得左子数组中的所有元素都小于基准元素,右子数组中的所有元素都大于基准元素。分区操作可以通过双指针技术实现,一个指针从数组的开头向右移动,另一个指针从数组的末尾向左移动,当左指针指向的元素小于等于基准元素且右指针指向的元素大于等于基准元素时,交换这两个元素的位置。当左指针和右指针相遇时,分区操作完成,基准元素被放置在正确的位置上。
3、递归排序:对分区后得到的两个子数组分别递归地调用快速排序算法,直到子数组的长度为1或0时停止递归,此时子数组已经有序。
算法实现(以C语言为例)
左右指针法
1、选出一个pivot(基准),一般是最左边或是最右边的。
2、定义一个begin和一个end(及代码中的i、j),begin从左向右走,end从右向左走。(需要注意的是:若选择最左边的数据作为pivot,则需要end先走;若选择最右边的数据作为pivot,则需要bengin先走,如果是最右边先做为基准,代码中partition函数返回i,并且将基准值与arr[i]交换)。
3、最左边做为基准为例,在走的过程中,若end遇到小于key的数,则停下,begin开始走,直到begin遇到一个大于key的数时,将begin和right的内容交换,end再次开始走,如此进行下去,直到begin大于等于end(此时begin数组已经被分为大于基准和小于基准的两部分,而begin处于从左往右第一个大于基准的数,end处于从右往左第一个小于基准的数),此时将end(即从右往左第一个小于基准的数)与基准互换。
4.此时pivot的左边都是小于pivot的数,pivot的右边都是大于pivot的数
5.将key的左序列和右序列再次进行这种单趟排序,如此反复操作下去,直到左右序列只有一个数据,或是左右序列不存在时
bash
#include <stdio.h>
// 交换两个元素的值
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
// 分区操作
int partition(int arr[], int low, int high) {
int pivot = arr[low]; // 选择第一个元素作为基准元素
int i = low + 1; // 从第二个元素开始向右移动
int j = high; // 从最后一个元素开始向左移动
while (1) {
// 从左向右找到第一个大于等于基准元素的元素
while (i <= j && arr[i] < pivot) {
i++;
}
// 从右向左找到第一个小于等于基准元素的元素
while (i <= j && arr[j] > pivot) {
j--;
}
// 如果指针相遇,分区操作完成
if (i >= j) {
break;
}
// 交换两个元素的位置
swap(&arr[i], &arr[j]);
i++;
j--;
}
// 将基准元素放置在正确的位置上
swap(&arr[low], &arr[j]);
return j; // 返回基准元素的索引
}
// 递归快速排序
void quickSort(int arr[], int low, int high) {
if (low < high) {
// 获取分区点
int pivotIndex = partition(arr, low, high);
// 递归排序左子数组
quickSort(arr, low, pivotIndex - 1);
// 递归排序右子数组
quickSort(arr, pivotIndex + 1, high);
}
}
int main() {
int arr[] = {30, 20, 50, 60, 10, 40};
int n = sizeof(arr) / sizeof(arr[0]);
printf("排序前的数组:\n");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
quickSort(arr, 0, n - 1);
printf("排序后的数组:\n");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
挖坑法
选出一个数据(一般是最左边或是最右边的)存放在key变量中,在该数据位置形成一个坑。
定义一个left和一个right,left从左向右走(当遇到大于key的值时停下来)。right从右向左走(当遇到小于key的值时停下来)。(若在最左边挖坑,则需要right先走;若在最右边挖坑,则需要left先走) 。
把right的那个小的数放在坑中,在把left那个位置的值放在right那个位置中。
重复操作,直到left>right时结束,完成一趟,把key放在了正确的位置。
方法一:
通过交换数组实现,较为简单
bash
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* 临时数组实现快速排序 */
void quick_sort_temp(int arr[], int left, int right) {
if (left >= right) return; /* 递归终止条件 */
int pivot = arr[left]; /* 选择首元素为基准 */
int* left_arr = (int*)malloc((right - left) * sizeof(int));
int* right_arr = (int*)malloc((right - left) * sizeof(int));
int left_size = 0, right_size = 0;
/* 分区:将元素分配到左右临时数组 */
for (int i = left + 1; i <= right; i++) {
if (arr[i] < pivot) {
left_arr[left_size++] = arr[i];
} else {
right_arr[right_size++] = arr[i];
}
}
/* 合并临时数组到原数组 */
memcpy(arr + left, left_arr, left_size * sizeof(int));
arr[left + left_size] = pivot; /* 放置基准值 */
memcpy(arr + left + left_size + 1, right_arr, right_size * sizeof(int));
/* 递归排序左右子数组 */
quick_sort_temp(arr, left, left + left_size - 1);
quick_sort_temp(arr, left + left_size + 1, right);
/* 释放临时数组 */
free(left_arr);
free(right_arr);
}
/* 打印数组 */
void print_array(int arr[], int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main() {
int arr[] = {5, 3, 8, 6, 2, 7, 1, 4};
int size = sizeof(arr) / sizeof(arr[0]);
printf("Original array: ");
print_array(arr, size);
quick_sort_temp(arr, 0, size - 1);
printf("Sorted array: ");
print_array(arr, size);
return 0;
}
方法二:
以下代码实现通过交换数据实现:
bash
#include <stdio.h>
// 挖坑法实现快速排序的分区函数
int partition(int arr[], int left, int right) {
int pivot = arr[left]; // 选取第一个元素作为基准值
while (left < right) {
// 从右向左扫描,找到第一个小于基准值的元素
while (left < right && arr[right] >= pivot) {
right--;
}
arr[left] = arr[right]; // 将其填到左边的坑位中
// 从左向右扫描,找到第一个大于基准值的元素
while (left < right && arr[left] <= pivot) {
left++;
}
arr[right] = arr[left]; // 将其填到右边的坑位中
}
arr[left] = pivot; // 将基准值填入左指针和右指针相遇的位置
return left; // 返回基准值的位置
}
// 快速排序函数
void quickSort(int arr[], int left, int right) {
if (left < right) {
int pivotIndex = partition(arr, left, right); // 获取基准值的位置
quickSort(arr, left, pivotIndex - 1); // 对左边的子数组进行排序
quickSort(arr, pivotIndex + 1, right); // 对右边的子数组进行排序
}
}
// 打印数组函数
void printArray(int arr[], int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main() {
int arr[] = {5, 3, 8, 6, 2, 7, 1, 4};
int size = sizeof(arr) / sizeof(arr[0]);
printf("Original array: ");
printArray(arr, size);
quickSort(arr, 0, size - 1);
printf("Sorted array: ");
printArray(arr, size);
return 0;
}
前后标记法(双指针法)
思路:
1、选出一个key,一般是最左边。
2、起始时,prev指针指向序列开头,cur指针指向prev+1。
3、让cur一直向前走,当遇到小于a[key]时,让prev向前走一格(这个值一定大于a[key],因为是cur走过的),然后a[cur]和a[prev]交换。
4、最后,将key与a[prev]交换。
5、递归以上操作。
bash
#include <stdio.h>
// 分区函数(前后标记法)
int partition(int arr[], int low, int high) {
int key = arr[low]; // 选择最左边元素作为基准
int prev = low; // prev指针指向序列开头
int cur = low + 1; // cur指针指向prev+1
while (cur <= high) {
// 当cur遇到小于key的元素时
if (arr[cur] < key) {
prev++; // prev向前移动一格
// 交换prev和cur位置的元素
int temp = arr[prev];
arr[prev] = arr[cur];
arr[cur] = temp;
}
cur++; // cur继续向前移动
}
// 将基准元素放到正确位置
arr[low] = arr[prev];
arr[prev] = key;
return prev; // 返回基准位置
}
// 快速排序主函数
void quickSort(int arr[], int left, int right) {
if (left > right) // 递归终止条件
return;
int pivotIndex = partition(arr, left, right);
quickSort(arr, left, pivotIndex - 1); // 排序左子序列
quickSort(arr, pivotIndex + 1, right); // 排序右子序列
}
// 测试代码
int main() {
int arr[] = {10, 7, 8, 9, 1, 5, 3};
int n = sizeof(arr) / sizeof(arr[0]);
printf("Original array: ");
for (int i = 0; i < n; i++)
printf("%d ", arr[i]);
quickSort(arr, 0, n - 1);
printf("\nSorted array: ");
for (int i = 0; i < n; i++)
printf("%d ", arr[i]);
return 0;
}
算法优化
1、随机化选择基准元素:为了避免最坏情况的发生,可以随机选择基准元素,使得每次划分得到的子数组大小更加均衡。
2、"三数取中"法:选择数组首、中、尾三个元素的中位数作为基准元素,可以进一步优化划分的均衡性。
3、小数组优化:当子数组的长度较小时(如小于10),可以采用插入排序等简单排序算法进行排序,以减少递归调用的开销。
4、尾递归优化:通过尾递归技术减少递归调用的深度,从而降低空间复杂度。