面试-数据结构算法-Java快速排序

学习笔记用途,部分内容参考各位前辈的。快速排序基于分治法进行。通过递归进行实现,基本思想就是:一般选取第一个值作为基准,过程是将比这个值大的放在它右边,小的放在这个值的左边。一次运算结束条件是low=high,经过一次运算,基准值的位置就确定好了,下次递归的对这个值的左右两部分分别进行上述操作。

一、具体过程:

无序的数组为R[low,high]

 

分解:

在R[low..high]中任选一个记录作为基准(middle),以此基准将当前无序区划分为左、右两个较小的子区间R[low, middlepos-1)和R[middlepos+1,high],并使左边子区间中所有记录的关键字均小于等于基准记录(不妨记为middle)的关键字middle.key,右边的子区间中所有记录的关键字均大于等于middle.key,而基准记录middle则位于正确的位置(middlepos)上,它无须参加后续的排序。

 注意:
   划分的关键是要求出基准记录所在的位置middlepos。划分的结果可以简单地表示为(注意middle=R[middlepos]):
   R[low..middle-1].keys≤R[middlepos].key≤R[middlepos+1..high].keys

 

  其中low≤middlepos≤high。

 

 求解:

     通过递归调用快速排序对左、右子区间R[low..middlepos-1]和R[middlepos+1..high]快速排序。

具体一次排序的过程:

二、代码实现:

package kuaipai;

/**
 * @author wsz
 *
 */

public class TestKuaipai {
	public static void main(String[] args) {
		/**
		 * 对方法进行测试,输入八个待排序的数
		 */
        int R[] = {49, 38, 65, 97, 76, 13, 27, 49};
        
        QuickSort(R,0,7);//递归过程不需要返回值是因为对数组中的元素直接进行交换操作,改变了数值
        for(int i = 0;i < 8; i++)
        	System.out.print(R[i] + ",");
		
	}
    /**
     * 快排算法执行部分。R是待排序的数组,表示排序的范围是R[low,high]
     * @param R
     * @param low
     * @param high
     */
    public static void QuickSort(int[] R, int low, int high) {
    	
		int middlepos; //划分函数基准记录的位置
		if(low < high){//当区间长度大于1需要排序
			middlepos = Partion(R, low, high);//对R[low,high]进行划分,找到基准middle的准确位置
			QuickSort(R, low, middlepos - 1);//对左区间递归排序
			QuickSort(R, middlepos + 1, high);//对右区间递归排序
		} 
	}
    
    /**
     * 对R[low,high]划分,返回基准middle值的准确位置
     * 步骤:
     * @param R
     * @param low
     * @param high
     * @return
     */
    public static int Partion(int[] R, int low, int high){
    	int middle = R[low];//区间第一个数作为基准
    	while(low < high){//从区间两端交替向中间扫描,直到i=j结束
    		while(low < high && R[high] >= middle)//从high右往左扫面,只要比middle大,(说明正常)就往左继续--,直到找到比middle小的
    			high--;
    		if(low < high)//表示上一步找到了右侧high指的数据小于middle,要交换
    			R[low++] = R[high];//相当于交换了R[low]和R[high],交换完了让low+1 右移。这部分相当于R[high]没动,但是下次R[high]会被替换
    		                       //最终重合了,R[low]被覆盖掉的middle值已经保存好了,再赋值回来即可,整个交换过程相当于只有middle掉了
    		while(low < high && R[low] <= middle)//从low向右扫面,只要比middle小,(说明正常)就往右继续++,直到找到比middle大的
    			low++;
    		if(low < high)//表示上一步找到了左侧low指的数据大于middle,要交换
    			R[high--] = R[low];//相当于交换了R[low]和R[high],交换完了让high-1 左移
    	}
    	R[low] = middle; //找到基准的准确位置
    	return low;//返回基准的位置
    }
}

三、运算复杂度分析:

快速排序只需要大约nlog2n次比较操作,在最坏的情况下需要大约1/2 n2 次比较操作。所以运算的复杂度,是平均O(nlog2n),最坏O(n^2)但这种状况并不常见。事实上,快速排序通常明显比其他O(nlog2n)算法更快,因为它的内部循环可以在大部分的架构上很有效率地被实现出来。数据结构课本中,算法复杂度的近似如下:c<log2n<n<nlog2n<n^2<n^3<2^n<3^n<n!。对下列结论仅仅记住结论我认为就可以了。

 

