排序算法(四)——快速排序

本文详细介绍了快速排序算法的基本思想、实现步骤及其性能分析。快速排序是一种高效的排序算法,通过选择轴值进行一趟排序将序列分为两部分,递归地对这两部分进行排序。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

排序

快速排序:

下面我们来研究一下快速排序,快速排序也是经常运用到的,java库中对于基本数据类型的数据就是采用快速排序。

快速排序(quick sort)是对冒泡排序的一种改进,在冒泡排序中,记录的比较和移动是在相邻位置进行,记录每次交换后只能后移一个位置,因而总的比较次数和移动次数较多。在快速排序中,记录的比较和移动是从两端向中间进行的,较大的记录一次就能从前面移动到后面,较小的记录一次就能从后面移动到前面,记录移动的距离较远,从而减少了总的比较次数和移动次数。

基本思想: 首先选一个轴值(即比较的基准),通过一趟排序将待排序记录分割成独立的两部分,前一部分记录均小于或等于轴值,后一部分记录均大于或等于轴值,然后分别对这两部分重复上述方法,直到整个序列有序。所以快速排序是一个递归的过程

选择轴值:
1.使用第一个记录的关键码;
2.选取序列中间记录的关键码;
3.比较序列中第一个记录、最后一个记录和中间记录的关键码,取关键码居中的作为轴值并调换到第一个记录的位置;
4.随机选取轴值。
轴值的选择将决定两个子序列的长度,子序列的长度最好相等。这里笔者选择第一个记录作为轴值


一次划分:对待排序列进行一次划分的过程。

设待划分的序列是r[s] ~ r[t],设参数i,j分别指向子序列左、右两端的下标s和t,令r[s]为轴值,

(1)j从后向前扫描(找小),直到r[j]<r[i],将r[j]移动到r[i]的位置,使关键码小(同轴值比较)的记录移动到前面去;

(2)i从前向后扫描(找大),直到r[i]>r[j],将r[i]移动到r[j]的位置,使关键码大(同轴值比较)的记录移动到后面去;

(3)重复上述过程,直到i=j。


一次划分如图

quickSort


看看具体过程:

quickSort


快速排序 进行划分
/*
* first end 分别指向待划分区间的最左侧记录和最右侧记录
* ① 初始化 第一个记录为轴值
* ② 右侧扫描 从end向前扫,直到扫描到小于轴值的记录后交换,此时轴值交换到了end所指的位置。
* ③ 左侧扫描 从first想后扫,知道扫描到了大于轴值的记录后交换,此时轴值交换到了first所指的位置。
* ④ 重复 ② ③ 过程直到 first 和 end 相遇,并返回相遇位置(轴值最终位置)
*/

	private int partition(T[] t, int first, int end){
		while(first < end){
			 //右侧扫描 : 将轴值与end指向的记录进行比较。若end大,则end 前移一个位置,直到end小于轴值
			while(first < end && compare(t[first], t[end])){ 
				end--;     
			}
			
			if(first < end){
				T tmp = t[end];    //小的记录移到前面, 交换后轴值为end指向的记录。
				t[end] = t[first];
				t[first] = tmp;
				first ++;
			}
			//左侧扫描 :将轴值与first指向的记录比较。若first小,则 first前移一个位置,直到first大于轴值
			while(first < end && compare(t[first], t[end])){   
				first ++;	
			}
			
			if(first < end){
				T tmp = t[end];     // 大的记录移到后面,交换后轴值又为first指向的记录
				t[end] = t[first];
				t[first] = tmp;
				end --;
			}
			
		}
		return first;
	}


递归过程:快速排序是一个递归的过程,需要不断对子序列进行划分,直到每个序列都为有序序列。
	private void sort(T[] t, int first, int end){
		if(first < end){                                //区间长度大于1,执行一次划分,否则递归结束。
			int center = partition(t, first, end); //一次划分
			sort(t, first, center -1);            // 递归地对左侧子序列进行快速排序
			sort(t, center +1, end);<span style="white-space:pre">	</span>     //递归地对右侧子序列进行快速排序
		}
	}

快速排序主入口:
	/**
	 * 交换排序: 快速排序
	 */
	@Override
	public void quickSort(T[] t) {
		if(t.length > 0){
			sort(t, 0, t.length -1);
		}
		System.out.println("快速排序:");
		print(t);
	}

上面的compare 、 print 方法将在讲解完全部排序介绍,这里仍然才用泛型。


性能分析:  快速排序的趟数取决于递归的深度

(1)在最好情况下,每一次划分对一个记录定位后,该记录的左侧子表与右侧子表的长度相同,时间复杂度为O(nlog2n)。对n个记录的序列进行排序的时间,每次划分后,正好把待划分区间划分成长度相等的两个子序列。

(2)在最坏情况下,待排序列为正序或者逆序。每次划分只得到一个比上一次划分少一个记录的子序列(另一个子序列为空),时间复杂度为 O(N*N)。

(3)平均情况下,由于给出的序列一般都是乱序的,很少情况出现正序或者逆序,所以平均性能的数量级也是O(nlog2n)。

稳定性:

若两个记录A和B值相等,但是排序后A、B的先后次序保持不变,则这种排序是稳定的,否则就是不稳定。快速排序是一种不稳定的排序算法。

总结: 

(1)由于快速排序是递归的,需要一个栈来存放每一层递归调用的信息,其容量与递归调用的深度一致,平均深度为:O(logN)。

(2)快速排序是一种不稳定的排序算法。

(3)快速排序适用于待排记录个数很大并且原始记录随机(这也是数据的主要情况:多、乱)。

(4)快速排序的平均性能很好。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值