快速排序相关——基本快排实现,优化,第K大数

快速排序

//部分参考维基百科 https://zh.wikipedia.org/wiki/

目录

快速排序

基本介绍

整体的思路

代码实现

第K大数字:

最坏情况优化:

与堆排序、归并排序的比较 


基本介绍

在平均状况下,排序n个项目要O(nlogn)次比较。在最坏状况下则需要O(n^2)次比较,但这种状况并不常见。事实上,快速排序O(nlogn)通常明显比其他算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地达成;快速排序是不稳定的

使用的思想是分治法,分成两组,递归的解决

最坏情况下

出现在输入完全逆序的情况下

每次划分产生的两组分别包含n-1个和0个元素,整个的运行时间递归式可以表示为:

T(n) = T(n-1)+θ(n),也就是O(n^2)级别

最好情况划分

最平衡的划分中,Partition得到的两个子问题的规模都不大于n/2,这种情况下快速排序的性能非常好

T(n)=2*T(n/2)+θ(n),也即是θ(nlogn)

快速排序的平均运行时间更接近于其最好情况,基本上只要划分是常数比例的,算法的运行时间总是O(nlogn)

整体的思路

1、从待排序数列中选择一个基准值(key)

2、重新排列数组,使得比key小的元素在key的左边,比key大的元素在key右边,这个操作称为分割(partition)

3、递归的把小于key的部分数列和大于key的部分数列进行重排

递归到最底部时,数组的长度为0或1,该算法一定会结束,因为至少会把一个元素摆到后面的位置去

C++中的 sort函数就是实现的快排算法

代码实现

下面给出一种原址排序的C++实现方法:

p是第一个下标位置,r是最后一个数字的下标位置

在Partition中,以最后一个元素为key值进行划分,在剩下的元素中找出比k小的元素,交换到左边 --- 以i记录比key小的最后一个元素的下标,j去找等待被交换回来的小于k的元素的下标

关键是记住i的初始化,先更新i再交换,以及最后返回的key在第i+1位置上

int Partition(int p, int r)
{
	int k = a[r];
	int i = p - 1;
	for (int j = p; j < r; j++)
	{
		if (a[j] <= k)
		{
			i++;
			swap(a[i], a[j]);
		}
	}
	swap(a[i + 1], a[r]);
	return i + 1;
}
void quickSort(int p, int r)
{
	if (p < r)
	{
		int q = Partition(p, r);
		quickSort(p, q - 1);
		quickSort(q + 1, r);
	}
}

另一种方法的区别是Partition部分,采用“挖坑填坑”的思想,用i,j分别从两边找,以最后一个为k的值的话,i从前往后找比k大的,j从后往前找比k小的,直到i==j为止

int Partition2(int p, int r)
{
	int k = a[r];
	int i = p, j = r;
	while (i<j)
	{
		while (i<j && a[i]<k)
		{
			i++;
		}
		if (i < j) { a[j] = a[i]; j--; }
		while (i<j && a[j]>k)
		{
			j--;
		}
		if (i < j) { a[i] = a[j]; i++; }
	}
	a[i] = k;
	return i;
}

第K大数字:

可以排序后返回第k大数字,时间复杂度为O(nlogn);

 

还可以利用快排,直接复用上面的Partition代码就可以

有几点要注意:返回的索引q在当前数组里是第key大的,不是直接比较q与k; 另外在k>key时,不在求的是第k大,而是第k-key大;

还有递归出口是p==r的情况

int selectKBiggest(int p, int r, int k)
{
	if (p == r)return a[p];
	int q = Partition(p, r);
	int key = q - p+1 ;
	if (k == key)return a[q];
	else if (k < key)
		return selectKBiggest(p, q - 1, k);
	else return selectKBiggest(q + 1, r, k-key);
}

非递归版本:

int selectKUnRec(int p, int r, int k)
{
	while (p <= r)
	{
		int q = Partition(p, r);
		int key = q - p + 1;
		if (k == key)return a[q];
		else if (k < key)r = q-1;
		else { p = q + 1; k = k - key; }
	}
}

只需要一个while就可以了,该上下下标和k值就好了

 

更方便的方式不再需要变化k:partition返回下标q,p..q-1处的数字都小于等于a[q];q+1..r的下标都大于等于a[q]。第k大数字的下标再排好序的数组里就是第a.size()-k个位置处,比如第一大的数字就在从小到大排序数组中的最后一个下标,这是个不变的值。所以只要比较返回的q是否等于我们要的index就可

