04快速排序

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 时间复杂度

  1. 最优情况:被选出来的基准值都是当前子数组的中间数。
    这样的分割,能保证对于一个规模大小为n的问题,能被均匀分解成两个规模大小为 n/2 的子问题(归并排序也采用了相同的划分方法),时间复杂度就是:T(n) = 2×T(n/2) + O(n)
    把规模大小为 n 的问题分解成 n/2 的两个子问题时,和基准值进行了 n-1 次比较,复杂度就是 O(n)。很显然,在最优情况下,快速排序的复杂度也是 O(nlogn)

  2. 最坏情况:基准值选择了子数组里的最大或者最小值
    每次都把子数组分成了两个更小的子数组,其中一个的长度为 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)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值