三路快排(基于三指针单趟排序的快速排序)+快排时间复杂度再分析

目录

 一.前言

二. 三路快排

😍算法思想:

😍算法实现步骤:

😍三指针单趟排序的实现:​

😍非递归快排完全体:

🤔与C标准库里的快排进行对比测试:

三.快排时间复杂度再分析


 一.前言

http://t.csdn.cn/mz8dghttp://t.csdn.cn/mz8dghttp://t.csdn.cn/1TqDphttp://t.csdn.cn/1TqDp

  • 😄关于快排的基本思想和实现及其优化
  • 😄利用双指针单趟排序实现的快速排序有一个无法避免的缺陷:当待排序序列中有大量(或全部)元素相同时,快排的时间复杂度会升阶为O(N^2),此时快排的递归树线型结构,递归的深度为O(N),时间消耗空间消耗都非常巨大:
  • 😄为了避免这种情况出现,就需要重新设计一下快排的单趟排序,目的在于:当待排序序列中存在大量相同元素时,减小快排递归树的深度

二. 三路快排

😍算法思想:

  • 🤪三路快排的单趟排序是利用三指针算法来实现的
  • 🤪其基本思想是利用三个指针将数组从左到右划分为三个部分,第一部分中所有元素都比key小,第二部分中所有元素都等于key,第三部分中所有元素都大于key
  • 🤪后续就可以对数组第一部分和第三部分进行分治,数组的第二部分所有元素已经处于它们在有序序列中的最终位置,无须再进行处理
  • 🤪三路快排的边界条件有点折磨人​​

😍算法实现步骤:

  • 🤪三个指针的初始位置如图所示
  • 🤪left是待排序区间的左边界,right是待排序区间的右边界,待排序区间闭区间
  • 🤪算法实现思路:
  • 🤪利用midi指针遍历待排序序列,遍历限制条件为:midi<greater.
  1. 😝若arr[midi]比key大,令grater指针减一,并将arr[midi]交换到[greater,right]区间中,midi指针不动
  2. 😝若arr[midi]比key小,令small指针加一, 并将arr[midi]交换到[left+1,small]区间中,midi指针向前走一步
  3. 😝若arr[midi]与key相同,midi指针向前走一步,其余指针不动,目的是将等于key元素的arr[midi]"括入"[small+1,midi)区间
  • 😝重复上述过程直到midi指针和geater指针相遇,算法gif:
  • 😝经过上述过程,最终[left+1,small]区间中的所有元素一定比key小,[greater,right]区间中的所有元素一定比key大,[small+1,midi)区间中的所有元素一定等于key:
  • 😝同时注意,迭代过程结束后,small最终指向的元素一定小于key,所以最后一步就是将arr[small]和arr[left]进行交换,于是数组就被划分成了三个部分:
  • 😝接下来就可以对区间[left,small-1]区间[greater,right]进行分治形成递归完成快速排序

😍三指针单趟排序的实现:

void QuickSort(int* arr, int left, int right)
{
	assert(arr);
	int key = left;
	int midi = left + 1;
	int small = left;
	int greater = right + 1;
	while (midi < greater)
	{
		if (arr[midi] < arr[key])	   //将arr[midi]交换到[left + 1, small]区间中,同时注意small位置的元素一定比key元素小
		{
			++small;
			if (small != midi)
			{
				swap(&arr[small], &arr[midi]);
			}
			++midi;
		}
		else if (arr[midi] > arr[key]) //将arr[midi]交换到[greater,right]区间
		{
			--greater;
			swap(&arr[midi], &arr[greater]);
		}
		else
		{
			++midi;					   //将等于key元素的arr[midi]"括入"[small+1,midi)区间中
		}
	}
	swap(&arr[small], &arr[key]);      //small最终指向的元素一定小于key
}

接下来再进行分治递归并给出递归出口完成快速排序:

