学习C/C++笔记——算法初步——快速排序(QuickSort)

一、原理
    快速排序(QuickSort)主要是采用分治处理的方法,将一个无序(或有序)的数据一分为二,使其左边的数都比基准数(Pivot)小,而右边都比它大。此后分别递归处理左右两端,就可以完成排序。

二、流程
    1.划分区间范围,用L和R变量作为索引(需要参数传进来的左右区间low和high赋值给L和R),分别指向最左端和最右端的索引值。注意调整递归出口条件:low大于等于high
    2.找到一个关键字(Key),如规定L索引的值是key。
    3.先动右边的指针,如果R所指的数据比key大,那么while循环继续将R往左边靠拢,直至重合或数据比key小。若找到一个比key小的数据那么就将L和R的指针所指向的数据交换。
    4.交换完毕后,调换指针运动方向,改变为L向右运动,如果L所指的数据比key小,那么while循环继续将L往左边靠拢,直至重合或数据比key大。
    5.通过以上3、4步的不断的改变,直至两个指针重合时停止,完成一次排序。
    6.递归处理两端数据,分别是最左端区间low和R-1,以及最右端区间high和L+1,两次递归即可。

三、代码

void QuickSort(int arr[], int low, int high) {
	if (low >= high) return;//递归出口条件:当高低指针大小相反或指针重合
	int i = low, j = high;//置指针
	int key = arr[i];//设定关键字
	while (i < j) {//当指针大小顺序正确的时候
		while (i < j && arr[j] >= key) j--;//只动高指针
		swap(arr[i], arr[j]);//当遇到需要交换的数据或者指针重合时
		//调换顺序
		while (i < j && arr[i] <= key) i++;//只动低指针
		swap(arr[i], arr[j]);//当遇到需要交换的数据或者指针重合时
	}
	QuickSort(arr, low, j - 1);
	QuickSort(arr, i + 1, high);
}

四、时间复杂度分析
    1、最好情况:当数组正好分为两个长度长等的子数组
T ( n ) = { O ( 1 ) n = 1 2 T ( n 2 ) + O ( n ) n > 1 T(n)= \begin{cases} O(1) & \text {n = 1} \\ {2T(\frac{n}{2}) + O(n)} & \text{n > 1} \end{cases} T(n)={O(1)2T(2n)+O(n)n = 1n > 1
    由此我们可以得出最终表达式: T ( n ) = O ( n log ⁡ 2 n ) T(n) = O(n{\log _2}n) T(n)=O(nlog2n)

    2、最坏情况:当数组分为0和n-1个长度的子数组
T ( n ) = { O ( 1 ) n = 1 T ( n − 1 ) + O ( n ) n > 1 T(n)= \begin{cases} O(1) & \text {n = 1} \\ {T(n - 1) + O(n)} & \text{n > 1} \end{cases} T(n)={O(1)T(n1)+O(n)n = 1n > 1
    由此我们可以得出最终表达式: T ( n ) = O ( n 2 ) T(n) = O(n^2) T(n)=O(n2)

    3、平均情况:除最坏情况,其他情况会产生深度为 O ( log ⁡ 2 n ) O({\log _2}n) O(log2n)的递归树,每层均是 O ( n ) O(n) O(n)
T ( n ) = { O ( 1 ) n = 1 T ( n − 1 ) + O ( n ) n > 1 T(n)= \begin{cases} O(1) & \text {n = 1} \\ {T(n - 1) + O(n)} & \text{n > 1} \end{cases} T(n)={O(1)T(n1)+O(n)n = 1n > 1
    由此我们可以得出最终表达式: T ( n ) = O ( n log ⁡ 2 n ) T(n) = O(n{\log _2}n) T(n)=O(nlog2n)

五、优化快速排序
    1、减少交换次数:不难发现,在基础版本上,交换可能发生在L=R上,那么这大大地增加了交换次数,导致处理大型数据时可能会造成时间过长(TLE)。只需要将while部分代码变为如下,即可减少交换次数
    原理:当L=R时,只需要交换基准元素,否则交换L和R的元素

while (i < j) {//当指针大小顺序正确的时候
	while (i < j && arr[j] >= key) j--;//只动高指针
	while (i < j && arr[i] <= key) i++;//只动低指针
	swap(arr[i], (i == j) ? arr[low] : arr[j]);//减少一半交换次数
}

    2、随机化:我们容易发现,在固定形式下,平均情况和最好情况的时间复杂度几乎相等,那么我们可以进行随机取值以达到我们的期望

int i = low, j =high, pivot = rand() % (high - low + 1) + low;//随机化
swap(arr[i], arr[pivot]);//基准数交换
int key = arr[i];//设定关键字

    3、小区间插入排序:在经典形式下,当分割的数组长度小于一定范围时,使用插入排序速度比快速排序更快,所以我们改用数组区间小于10时使用插入排序

void InsertSort(int arr[], int i, int j) {//变种版
	for (int l = i + 1; l <= j; l++) {
		int k = l, key = arr[l];
		while (arr[k - 1] > key) {
			arr[k] = arr[k - 1];
			k--;
			if (k == i) break;
		}
		arr[k] = key;
	}
}
void QuickSort_opt3(int arr[], int low, int high) {
	//当区间长度小于10的时候改为插入排序,加快速度
	if (low >= high) return;//递归出口条件:当高低指针大小相反或指针重合
	if (high - low + 1 < 10) {
		InsertSort(arr, low, high);
		return;
	}
	//………………
}







代码分析来源于B站UP:鹤翔万里

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值