05-快速排序

快速排序:和归并排序递归算法类似,采用分而治之的思路,采用枢纽的方式将数组分成两大块(分),进而递归的解决枢纽左边的和解决枢纽右边的(治),将左边+枢纽+右边放进一个数组则完成快速排序。

//伪代码
void quickSort(ElementType A[],int N){
	if (N < 2) return;
	pivot = 从A[]中选一个主元;
	将S = { A[] \ pivot } 分成2个独立子集:
		A1 = { a∈S | a <= pivot } && A2={ a∈S | a >= pivot };
	A[] = quickSort(A1,N1) ∪ {pivot} ∪ quickSort(A2,N2);
}

算法分析:由上面的伪代码可知,快速排序算法并不复杂,一个递归终止条件,一个pivot选取方法,一个划分过程,一个治理过程(递归调用),需要重点关注的是如何选择pivot和如何划分两个子集。划分子集的相对比较固定,而如何选择pivot则关系到算法的效率,最好情况是每次正好中分,从而整体时间复杂度是O(N log N)。

选主元:

  • 最简单的方法是选A[0]?  不科学,容易导致两个子集分布很不均匀,甚至每次只能划分成一个子集导致算法退化到O(N^2)
  • 随机取pivot? rand()函数开销大
  • 三者取中法:取头中尾的中位数(推荐)
//三者取中函数
int median3(vector<int>& arr, int Left, int Right) {
	int mid = Left + (Right - Left) / 2;
	if (arr[Left] > arr[mid]) {
		swap(arr[Left],arr[mid]);
	}
	if (arr[Left]>arr[Right]) {
		swap(arr[Left],arr[Right]);
	}
	if (arr[mid]>arr[Right]) {
		swap(arr[mid],arr[Right]); //arr[Left]<=arr[mid]<=arr[Right]
	}
	swap(arr[mid], arr[Right - 1]); //将pivot藏到右边
        //只需要考虑arr[Left+1] ... arr[Right-1]
	return arr[Right-1];            //返回pivot
}

子集划分:定义左指针i,定义右指针j,找到左边第一个大于pivot的位置,然后找到右边第一个小于pivot的位置,交换i,j位置的元素,直到左指针i大于右指针j,i最后的位置就是pivot的位置。以此划分pivot会放置在其最终位置。

:如果有元素正好等于pivot怎么办? (举个例子:全是1)

  • 停下来作交换?(多了无用的交换操作,i会停在中间位置,导致子集较为均分)
  • 不理它,继续移动指针?(i一直移动,j没机会移动,子集不均分,时间复杂度退化到O(N^2))
  • 综上,选择停下来作交换

:小规模数据的处理,快速排序的问题

  • 用递归会额外占用系统的堆栈空间,而且每一次调用系统堆栈都涉及push和pop操作,耗费时间
  • 对于小规模的数据(比如小于100)可能还不如插入排序快
  • 解决方案:对于大规模数据用快排递归,小规模数据用简单排序(比如插入排序),在程序中定义一个cutoff的阈值
#include<bits/stdc++.h>
using namespace std;

int median3(vector<int>& arr, int Left, int Right) {
	int mid = Left + (Right - Left) / 2;
	if (arr[Left] > arr[mid]) {
		swap(arr[Left],arr[mid]);
	}
	if (arr[Left]>arr[Right]) {
		swap(arr[Left],arr[Right]);
	}
	if (arr[mid]>arr[Right]) {
		swap(arr[mid],arr[Right]);    //arr[Left]<=arr[mid]<=arr[Right]
	}
	swap(arr[mid], arr[Right - 1]);   //将pivot藏到右边
	return arr[Right-1];
}

void qSort(vector<int>&arr,int Left,int Right) {
	if (Right-Left+1 < 2) return;  //保证至少有两个元素
	int pivot = median3(arr,Left,Right);
	int i = Left;
	int j = Right - 1;
	while (i<j) {  
	//改成i<j是因为当有两个元素的时候--j会越界,两个元素的排序通过median3就已经搞定了,i=left=right-1;
		while (arr[++i] < pivot) {};
		while (arr[--j] > pivot) {};
		if (i<j)
			swap(arr[i], arr[j]);
		else
			break;
	}
	swap(arr[i],arr[Right-1]);
	qSort(arr, Left, i-1);
	qSort(arr,i+1,Right);
}

void quickSort(vector<int>&arr) { //提供给外部统一接口,封装了一下qSort
	int n = arr.size();
	qSort(arr,0,n-1);
}

int main() {
	vector<int> arr = { 8,6,10,7,9,4,3,2,5,1 };
	quickSort(arr);
	for (auto x : arr) {
		cout << x << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}
1 2 3 4 5 6 7 8 9 10
请按任意键继续. . .

 性能分析:整体O(nlogn),考虑到堆栈保存耗时间,所以小规模数据(小于100)采用插入排序。

  • 时间复杂度:O(n log n)
  • 空间复杂度:O(log n)
  • 稳定性:不稳定

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值