😍非递归快排完全体:

  • 😝同时辅以三数取中优化
    void swap(int* e1, int* e2)
    {
    	assert(e1 && e2);
    	int tem = *e1;
    	*e1 = *e2;
    	*e2 = tem;
    }
    
    //三数取中优化
    int GetMid(int* arr,int left,int right)
    {
    	int mid = left + ((right - left) >> 2);     //在arr[left],arr[mid],arr[right]三者中取中间值作为key,返回key的下标
    	if (arr[left] < arr[right])
    	{
    		if (arr[left] < arr[mid] && arr[mid] < arr[right])
    		{
    			return mid;
    		}
    		else if (arr[mid] > arr[right])
    		{
    			return right;
    		}
    		else
    		{
    			return left;
    		}
    	}
    	else
    	{
    		if (arr[left] > arr[mid] && arr[mid] > arr[right])
    		{
    			return mid;
    		}
    		else if (arr[mid] > arr[left])
    		{
    			return left;
    		}
    		else
    		{
    			return right;
    		}
    	}
    }
    void QuickSort(int* arr, int left, int right)
    {
    	if (left >= right)                 //递归出口
    	{
    		return;
    	}
    	assert(arr);
    	int key = left;
        swap(&arr[left], &arr[GetMid(arr, left, right)]);
    	int midi = left + 1;
    	int small = left;
    	int greater = right + 1;
    	while (midi < greater)
    	{
    		if (arr[midi] < arr[key])	   //将arr[midi]交换到[left + 1, small]区间中,同时注意small位置的元素一定比key元素小
    		{
    			++small;
    			if (small != midi)
    			{
    				swap(&arr[small], &arr[midi]);
    			}
    			++midi;
    		}
    		else if (arr[midi] > arr[key]) //将arr[midi]交换到[greater,right]区间
    		{
    			--greater;
    			swap(&arr[midi], &arr[greater]);
    		}
    		else
    		{
    			++midi;					   //将等于key元素的arr[midi]"括入"[small+1,midi)区间中
    		}
    	}
    	//small指向的元素一定小于key
    	swap(&arr[small], &arr[key]);      //将key交换到其应该出现的最终位置
    	QuickSort(arr, left, small - 1);   //分治左子数组
    	QuickSort(arr, midi,right);		   //分治右子数组
    }
  • 🤔经过三数取中三指针优化后的快排就可以对任意序列进行高效排序,不会再出现时间复杂度升阶为O(N^2)的情况

  • 🤔力扣排序测试:(该测试非常针对未经优化和非三指针的快排)912. 排序数组 - 力扣(Leetcode)https://leetcode.cn/problems/sort-an-array/description/

🤔与C标准库里的快排进行对比测试:

int main()
{
    srand((unsigned int)time(0));
	const int N = 10000000;
	int* arr1 = (int*)malloc(sizeof(int) * N);
	int* arr2 = (int*)malloc(sizeof(int) * N);
	int* arr3 = (int*)malloc(sizeof(int) * N);
	for (int i = 0; i < N; ++i)
	{
		arr1[i] = rand();
		arr2[i] = arr1[i];
		arr3[i] = arr1[i];
	}

	int begin2 = clock();
	qsort(arr2, N, sizeof(int), cmp);
	int end2 = clock();
	printf("qsort:%d\n", end2 - begin2);

	int begin3 = clock();
	QuickSort(arr3, 0,N-1);
	int end3 = clock();
	printf("QuickSort:%d\n", end3 - begin3);
    
    free(arr1);
    free(arr2);
    free(arr3);
}

  • 🤔有点奇怪的是在我的机器环境中,我自己写的快排比标准库里的快排还要快一倍左右(可执行程序为release版本) 

三.快排时间复杂度再分析

  • 😍设N为待排序序列元素个数
  • 😍以下分析中的log都表示以2为底的对数
  • 😍经过三数取中三指针优化后的快排分治递归递归树可以认为在处理任何序列时都接近一颗满二叉树:(注意数组的分割点不参与后续的单趟排序)
  • 😍从递归树第一层开始,递归树每一层所有单趟排序所需遍历元素总个数依次为:N+(N-1)+(N-3)+(N-7)......即快排的时间复杂度计算公式为:
  • 😍将上述复杂度公式进行求和运算,取b = logN可得:
  • 😍再化简可得:
  • 😍可见快速排序时间复杂度O(NlogN)的基础上存在进一步的微收敛,这使得快速排序在四个时间复杂度数量级O(NlogN)的排序算法中独占鳌头进而成为工业级排序中用的最多的排序算法。(四个时间复杂度为O(NlogN)数量级的排序算法分别为:希尔排序,堆排序,归并排序和快速排序)

 

  • 94
    点赞
  • 85
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 138
    评论