最优情况下时间复杂度

        快速排序最优的情况就是每一次取到的元素都刚好平分整个数组

        此时的时间复杂度公式则为:T[n] = 2T[n/2] + f(n);T[n/2]为平分后的子数组的时间复杂度,f[n] 为平分这个数组时所花的时间;

        下面来推算下,在最优的情况下快速排序时间复杂度的计算(用迭代法):

                                         T[n] =  2T[n/2] + n                                                                     ----------------第一次递归

                 令:n = n/2        =  2 { 2 T[n/4] + (n/2) }  + n                                               ----------------第二次递归

                                            =  2^2 T[ n/ (2^2) ] + 2n

                令:n = n/(2^2)   =  2^2  {  2 T[n/ (2^3) ]  + n/(2^2)}  +  2n                         ----------------第三次递归  

                                            =  2^3 T[  n/ (2^3) ]  + 3n

                ......................................................................................                        

                令:n = n/(  2^(m-1) )    =  2^m T[1]  + mn                                                  ----------------第m次递归(m次后结束)

               当最后平分的不能再平分时,也就是说把公式一直往下跌倒,到最后得到T[1]时,说明这个公式已经迭代完了(T[1]是常量了)。

               得到:T[n/ (2^m) ]  =  T[1]    ===>>   n = 2^m   ====>> m = logn;

               T[n] = 2^m T[1] + mn ;其中m = logn;

               T[n] = 2^(logn) T[1] + nlogn  =  n T[1] + nlogn  =  n + nlogn  ;其中n为元素个数

               又因为当n >=  2时:nlogn  >=  n  (也就是logn > 1),所以取后面的 nlogn;

               综上所述:快速排序最优的情况下时间复杂度为:O( nlogn )

还可以参考如下的理解方式理解最优情况下的时间复杂度:(但是不够精确,因为分到第二层的时候应该是(N-1)/2才对 ,而不是N/2)

 

最差情况下时间复杂度

        最差的情况就是每一次取到的元素就是数组中最小/最大的,这种情况其实就是冒泡排序了(每一次都排好一个元素的顺序)

     这种情况时间复杂度就好计算了,就是冒泡排序的时间复杂度:T[n] = n * (n-1) = n^2 + n;

     综上所述:快速排序最差的情况下时间复杂度为:O( n^2 )

 

平均时间复杂度

       快速排序的平均时间复杂度也是:O(nlogn)

四、快速排序的优化:

1.对于基准位置的选取一般有三种方法:固定切分,随机切分和三取样切分。固定切分的效率并不是太好,随机切分是常用的一种切分,效率比较高,最坏情况下时间复杂度有可能为O(N2).对于三数取中选择基准点是最理想的一种。

2.快速排序在序列中元素很少时,效率将比较低,不然插入排序,因此一般在序列中元素很少时使用插入排序,这样可以提高整体效率

 

五、快速排序是不稳定的排序算法,

不稳定发生在基准元素与A[tail+1]交换的时刻。

  比如序列:{ 1, 3, 4, 2, 8, 9, 8, 7, 5 },基准元素是5,一次划分操作后5要和第一个8进行交换,从而改变了两个元素8的相对次序。

 排序算法的稳定性大家应该都知道,通俗地讲就是能保证排序前2个相等的数其在序列的前后位置顺序和排序后它们两个的前后位置顺序相同。在简单形式化一下,如果Ai = Aj,Ai原来在位置前,排序后Ai还是要在Aj位置前。

  Java系统提供的Arrays.sort函数。对于基础类型,底层使用快速排序。对于非基础类型,底层使用归并排序。请问是为什么?

  答:这是考虑到排序算法的稳定性。对于基础类型,相同值是无差别的,排序前后相同值的相对位置并不重要,所以选择更为高效的快速排序,尽管它是不稳定的排序算法;而对于非基础类型,排序前后相等实例的相对位置不宜改变,所以选择稳定的归并排序。 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值