本文源自https://xuzhongcn.github.io/my/02/Sort.html
排序算法在商业算法、信息搜索、运筹学、事件驱动模拟、数值计算、组合搜索等等领域都有着广泛的应用,不仅如此,定序序列往往是其他算法的前提条件,例如二分查找等。
排序算法基本可以分为两大类:
此文以最基本冒泡排序、选择排序作为开始;之后讨论插入排序、希尔排序、归并排序;在之后我们讨论快速排序分析JDK1.7以后的Arrays.sort中对于快速排序的优化,三向切分的快速排序与双轴快速排序。最后我们来看基于优先队列数据结构的堆排序,以及非基于比较的排序:计数排序、桶排序、基数排序。
x //1、冒泡排序 public static void main(String[] args) { // TODO Auto-generated method stub int a[]= {5,6,8,7,2,9,1,3,4}; for (int i = 0; i < a.length; i++) { for (int j = 0; j < a.length-i-1; j++) { if(a[j]>a[j+1]) { int temp=a[j]; a[j]=a[j+1]; a[j+1]=temp; } } } for (int i = 0; i < a.length; i++) { System.out.print(a[i]+" "); } } //2、选择排序 public static void main(String[] args) { // TODO Auto-generated method stub int [] a= {5,6,8,7,2,9,1,3,4}; for (int i = 0; i < a.length; i++) { //记录最小的元素的脚标 //最开始,我们假定第一个元素最小 int min=i; for (int j = i+1; j < a.length; j++) { if(a[min]>a[j]) { //如果a[j]更小。 //我们把min更新为j。 //这样min就是这时之前所有比较过的元素的最小值的脚标。 min=j; } } //把这一次的最小值放到最前面。 int temp=a[min]; a[min]=a[i]; a[i]=temp; } for (int i = 0; i < a.length; i++) { System.out.print(a[i]+" "); } } x
//3、插入排序 public static void main(String[] args) { // TODO Auto-generated method stub int [] a= {5,6,8,7,2,9,1,3,4}; for (int i = 0; i < a.length; i++) { for(int j=i;j>0&&a[j-1]>a[j];j--) { int temp=a[j]; a[j]=a[j-1]; a[j-1]=temp; } } for (int i = 0; i < a.length; i++) { System.out.print(a[i]+" "); } } xxxxxxxxxx //4、希尔排序 public static void main(String[] args) { // TODO Auto-generated method stub int [] a= {5,6,8,7,2,9,1,3,4}; int N=a.length; int h=1; while(h<N/3) { //h的取值为:1,4,13,40,121,364,1093... h=3*h+1; } while(h>=1) { //将数组变为“h有序” for (int i = h; i <N; i++) { for (int j = i; j>=h&&a[j]<a[j-h]; j-=h) { int temp=a[j]; a[j]=a[j-h]; a[j-h]=temp; } } h=h/3; } for (int i = 0; i < a.length; i++) { System.out.print(a[i]+" "); } } xxxxxxxxxx //5、归并排序 public static void main(String[] args) { // TODO Auto-generated method stub int [] a= {5,6,8,7,2,9,1,3,4}; aux=new int [a.length]; sort(a,0,a.length-1); for (int i = 0; i < a.length; i++) { System.out.print(a[i]+" "); } } //临时数组,借用空间 private static int [] aux; //排序的算法 private static void sort(int [] a,int lo,int hi) { if(hi<=lo) { return ; } int mid=lo+(hi-lo)/2; //对左边排序 sort(a,lo,mid); //对右边排序 sort(a,mid+1,hi); //合并两个部分 merge(a,lo,mid,hi); } //把mid左边和右边归并成一个数组。 private static void merge(int[] a,int lo,int mid,int hi) { int i=lo; int j=mid+1; for (int k = lo; k <= hi; k++) { aux[k]=a[k]; } for (int k=lo;k<=hi;k++) { if(i>mid) { //如果左边没有元素了,剩下的就全是右边的。 a[k]=aux[j++]; }else if(j>hi) { //如果右边没有元素了,剩下的就全是左边的。 a[k]=aux[i++]; }else if(aux[j]<aux[i]) { //如果两边都有元素,且右边的小于左边的 //我们认为下一个元素应该是用右边的元素 a[k]=aux[j++]; }else { //如果两边都有元素,且右边的不小于左边的 //我们认为下一个元素应该是用左边的元素 a[k]=aux[i++]; } } }因为时间复杂度的问题,其实冒泡排序与选择排序在实际应用中并不多,因为它们是O(n^2)。快速排序时间复杂度要低,它是O(nlgn)。明显要快很多。
快速排序时间复杂度求解:


