快速排序
快速排序是对[[冒泡排序]]的优化,使用了分治的思想。
步骤
-
选择基准元素(Pivot):从数组中选择一个元素作为基准。常见的选择方法有:
- 选择第一个元素
- 选择最后一个元素
- 选择中间的元素
- 随机选择一个元素
- 三数取中法(选择第一个、最后一个和中间值的中间值)
-
划分操作(Partition):将数组重新排列,使得所有小于基准的元素都位于基准的左侧,而所有大于基准的元素都位于基准的右侧。基准元素位于最终位置。理想情况下左右划分应相对均匀。
-
递归排序:
- 对基准左侧的子数组进行快速排序
- 对基准右侧的子数组进行快速排序
-
合并:由于快速排序是在原地排序(in-place),不需要额外的合并步骤。
复杂度分析
理想情况(平均情况)
- 假设划分均匀,那么共需要 O ( l o g n ) O(logn) O(logn)时间。
- 每次划分对每个数排序,即
n
次。时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)
最坏情况
- 原数组有序,且基准元素选择不优秀;
假设有数组[1,2,3,4,5,6,7,8,9],基准元素为最后一个,递增排序;
第一次划分
- 基准:9
- 左侧子数组:[1, 2, 3, 4, 5, 6, 7, 8]
- 右侧子数组:[]
第二次划分
- 基准:8
- 左侧子数组:[1, 2, 3, 4, 5, 6, 7]
- 右侧子数组:[]
第三次划分
- 基准:7
- 左侧子数组:[1, 2, 3, 4, 5, 6]
- 右侧子数组:[]
可以看出,这其实就是冒泡,复杂度为 O ( n 2 ) O(n^2) O(n2),也可以看出,这个算法不稳定
总结
- 快速排序是一种高效的排序算法,平均时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)
- 在最坏情况下,时间复杂度可能退化为 O ( n 2 ) O(n^2) O(n2),但通过优化基准元素的选择,可以减少这种情况的发生。
- 快速排序是不稳定的排序算法,可能会改变相同元素的相对顺序。
模板
#define MAX_N 1000000
int N;
int nums[MAX_N];
int partition(int low, int high)
{
int pivot = nums[low];
while (low < high)
{
while (low < high && nums[high] >= pivot) high--;
nums[low] = nums[high];
while (low < high && nums[low] <= pivot) low++;
nums[high] = nums[low];
}
nums[low] = pivot;
return low;
}
void qsort(int low, int high)
{
if (low < high)
{
int mid = partition(low, high);
qsort(low, mid - 1);
qsort(mid + 1, high);
}
}