• 【数据结构与算法】八大排序(中)快速排序 快排居然还能这么优化?快排的非递归该如何写?


    🧸🧸🧸各位大佬大家好,我是猪皮兄弟🧸🧸🧸
    在这里插入图片描述

    今天带来的内容是快速排序

    这里是下面要讲的知识内容🥳🥳🥳


    一、⚽快速排序(原始方法)

    1.🌈单趟代码与解释(升序)

    选出一个key,一般是最左边或者最右边的数据,定义两个指针,左指针从左开始走,左指针找大,右指针从右开始走,右指针找小,然后Swap左指针和右指针的数据,当左指针和右指针相遇的时候,Swap key和该点的数据
    保证在一趟走完之后,key的左边比key小,右边比key大

    //单趟代码
    void QuickSort(int *a,int n)
    {
    	int key=0;
    	int left=0,right=n-1;
    	while(left<rigth)
    	{
    		while(left<right&&a[right]>a[key])
    			right--;
    		while(left<right&&a[left]<a[key])
    			left++;
    		Swap(&a[left],&a[right]);
    	}
    	Swap(a[key],a[left]);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    我们必须保证right要先走,解释如下:

    right先走,到最后,right停下了之后,left++去碰right,因为right找到的是小的数据,如果说让left先走,这种情况下最后停下来的是比key大的数据
    在这里插入图片描述

    2.🌈递归完整代码&&时间复杂度分析

    void QuickSort(int*a,int begin,int end)
    {
    	if()
    		return ;
    	int left=begin;
    	int right=end;
    	int keyi=left;
    	while(left<rigth)
    	{
    		while(left<right&&a[right]>a[keyi])
    			right--;
    		while(left<right&&a[left]<a[keyi])
    			left++;
    		Swap(&a[left],&a[right]);
    	}
    	Swap(a[keyi],a[left]);
    	keyi=left;
    	QuickSort(a,begin,keyi-1);
    	QuickSort(a,keyi+1,end);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    时间复杂度O(NlogN)
    最坏的情况是有序或者接近有序,O(N^2),N+N-1+N-2+…

    二、⚽快速排序(挖坑法)

    按升序来说
    右边找小,填到左边的坑,这个位置成为新的坑,然后左边找大,填到这个坑当中

    int PartSort(int *a,int begin,int end)
    {
    	int key=a[begin];
    	int piti=begin;
    	while(begin<end)
    	{
    		while(begin<end&&a[end]>=key)
    			--end;
    		a[piti]=a[end];
    		piti=end;
    		while(begin<end&&a[begin<=key])
    			++begin;
    		a[piti]=a[begin];
    		piti=begin;
    	}
    	a[piti]=key;
    	return piti;
    }
    
    void QuickSort(int *a,int begin,int end)
    {
    	if(begin>=end)
    	return ;
    	int keyi=PartSort(a,begin,end);
    	QuickSort(a,begin,keyi-1);
    	QuickSort(a,keyi+1,end);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    三、⚽快速排序(前后指针版本)

    思路就是prev指向更新数据的下一个,cur去找比key小的数据(升序)

    int PartSort(int *a,int begin,int end)
    {
    	int prev,cur,key;
    	prev=key=begin;
    	cur=begin+1;
    	while(cur<=end)
    	{
    		if(a[cur]<a[key])
    		{
    			++prev;
    			Swap(&a[cur],&a[prev]);
    		}
    		++cur;
    	}
    	Swap(&a[prev],&a[key]);
    	return prev;
    }
    
    void QuickSort(int *a,int begin,int end)
    {
    	if(begin>=end)
    	return ;
    	int keyi=PartSort(a,begin,end);
    	QuickSort(a,begin,keyi-1);
    	QuickSort(a,keyi+1,end);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    四、⚽快速排序的优化

    可以看出key的取值是很影响QuickSort的效率的,因此,我们需要对快速排序的算法进行优化

    1、🌈随机选key

    随便选一个当做key,可能选到最小的或者最大的那一个,不够好,因为快速排序最理想的就是二分,选到最中间的那个数。所以这种就不进行深度探索了

    2、🌈三数取中

    ****

    void GetMidIndex(int*a,int begin,int end)
    {
    	int mid=(begin+end)/2;
    	if(a[begin]<a[end])
    	{	
    		if(a[mid]>a[end])
    		return end;
    		else if(a[mid]<a[begin])
    		return begin;
    		else
    		return mid;
    	}
    	else
    	{
    		if(a[mid]>begin)
    		return begin;
    		else if(a[mid]<a[end])
    		return end;
    		else
    		return mid;
    	}
    	//找到了之后和begin交换以下即可作为keyi
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    3、🌈处理小区间

    因为递归是个很麻烦的事,当递归划分小区间,区间比较小的时候,就不再递归划分去排序这个小区间,可以考虑用其他排序对小区间处理,比如插入排序
    这样做减少了非常多的递归次数,最后一层就占了总的递归次数的一半,如果能把最后3、4层都去掉,至少减掉了过半的递归次数(可以假设区间小于10就不再递归)

    代码:

    void QuickSort(int *a,int begin,int end)
    {
    	if(begin>end)
    	{
    		return ;
    	}
    	if(end - begin>10)
    	{
    		//快排
    	}
    	else
    	{
    		//其他几种排序算法
    	}
    			
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    4、🌈最终优化代码(没有加上小区间处理)

    void GetMidIndex(int*a,int begin,int end)
    {
    	int mid=(begin+end)/2;
    	if(a[begin]<a[end])
    	{	
    		if(a[mid]>a[end])
    		return end;
    		else if(a[mid]<a[begin])
    		return begin;
    		else
    		return mid;
    	}
    	else
    	{
    		if(a[mid]>begin)
    		return begin;
    		else if(a[mid]<a[end])
    		return end;
    		else
    		return mid;
    	}
    	//找到了之后和begin交换以下即可作为keyi
    }
    
    int PartSort(int *a,int begin,int end)
    {
    	int prev,cur,key;
    	prev=key=begin;
    	cur=begin+1;
    	int midi=GetMidIndex(a,begin,end);
    	Swap(&a[midi],&a[begin]);
    	while(cur<=end)
    	{
    		if(a[cur]<a[key])
    		{
    			++prev;
    			Swap(&a[cur],&a[prev]);
    		}
    		++cur;
    	}
    	Swap(&a[prev],&a[key]);
    	return prev;
    }
    
    void QuickSort(int *a,int begin,int end)
    {
    	if(begin>=end)
    	return ;
    	int keyi=PartSort(a,begin,end);
    	QuickSort(a,begin,keyi-1);
    	QuickSort(a,keyi+1,end);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    五、⚽快速排序非递归

    递归的大问题就是,极端场景下,深度太深,会栈溢出
    1.直接改循环,比如斐void波那契数列、归并排序
    2.用数据结构栈模拟递归过程

    代码:

    void QuickSortNonR(int*a,int begin,int end)
    {
    	ST st;
    	StackInit(&st);
    	StackPush(&st,end);
    	StackPush(&st,begin);
    
    	while(!StackEmpty(&st))
    	{
    		int left = StackTop(&st);
    		StacckPop(&st);
    		int right = StackTop(&st);
    		StacckPop(&st);
    		
    		int keyi=PartSort(a,left,right);
    		if(right>keyi+1)
    		{
    			StackPush(&st,right);
    			StackPush(&st,keyi+1);
    		}
    		if(left<keyi-1)
    		{
    			StackPush(&st,keyi-1);
    			StackPush(&st,left);
    		}
    		
    	}
    	StackDestroy(&st);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    用队列也能完成,用队列的话就是以层序遍历的方式进行,模拟不出函数调用堆栈的实际情况
    只是访问的顺序不一样,要保持需要的那个顺序,那就要用栈

    七、⚽总结

    在八大排序算法中,快速排序是非常快的也是非常重要的,但是普通的快速排序还没有那么快,快的是优化过后的快排
    在这里插入图片描述
    加粗样式

  • 相关阅读:
    Django——模型层进阶
    深度学习_12_softmax_图片识别优化版代码
    Python中的异常处理以及自定义异常类型
    06 C++设计模式之代理(Proxy)模式
    npm改变npm缓存路径和改变环境变量
    SpringMVC简介
    逆势涨薪3k!新媒体运营毅然转行测试,我的入行秘籍是什么?
    【3维视觉】20230922_网格编码最新进展
    微信小程序开发:精细化处理人像动漫化调用之前的人像修复增强
    微信小程序之微信授权登入及授权的流程讲解
  • 原文地址:https://blog.csdn.net/zhu_pi_xx/article/details/126503644