Java 快速排序

快速排序作为一种快速的排序算法,所以现在就详细的介绍一下 。

快速排序是冒泡的一种改进 都是交换类的排序算法

( 冒泡排序是每轮都把当前排序范围中最大的数放到最后 )


快速排序是确定一个PivotKey ,把当前数组范围分成三份,左边是小于PivotKey的,中间是PivotKey,右边是大于PivotKey的

当然可能PivotKey最小 没有左边的部分 ,反之。


快速排序是基于分治策略的一种算法    (分治法的思想是将一个规模为n 的问题分解成k个较小规模的子问题,子问题互相独立且与原问题相同)


快速排序的基本思想是三个步奏:

 1,分解 : 通常以待排列数组第一个元素为 PivotKet(或用随机选择策略 随机选取其中一个元素作为PivotKey), 将数组划分成三段 , 中间是小于PivotKey的元素
 中间是PivotKey 右侧是大于它的元素 (要是PivotKey为最小的元素 就直接去第二步)

 2,递归求解:通过递归手段 对上一步的左边 和 右边的数组进行快速排序

 3,合并 ;由于对于子数组的排列是直接用的原数组,所以在递归结束,整个数组已经是有序的了 不需要额外的合并操作 


上代码,这里的代码只是一种解法  别的解法暂无研究 ,代码有错误  或者不完美的留言指正,你说我就改,就怕你不来


public class Test {

	private static int[] data;
	/**
	 * 对 数组 s 到 e 这一段进行快速排序 步骤如下: 定义 i = s; j = e; i,j 都为数组下标 
	 * 
	 *          从j向前搜索找第一个小于 X 的元素,  i , j 交换 --------------步骤1 
	 *          从i向前搜索找第一个大于 X 的元素,  i , j 交换 --------------步骤2 
	 * 重复步骤 1 2 直到 i = j
	 * 
	 * **** 总的思想就是以PivotKey为中心分解成两拨数的时候 一直都是PivotKey这个数一直在交换 所以我们需要交换一次 就得调转方向
	 * 以便让比较的范围逐渐缩小 也让PivotKey一直向中心靠近
	 */
	public static void Divide(int s, int e) {
		int i, j;
		i = s;
		j = e;
		boolean isLeft = true; // 表示当前PivotKey是在相对于左边一点的位置还是右边一点的位置
				       // 其实就是上一次交换中PivotKey是被换到左边还是右边
		if (i >= j) {
		     return;
		}
		while (i != j) {
			if (data[j] < data[i]) { //  j,i对应的数组元素必有一个是PivotKey  (慢慢根据结果分析得出)
				                     //  当 j = PivotKey,按步骤2 
				                     //  当 i = PivotKey,按步骤1 
				                     //  所以一个判断就搞定  是不是nice
				int temp = data[i];
				data[i] = data[j];
				data[j] = temp;
				isLeft = (isLeft == true) ? false : true;
			}
			if (!isLeft) {    // 每次比较之后,如果PivotKey在右边,根据步骤1,左边的指针后移,反之
				i++;
			} else {
				j--;
			}
		}
		// while循环结束 i,j 相等 停在PivotKey上 所以递归求解时 必须i,j分离
		i--;
		j++;

                //递归时刻  两次递归调用 分别对应分解出的左边和右边的数组
		Divide(s, i);  
		Divide(j, e);
	}
	/**
	 * 
	 * 输出当前数组
	 */
	public static void PrintData() {
		for (int i = 0; i < 10; i++) {
			System.out.print(" " + data[i]);
		}
		System.out.println("");
	}

	public static void main(String[] args) {
		data = new int[10];
		Scanner scanner = new Scanner(System.in);
		System.out.println("输入十个数字进行快速排序");
		for (int m = 0; m < 10; m++) {
			data[m] = scanner.nextInt();
		}
		Divide(0, 9);
		PrintData();
		scanner.close();
	}
}
代码固定了只输入十个数    是为了方便观察结果  数组长度可以自定义,稍微修改下代码即可 


示例输入 : 41 1 23 14 78 55 69 15 6 51
每次交换后的数组变化 :
6 1 23 14 78 55 69 15 41 51
6 1 23 14 41 55 69 15 78 51
6 1 23 14 15 55 69 41 78 51
6 1 23 14 15 41 69 55 78 51
1 6 23 14 15 41 69 55 78 51
1 6 15 14 23 41 69 55 78 51
1 6 14 15 23 41 69 55 78 51
1 6 14 15 23 41 51 55 78 69
1 6 14 15 23 41 51 55 69 78

以上面数据为例   说一下第一次分解的过程

开始   : 41  1  23  14  78  55  69  15  6  51

以41 为PivotKey  , i = 0, j = 9  ,  data[j] < data[i]   即51<41 不满足 ; 

 j-- 变为8  , 6 <41 满足if判断,交换。  

数组变为 :6 1 23 14 78 55 69 15 41 51

 然后i++ 变为1  ,1 不满足 , i++ 变为2 ,23 不满足,i++ 变为3  ,14也不满足,i++变为4 ,78满足交换

数组变为:  6 1 23 14 41 55 69 15 78 51

此时j-- , 变为 7,15 < 41 ,满足 交换

数组变为:  6 1 23 14 15 55 69 41 78 51

此时i++,变为5 ,41 <55  满足交换

数组变为:  6 1 23 14 15 41 69 55 78 51

此时j-- ,变为6 ,69>41 不满足 ,j-- 变为5

 i == j  while循环结束 进入递归。。。。。。。


快速排序的平均时间复杂度 :O(n*lgn) 最坏时间复杂度为 : O(n^2),最好情况:O(nlgn)  空间复杂度 : O(lgn)  


 * 快速排序的时间复杂度与    划分是否对称   有关;
 *   ***最坏情况** 为:每次递归都划分为产生两个区域分别包含 n-1和 1 个元素  由于分解当前数组这部分的时间复杂度为O(n),
 *   所以快排的时间复杂度T(n)满足:
 *               { O(1)             n<=1
 *   T(n) =  |          
 *               { T(n-1) + O(n)    n>1
 *   解此递归方程得  T(n) = O(n^2)
 * 
 *   ***最好情况*** 为:每次划分的 PivotKey都处在最中间,即产生了两个大小为 n/2的区域
 *               { O(1)             n<=1
 *   T(n) =  |          
 *               { 2T(n/2) + O(n)   n>1
 *   解得 :  T(n) = O(nlgn)  


快速排序最坏的情况就是待排数据基本有序,退化为冒泡排序,时间复杂度为O(n^2)

快速排序递归过程对应一棵二叉树,其递归工作栈的大小与递归调用二叉树的深度相对应,最好情况下是一棵完全二叉树 


可以证明快速排序在平均情况下时间复杂度也是O(nlgn)   ~~~~~怎么证明的搞不懂

下一讲将最简单的交换类排序:冒泡










评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值