class Solution {
public:
    int partition(vector<int>& nums, int p, int r){
        int i = p-1;
        int key = nums[r];
         for(int j = p;j<r;j++){
             if(nums[j]<=key){
                 i++;
                 swap(nums[i],nums[j]);
             }
         }
         swap(nums[i+1],nums[r]);
         return i+1;
    }
    int findKthLargest(vector<int>& nums, int k) {
        int r = nums.size()-1;
        int p = 0;
        int ans =0;
        k = nums.size()-k;
        while(p<=r){
            int q = partition(nums,p,r);
            
            if(q == k){
                ans = nums[q];
                break;
            }
            else if(q < k){p = q+1;}
            else{r=q-1;}
        }
        return ans;
    }
};

 

还可以用堆排序来解决

建立最大堆后,堆顶就是第一大的数;与最后一个元素调换后,重新建堆,堆顶就是第二大的元素,所以要做k-1此对调,此时的根元素就是第k大元素:

class Solution {
public:
    void maxHeapify(vector<int>& a, int i, int heapSize) {
        int l = i * 2 + 1, r = i * 2 + 2, largest = i;
        if (l < heapSize && a[l] > a[largest]) {
            largest = l;
        } 
        if (r < heapSize && a[r] > a[largest]) {
            largest = r;
        }
        if (largest != i) {
            swap(a[i], a[largest]);
            maxHeapify(a, largest, heapSize);
        }
    }

    void buildMaxHeap(vector<int>& a, int heapSize) {
        for (int i = heapSize / 2; i >= 0; --i) {
            maxHeapify(a, i, heapSize);
        } 
    }

    int findKthLargest(vector<int>& nums, int k) {
        int heapSize = nums.size();
        buildMaxHeap(nums, heapSize);
        for (int i = nums.size() - 1; i >= nums.size() - k + 1; --i) {
            swap(nums[0], nums[i]);
            --heapSize;
            maxHeapify(nums, 0, heapSize);
        }
        return nums[0];
    }
};

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/kth-largest-element-in-an-array/solution/shu-zu-zhong-de-di-kge-zui-da-yuan-su-by-leetcode-/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

最坏情况优化:

分治方法,分成的两个数组差不多大时,效率是最高的,所以基准的选择很重要;前面都是选择了固定的最后一个元素作为划分的基准;

在此基础上稍作改进可以是随机的选择基准值,能一定程度提高,但是最坏的情况下还是O(n^2);

进一步的改进是选用三数取中的方法:

最佳的取值应当是待排序数组分成相等的两组,选择中间那个数;但是要精确的找到中间那个数是很费时的,所以可以从三个数中选择中间那个值,实际上选择首、尾和中间值,找到这三个数中中间位置的数作为key即可,这样可以消除预输入不好的情况;当然也可以由三数取中换成五数取中等

但是三数取中优化不了重复数组的情况

在这上面的进一步优化的考虑可以是,在划分到一定程度后,用插入排序来替代继续划分,因为当数组部分有序时,快排没有插入排序效率高;

if (high - low + 1 < 10)
{
	InsertSort(arr,low,high);
	return;
}//else时,正常执行快排

或者在一次划分结束后,将与key相等的元素聚集到一起,划分时不对其进行划分:  可以在划分时,将与key相等的数移动到两端,然后划分结束后再移回中轴附近

还有一种优化思路是,在QuickSort中有两次递归,可以改成一次尾递归:

void QSort(int arr[],int low,int high)
{ 
	int pivotPos = -1;
	if (high - low + 1 < 10)
	{
		InsertSort(arr,low,high);
		return;
	}
	while(low < high)
	{
		pivotPos = Partition(arr,low,high);
		QSort(arr,low,pivot-1);
		low = pivot + 1;
	}
}
--------------------- 
作者:insistgogo 
来源:CSDN 
原文:https://blog.csdn.net/insistGoGo/article/details/7785038 
版权声明:本文为博主原创文章,转载请附上博文链接!

上面的博主实验结果:这里效率最好的快排组合 是:三数取中+插排+聚集相等元素,它和STL中的Sort函数效率差不多

与堆排序、归并排序的比较 

 平均时间复杂度最好最坏辅助空间稳定性
堆排序O(nlogn)O(nlogn)O(nlogn)O(1)不稳定
归并排序O(nlogn)O(nlogn)O(nlogn)O(1)稳定
快速排序O(nlogn)O(nlogn)O(n^2)看实现不稳定

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值