### 回答1: ,现在是2020年6月9日。 我可以提供一种快速排序算法,它的时间复杂度为O(nlog n),其中n是数组的大小。它的基本思想是:选择一个基准元素,将数组分割成两个子数组,其中一个子数组中的元素都小于基准元素,另一个子数组中的元素都大于基准元素,然后递归地对子数组进行排序。 ### 回答2: 快速排序(Quick Sort)是一种常用的排序算法,其时间复杂度为O(nlogn)。下面是一个简单的快速排序算法的实现: 1. 首先,从数组中选择一个元素作为基准(pivot)。 2. 创建两个指针,一个指向数组的起始位置(low),另一个指向数组的结束位置(high)。 3. 将基准元素放在合适的位置上,使得其左边的元素都比它小,右边的元素都比它大。可以通过以下过程实现: - 从high指针开始往前遍历,如果遇到一个比基准小的元素,则将其放在low指针所指的位置,并将low指针向后移动一位; - 从low指针开始往后遍历,如果遇到一个比基准大的元素,则将其放在high指针所指的位置,并将high指针向前移动一位; - 重复上述过程,直到low和high指针相遇。 4. 根据low指针的位置,将数组划分为两个子数组,分别对两个子数组递归地进行快速排序。 下面是使用Python实现的快速排序算法代码: ```python def quick_sort(arr): if len(arr) <= 1: return arr pivot = arr[len(arr) // 2] left = [x for x in arr if x < pivot] middle = [x for x in arr if x == pivot] right = [x for x in arr if x > pivot] return quick_sort(left) + middle + quick_sort(right) ``` 这段代码使用了列表推导式来将数组划分为小于、等于和大于基准元素的部分。然后,通过递归地对左右两个子数组进行快速排序,最终合并排序后的子数组。 快速排序算法具有较小的时间复杂度,适用于对大规模数据进行排序。但在最坏情况下,即已排序的数组中,快速排序算法时间复杂度可能为O(n^2),因此在实际应用中需要进行优化。 ### 回答3: 快速排序是一种常用的排序算法,它的时间复杂度可以达到O(nlogn)。下面是一个简单的快速排序算法实现: 快速排序的基本思路是通过一趟排序将待排序的数据分割成独立的两部分:小于基准值的元素和大于基准值的元素。然后再分别对这两部分进行排序。具体步骤如下: 1. 选择一个基准值,可以是数组的第一个元素,也可以是随机选择的元素。 2. 设定两个指针,左指针指向数组的第一个元素,右指针指向数组的最后一个元素。 3. 左指针向右移动,直到找到大于等于基准值的元素停止。 4. 右指针向左移动,直到找到小于等于基准值的元素停止。 5. 交换左指针和右指针指向的元素。 6. 重复步骤3~5,直到左指针大于等于右指针。 7. 将基准值和右指针指向的元素交换。 8. 对基准值左边的子数组和右边的子数组分别进行快速排序。 以下是一个示例的快速排序实现: ``` def quicksort(arr, low, high): if low < high: pivot_index = partition(arr, low, high) quicksort(arr, low, pivot_index - 1) quicksort(arr, pivot_index + 1, high) def partition(arr, low, high): pivot = arr[low] left = low + 1 right = high done = False while not done: while left <= right and arr[left] <= pivot: left = left + 1 while arr[right] >= pivot and right >= left: right = right - 1 if right < left: done= True else: arr[left], arr[right] = arr[right], arr[left] arr[low], arr[right] = arr[right], arr[low] return right arr = [5, 8, 2, 6, 1, 9, 3, 7] quicksort(arr, 0, len(arr) - 1) print(arr) ``` 该实现中,使用递归的方式对左右两个子数组进行快速排序,实现了快速排序算法时间复杂度主要取决于划分的次数,即递归调用quick_sort的次数,该次数与数组的大小以对数关系增长,因此该实现的时间复杂度为O(nlogn)。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力的青菜

谢谢各位爹娘的打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值