快速排序(Quick Sort)是一种非常高效的排序算法,广泛用于实践中。在这篇文章中,我们将详细介绍快速排序的工作原理、C语言实现,并提供一些优化建议、常见问题的解答以及编程技巧。
快速排序是分治算法的一种,它的基本思想是:
x
:通常选择数组的中间位置,或者随机选一个元素作为分界点。x
小的元素放到左边,比 x
大的元素放到右边。以下是一个快速排序算法模板的C语言实现:
#include
#define N 100010
int n;
int q[N];
// 快速排序
void quick_sort(int q[], int l, int r) {
// 递归终止条件
if (l >= r) return;
// 选择分界点
int i = l - 1, j = r + 1, x = q[(l + r) >> 1];
// 划分区间
while (i < j) {
do i++; while (q[i] < x); // 找到比分界点大的元素
do j--; while (q[j] > x); // 找到比分界点小的元素
if (i < j) {
// 交换元素
int temp = q[i];
q[i] = q[j];
q[j] = temp;
}
}
// 递归处理子问题
quick_sort(q, l, j);
quick_sort(q, j + 1, r);
}
int main() {
// 输入数据
scanf("%d", &n);
for (int k = 0; k < n; k++) scanf("%d", &q[k]);
// 调用快速排序
quick_sort(q, 0, n - 1);
// 输出排序结果
for (int k = 0; k < n; k++) printf("%d ", q[k]);
return 0;
}
quick_sort(int q[], int l, int r)
:通过递归对数组进行排序。l
和 r
表示当前区间的左右边界。l
不再小于右边界 r
时,递归结束。之所以说更正规,是因为算法教材上一般都是这么写的
快速排序的核心在于其划分步骤,即如何将数据划分成两个子数组。提供的代码采用的是Hoare分区方案,这是一种经典而高效的划分方法。
partition():划分函数
// 划分函数
int partition(int a[], int l, int r)
{
// 选择基准元素
int i = l, j = r + 1;
int x = a[l];
// 划分区间
while (true)
{
while (a[++i] < x && i < r) // 从左向右找第一个大于等于 x 的元素, 注意边界
;
while (a[--j] > x && j > l) // 从右向左找第一个小于等于 x 的元素, 注意边界
;
if (i >= j) // 如果两个指针相遇,则退出循环
break;
// 交换元素
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
// 此时 j 指向分界点元素
a[l] = a[j]; // 将分界点元素放到正确的位置
a[j] = x; // 将基准元素放到最终位置
return j; // 返回分界点位置
}
参数:
a[]
: 需要排序的数组。l
: 当前子数组的起始索引。r
: 当前子数组的结束索引。作用:
partition()
函数的主要任务是选择一个“基准”元素(代码中选择的是子数组的第一个元素,即 a[l]
),然后重新排列子数组,使得所有小于基准的元素位于基准的左边,而所有大于基准的元素位于基准的右边。函数最终返回基准元素在排序后应处的位置。过程描述:
x
。i
和 j
分别从左到右,以及从右到左扫描数组,找到需要交换的元素。i
、j
未相遇时,交换两个指针当前指向的元素。j
。randomPartition():随机化划分
// 随机选择基准值
int randomPartition(int q[], int l, int r)
{
int i = l + rand() % (r - l + 1); // 随机选择一个元素作为基准元素
int temp = q[i]; // 将分界点元素与最左边元素交换
q[i] = q[l];
q[l] = temp;
return partition(q, l, r); // 调用 partition 函数进行划分
}
i
,然后将 a[i]
和 a[l]
交换,以此来改变基准元素。partition()
函数执行实际的划分步骤。quick_sort():快速排序递归函数
// 快速排序函数
void quick_sort(int q[], int l, int r)
{
if (l >= r)
return;
int i = randomPartition(q, l, r); // 随机选择分界点并进行划分
// 以下没有对分界点进行处理,因为分界点已经在正确的位置上了
quick_sort(q, l, i - 1); // 递归处理左子区间
quick_sort(q, i + 1, r); // 递归处理右子区间
}
参数:
q[]
: 待排序的数组。l
: 当前处理子数组的起始索引。r
: 当前处理子数组的结束索引。作用:
q[]
的子数组 q[l...r]
进行排序。过程描述:
l < r
),进行递归排序:
randomPartition()
来确定一个基准位置 i
。q[l...i-1]
进行递归排序。q[i+1...r]
进行递归排序。#include
#include
#define N 100010
int n;
int q[N];
// 划分函数
int partition(int a[], int l, int r)
{
// 选择基准元素
int i = l, j = r + 1;
int x = a[l];
// 划分区间
while (true)
{
while (a[++i] < x && i < r) // 从左向右找第一个大于等于 x 的元素, 注意边界
;
while (a[--j] > x && j > l) // 从右向左找第一个小于等于 x 的元素, 注意边界
;
if (i >= j) // 如果两个指针相遇,则退出循环
break;
// 交换元素
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
// 此时 j 指向分界点元素
a[l] = a[j]; // 将分界点元素放到正确的位置
a[j] = x; // 将基准元素放到最终位置
return j; // 返回分界点位置
}
// 随机选择基准值
int randomPartition(int q[], int l, int r)
{
int i = l + rand() % (r - l + 1); // 随机选择一个元素作为基准元素
int temp = q[i]; // 将分界点元素与最左边元素交换
q[i] = q[l];
q[l] = temp;
return partition(q, l, r); // 调用 partition 函数进行划分
}
// 快速排序函数
void quick_sort(int q[], int l, int r)
{
if (l >= r)
return;
int i = randomPartition(q, l, r); // 随机选择分界点并进行划分
// 以下没有对分界点进行处理,因为分界点已经在正确的位置上了
quick_sort(q, l, i - 1); // 递归处理左子区间
quick_sort(q, i + 1, r); // 递归处理右子区间
}
int main()
{
// 输入数据
scanf("%d", &n);
for (int k = 0; k < n; k++)
scanf("%d", &q[k]);
// 调用快速排序
quick_sort(q, 0, n - 1);
// 输出排序结果
for (int k = 0; k < n; k++)
printf("%d ", q[k]);
return 0;
}
快速排序的空间复杂度主要来自于递归调用栈。每次递归都在栈上保存一些状态,递归深度为 O(log K),因此空间复杂度为 O(log K)。另外,快速排序的原地排序性质意味着我们不需要额外的存储空间来保存排序后的结果。
避免重复排序:如果数组的子部分已经有序,可以跳过排序。可以在递归中加入判断条件,只有在需要排序时才进行递归。
随机化分界点:为了避免最坏情况,可以通过随机化分界点来减少数组已经有序时最坏情况的发生几率。
选择合适的基准元素:对于某些特殊数据,选择最小值或最大值作为基准元素可能导致效率较低,可以采用三数取中法来选择基准元素,或使用随机化基准选择。
改进内存管理:在实际开发中,尽量减少不必要的内存分配和释放,合理使用内存空间。
快速排序是一种非常高效的排序算法,在平均情况下具有 O(K log K) 的时间复杂度,能够在大规模数据上高效排序。我们通过理解其分治思想,结合优化建议,能够在实际应用中实现高效排序。
希望本篇文章能帮助你更好地理解快速排序。如果你有任何问题或建议,欢迎在评论区留言讨论!一起进步,一起成长!
希望本篇文章对你学习算法能有所帮助!有任何问题和想法欢迎评论区一起交流探讨、共同进步!