代码实现:
x
//6、快速排序 public static void main(String[] args) { // TODO Auto-generated method stub int [] a= {5,6,8,7,2,9,1,3,4}; sort(a,0,a.length-1); for (int i = 0; i < a.length; i++) { System.out.print(a[i]+" "); } } //基本快速排序 private static void sort(int [] a,int start,int end) { if(start>=end) { return ; } int n=start; int m=end; for (;start<end;) { for (;start<end; end--) { if(a[end]<a[start]) { int temp=a[end]; a[end]=a[start]; a[start]=temp; start++; break; } } for (; start<end; start++) { if(a[start]>a[end]) { int temp=a[start]; a[start]=a[end]; a[end]=temp; end--; break; } } } sort(a,n,start-1); sort(a,start+1,m); }与基本的快速排序不同,我们改进快速排序,为了使得当“数组中有相同元素”时,不至于浪费比较次数。于是,我们向把数组分成三个部分,以数组中一个元素flag为标准,数组被拆分成{<flag,=flag,>flag},也就是小于,等于,大于三个部分。
在JDK1.7类java.util.DualPivotQuicksort.java中:
我们可以看到,JDK中,以pivot为基本值,对数组进行比较,企图把数组拆分成三个部分,这里左边分界点为less,当k和great相遇之时,这一次快速排序拆分数组完成,也就是说less左边的都比pivot小,great右边的都比pivot大,于是下一次只需要:
那么基于这种思想,我们可以写出三向切分的快速排序:
xxxxxxxxxx //三向切分的快速排序 public static void main(String[] args) { // TODO Auto-generated method stub int [] a= {5,6,8,7,2,9,1,3,4}; sort(a,0,a.length-1); for (int i = 0; i < a.length; i++) { System.out.print(a[i]+" "); } } private static void sort(int [] a,int start,int end) { if(end<=start) { return ; } int left=start; int right=end; int center=start+1; while(center<=right) { if(a[center]<a[left]) { int temp=a[center]; a[center]=a[left]; a[left]=temp; left++; center++; }else if(a[center]>a[left]) { int temp=a[center]; a[center]=a[right]; a[right]=temp; right--; }else { center++; } } sort(a,start,left-1); sort(a,right+1,end); }上面我们看到,快速排序一直都是以一个pivot值,来分裂数组的,双轴快速排序采用两个值,pivot1和pivot2,把数组拆分成三个部分{<pivot1,>=pivot1&&<=pivot2,>pivot2}。
在JDK1.7类java.util.DualPivotQuicksort.java中:

