1.基本思想
和归并排序一样,快速排序也采用分治的思想,选取一个元素作为枢pivot
,把原始的数组筛选成较小和较大的两个子数组,使得在pivot
左边的元素都小于pivot
,在pivot
右边的元素都大于pivot
,然后递归地排序两个子数组。
例子:对数组 [2, 1, 7, 9, 5, 8] 进行排序。
2.C++代码实现
编译环境:win10
系统下VS2013IDE
/* C++ 实现快速排序 */
#include <iostream>
using namespace std;
// A utility function to swap two elements
void swap(int* a, int* b)
{
int t = *a;
*a = *b;
*b = t;
}
int partition(int arr[], int low, int high)
{
#if 1
//在区间随机选取一个数作为pivot
if (high > low) {
int randNum = rand() % (high - low + 1) + low;//生成介于min和max之间的伪随机数
swap(arr[randNum], arr[high]);
}
#endif
int pivot = arr[high]; // pivot
int i = (low - 1); // Index of smaller element
for (int j = low; j <= high - 1; j++)
{
// If current element is smaller than the pivot
if (arr[j] < pivot)
{
i++; // increment index of smaller element
swap(&arr[i], &arr[j]);
}
}
swap(&arr[i + 1], &arr[high]);
return (i + 1);
}
/* The main function that implements QuickSort
arr[] --> Array to be sorted,
low --> Starting index,
high --> Ending index */
void quickSort(int arr[], int low, int high)
{
if (low < high)
{
/* pi is partitioning index, arr[p] is now
at right place */
int pi = partition(arr, low, high);
// Separately sort elements before
// partition and after partition
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
/* Function to print an array */
void printArray(int arr[], int size)
{
for (int i = 0; i < size; i++)
cout << arr[i] << " ";
cout << endl;
}
// Driver Code
int main()
{
srand((unsigned)time(NULL)); // 设置随机数种子
int arr[] = { 2, 1, 7, 9, 5, 8 };
int n = sizeof(arr) / sizeof(arr[0]);
cout << "给定数组为:";
printArray(arr, n);
quickSort(arr, 0, n - 1);
cout << "快速排序后:";
printArray(arr, n);
system("pause");
return 0;
}
输出:
给定数组为:2 1 7 9 5 8
快速排序后:1 2 5 7 8 9
请按任意键继续. . .
3.算法分析
3.1 空间复杂度
和归并排序不同,快速排序在每次递归的过程中,只需要开辟 O(1)
的存储空间来完成交换操作实现直接对数组的修改,又因为递归次数为 logn
,所以它的整体空间复杂度完全取决于压堆栈的次数,因此它的空间复杂度是 O(logn)
。
3.2 时间复杂度
-
最优情况:被选出来的基准值都是当前子数组的中间数。
这样的分割,能保证对于一个规模大小为n
的问题,能被均匀分解成两个规模大小为n/2
的子问题(归并排序也采用了相同的划分方法),时间复杂度就是:T(n) = 2×T(n/2) + O(n)
。
把规模大小为n
的问题分解成n/2
的两个子问题时,和基准值进行了n-1
次比较,复杂度就是O(n)
。很显然,在最优情况下,快速排序的复杂度也是O(nlogn)
。 -
最坏情况:基准值选择了子数组里的最大或者最小值
每次都把子数组分成了两个更小的子数组,其中一个的长度为 1,另外一个的长度只比原子数组少 1。
优化:可以通过随机地选取基准值来避免出现最坏的情况。
int randNum = rand() % (high - low + 1) + low;//生成介于low和high之间的伪随机数
4.leetcode练习
LeetCode 第 215 题,给定一个尚未排好序的数组,要求找出第 k 大的数。
解法 1:利用 C++ 的 STL 中的集成的排序方法,直接将数组进行排序,然后得出结果。
class Solution {
public:
//思路:先排序,然后取第k大的数
int findKthLargest(vector<int>& nums, int k) {
sort(nums.rbegin(), nums.rend());
return nums[k-1];
}
};
解法 2:快速排序。
每次随机选取一个基准值,将数组分成较小的一半和较大的一半,然后检查这个基准值最后所在的下标是不是 k,算法复杂度只需要 O(n)。
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
//思路:采用quick sort,当下标为len-k的数排好序,即找到第k大的数
int len = nums.size();
int target = len-k;//第k大的元素,下标为len-k
int low=0, high=len-1;
while(true){
int pi=partition(nums, low, high);
if(pi==target){
return nums[pi];
}else if(pi>target){
high=pi-1;
}else{
low=pi+1;
}
}
}
int partition(vector<int>& nums, int low, int high){
//在区间随机选取一个数作为pivot
if(high>low){
int randNum = rand() % (high-low) + low;//生成介于min和max之间的伪随机数
swap(nums[randNum], nums[high]);
}
int pivot=nums[high];
int i=low-1;
for(int j=low;j<=high-1;++j){
if(nums[j]<pivot){
++i;
swap(nums[i],nums[j]);
}
}
swap(nums[i+1], nums[high]);
return (i+1);
}
};
5.参考文档
QuickSort
LeetCode 215. Kth Largest Element in an Array 数组中第k大的数字
通过 partition 减治 + 优先队列(Java、C++、Python)