快速排序

  这次来复习排序,讲一讲快速排序。

  要说应用最广的排序大概就是快速排序了,因为它有着许多优点。实现简单,需要的辅助空间少,需要的时间比其他的排序少。所以,快速排序是一个必须要了解的排序算法。但是它也有着一些缺点,快速排序算法非常的脆弱,这一点下面也会提到。

  快速排序的基本思路就是将排序的元素分为两组,在将两组分别再次排序,两个子数组排序完成后整个数组的排序也就完成了。大体的思路非常简单,和归并排序有点相反,归并排序是将原本的数组分为两个数组,在分别排序子数组后,归并后再用归并排序。这个排序以后有机会再讲(感觉这句话自己打了很多遍)。快速排序相反,将数组分为两个数组,分别将子数组排序后,快速排序就认为数组已经完成了排序。这就是两者的区别。下面讲讲具体的思路。

  快速排序的具体思路如下:先是取出数组中的一个元素(切分元素),将切分元素放入原数组正确的位置,将大于切分元素的元素放到元素的右边,小于的则放到左边。在利用递归分别将左边数组和右边数组排序。排序完成就代表整体排序完成。整体的算法不难想到,同时也可以发现算法的最主要部分就是找到一个合适的切分元素。如果切分元素一直是一个有序数组的头一个元素,就会发现快排的性能还不如插入,这也是前面说到的快速排序的算法非常脆弱。在经过人们的改进后,发现将数组随机打乱后,取数组第一个元素会保证排序算法的稳定性。(非常搞笑,我第一次看到也是这样想的)找到合适的切分元素后,将数组分割和排序也就比较简单了。下面就粘贴代码,同时也对于算法的具体进行解释。

package sf;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class demo {
	/*
	 * 此方法是为了和其他的方法保持一致,和本算法无关
	 */
	public static void sort(Comparable[] a)
	{
		//快速排序
		//将原数组随机打乱
		List s = Arrays.asList(a);
		Collections.shuffle(s);
		Comparable[] b = (Comparable[]) s.toArray();//将数组转换为list,并将其打乱随机,最后在转换为数组
		//调用快速排序的算法,传入的是数组b
		sort(b, 0, a.length-1);
		//打印排序的结果,这里还是打印数组b
		for (int i = 0; i < a.length; i++) {
			System.out.print(b[i] + " ");
		}
	}
	public static void sort(Comparable[] a,int lo,int hi)
	{
		if(hi<=lo) return;
		int j=partition(a, lo, hi);//确定切点
		sort(a, lo,j-1);//切点左边排序。
		sort(a, j+1, hi);//切点右边排序
		
	}
	/*
	 * 快速排序的切分方法
	 */
	public static int partition(Comparable[] a,int lo,int hi)
	{
		int i=lo,j=hi+1;
		Comparable v=a[lo];//将随机的数组的第一个元素作为切分元素,将切分元素的值给v,方便后面的比较。
		//这里还是不清楚Comparable的用法
		/*
		 * 该接口定义类的自然顺序,实现该接口的类就可以按这种方式排序.一般要求:e1.equals((Object)e2)和e1.compareTo(
		 * (Object)e2)==0具有相同的值,这样的话我们就称自然顺序就和equals一致. 这个接口有什么用呢?
		 * 如果数据或者List中的元素实现了该接口的话,我们就可以调用Collections.sort或者Arrays方法给他们排序.
		 * 如果自然顺序和equals不一致的话,如果出现在Sorted Map和Set里面,
		 * 就会出现预想不到的逻辑错误,可能你调用add的时候添加不了,而集合里面确没有这个元素.具体的讨论要接口哈希表的应用.
		 */
		while (true) 
		{
			//1.注意++i和--j,先+。2.两个比较的先后顺序要注意
			/*
			 * 算法的主体是找到两个分别大于和小于的位置,如果只有一个,另一个还是会找到,不过不在特定边
			 */
			while(less(a[++i], v))  if (i==hi) break;  //直到找到比v大的。
			while(less(v, a[--j]))  if (j==lo) break; //直到找到比v小的
			if (i>=j) break;//检查扫描是否完成
			exch(a, i, j);//将大于v的和小于v的交换。
		}
		exch(a, lo, j);//交换,将切分元素放到切分元素合适的位置
		return j;
		
	}

	private static boolean less(Comparable v, Comparable w) {
		// 比较用来排序的两个参数。根据第一个参数小于、等于或大于第二个参数分别返回负整数、零或正整数。
		// 如果v大于w,返回false,相等也是false
		return v.compareTo(w) < 0;

	}

	private static void exch(Comparable[] a, int i, int j) {
		// 交换位置
		Comparable t = a[i];
		a[i] = a[j];
		a[j] = t;
	}

	public static void main(String args[]) {
		// 1 2 4 6 7 7 8 23 55 56 90 
		Comparable[] a = {1,4,2,6,7,55,8,23,7,90,56};
		/*
		 * 这里不清楚随机算法为什么不能放在sort函数中,有一些小bug
		 * 还是传入的参数问题,穿进去的数组a不是主函数的数组a
		 */
		sort(a); 

	}

}

  很难受,现在才发现,自己写的代码自己看不懂了。快速排序的主体算法比较简单,没什么难懂的,就是将数组随机化的时候发现传参有一点问题,后来发现参数和原本的数组不是同一个数组,传参的改变不会影响原本数组的变化。所以这一点要注意。难点就是将切分元素放到合适的位置,这一段代码看了大概一个小时,现在也不懂我当时怎么想的了。主要有一点一直不明白,两个交换的意义和特别情况下的切分元素的移动问题。具体的解释都在代码中了,这里不再累述。下面讲讲自己的一些看法。

  快速排序还是有一些缺点的,之前说的不稳定。还有就是当数组中有大量相等元素时,这个快速排序也是比较乏力的,有一个三向切分的快速排序,不再将原数组分为两个数组,而是将原数组分为三个子数组,与切分元素相等的元素在第二个子数组。这种快速排序对于数组中存在大量重复的元素的排序要比上面的好的多。剩下就没有什么问题了,切分方法还是要多看看,弄清楚。

原文地址:https://www.cnblogs.com/yanyu01/p/8999942.html