我们看到果真这般如此。less左边的都比pivot1小,great右边的都比pivot2大,当k与great相遇时,less与great之间的值就是介于[pivot1,pivot2]区间内部的值。
双轴快速排序代码实现:
xxxxxxxxxx //双轴快速排序 public static void main(String[] args) { // TODO Auto-generated method stub int [] a= {5,6,8,7,2,9,1,3,4}; sort(a,0,a.length-1); for (int i = 0; i < a.length; i++) { System.out.print(a[i]+" "); } } private static void sort(int [] a,int start,int end) { if(start >= end){ return; } if(a[start] > a[end]){ //保证pivot1 小于等于pivot2 int temp=a[start]; a[start]=a[end]; a[end]=temp; } int right=0;//统计右侧的个数 int left=0;//统计左侧的个数 int center=0;//统计中间的个数 int pivot1=start; int pivot2=end; for(int i=start;i<end;i++) { if(a[i]<a[pivot1]) { int temp=a[i]; a[i]=a[pivot1]; a[pivot1]=temp; pivot1=i;//把第一个轴对正 left++;//左侧数据个数++ }else if(a[i]>=a[pivot1]&&a[i]<=a[pivot2]) { center++;//中间数据个数++ }else if(a[i]>a[pivot2]) { //换到第二个轴的前一个元素。 int temp=a[i]; a[i]=a[pivot2-1]; a[pivot2-1]=temp; //和第二个轴换。 temp=a[pivot2-1]; a[pivot2-1]=a[pivot2]; a[pivot2]=temp; pivot2--;//第二个轴对正。 right++;//右侧数据个数++。 i--;//因为我们向前换过去了一个未知元素,这个未知元素需要我们继续判断的! } //如果所有元素都找到自己的区间了,就结束这次排序 if(left+center+right==end-start) { break; } } //三个区间各自快速排序 sort(a, start, pivot1-1); sort(a, pivot1+1, pivot2-1); sort(a, pivot2+1, end); }说到堆排序,它是基于优先队列的,优先队列概念大家自行补充,这里不再说明了,但是给出优先队列的代码实现:
xxxxxxxxxxclass MaxPQ<Key extends Comparable<Key>>{ private Key [] pq; private int N=0; ("unchecked") public MaxPQ(int maxN) { pq=(Key[])new Comparable[maxN+1]; } public boolean isEmpty() { return N==0; } public int size() { return N; } public void insert(Key v) { pq[++N]=v; //下沉 swim(N); } public Key delMax() { Key max=pq[1]; //和最后一个元素交换位置 exch(1,N--); //删掉这个元素 pq[N+1]=null; //上浮,直到叶子节点 sink(1); //返回删掉的那个最大值 return max; } //打印 public void show() { for (int i = 1; i < pq.length; i++) { System.out.print(pq[i]); } } //上浮 private void sink (int k) { while(2*k<=N) { int j=2*k; if(j<N&&less(j,j+1)) j++; if(!less(k,j))break; exch(k,j); k=j; } } //下沉 private void swim(int k) { while(k>1&&less(k/2,k)) { exch(k/2,k); k=k/2; } } //比较 private boolean less(int i,int j) { return pq[i].compareTo(pq[j])<0; } //交换 private void exch(int i,int j) { Key temp=pq[i]; pq[i]=pq[j]; pq[j]=temp; }}有了优先队列,我们把元素放入优先队列里,然后一个一个取出来,就可以了。代码如下:
xxxxxxxxxx //9、堆排序 public static void main(String[] args) { // TODO Auto-generated method stub int [] a= {5,6,8,7,2,9,1,3,4}; MaxPQ<Integer> maxPQ=new MaxPQ<Integer>(a.length); for (int i = 0; i < a.length; i++) { maxPQ.insert(a[i]); } for (int i = 0; i < a.length; i++) { System.out.print(maxPQ.delMax()); } } xxxxxxxxxx //10、计数排序 public static void main(String[] args) { // TODO Auto-generated method stub int []a= {5,6,8,7,2,9,1,3,4}; sort(a); for (int i = 0; i < a.length; i++) { System.out.print(a[i]+" "); } } private static void sort(int [] a) { int [] temp=new int [10]; for (int i = 0; i < a.length; i++) { temp[a[i]]++; } int k=0; for (int i = 1; i < temp.length; i++) { for (int j = 0; j < temp[i]; j++) { a[k++]=i; } } } xxxxxxxxxx //11、桶排序 public static void main(String[] args) { // TODO Auto-generated method stub int []a= {5,6,8,7,2,9,1,3,4}; sort(a); for (int i = 0; i < a.length; i++) { System.out.print(a[i]+" "); } } public static void sort(int a[]) { int n = a.length; int bask[][] = new int[10][n]; int index[] = new int[10]; int max = Integer.MIN_VALUE; for (int i = 0; i < n; i++) { max = max > (Integer.toString(a[i]).length()) ? max : (Integer.toString(a[i]).length()); } String str; for (int i = max - 1; i >= 0; i--) { for (int j = 0; j < n; j++) { str = ""; if (Integer.toString(a[j]).length() < max) { for (int k = 0; k < max - Integer.toString(a[j]).length(); k++) str += "0"; } str += Integer.toString(a[j]); bask[str.charAt(i) - '0'][index[str.charAt(i) - '0']++] = a[j]; } int pos = 0; for (int j = 0; j < 10; j++) { for (int k = 0; k < index[j]; k++) { a[pos++] = bask[j][k]; } } for (int x = 0; x < 10; x++) index[x] = 0; } } xxxxxxxxxx // 12、基数排序 public static void main(String[] args) { // TODO Auto-generated method stub int []a= {5,6,8,7,2,9,1,3,4}; sort(a,1); for (int i = 0; i < a.length; i++) { System.out.print(a[i]+" "); } } public static void sort(int[] number, int d) // d表示最大的数有多少位 { int k = 0; int n = 1; int m = 1; // 控制键值排序依据在哪一位 int[][] temp = new int[10][number.length]; // 数组的第一维表示可能的余数0-9 int[] order = new int[10]; // 数组orderp[i]用来表示该位是i的数的个数 while (m <= d) { for (int i = 0; i < number.length; i++) { int lsd = ((number[i] / n) % 10); temp[lsd][order[lsd]] = number[i]; order[lsd]++; } for (int i = 0; i < 10; i++) { if (order[i] != 0) for (int j = 0; j < order[i]; j++) { number[k] = temp[i][j]; k++; } order[i] = 0; } n *= 10; k = 0; m++; } }以上12种排序方式,基本就是目前流行的所有排序了,笔者在这里对于“自认为”比较难理解的“快速排序”、“三向切分的快速排序”、“双轴快速排序”,以及JDK1.7中的Arrays.sort进行了一些说明,谬误之处还望大家批评指正,不胜感激。