最近看书,发现竟然连选择排序的原理都给忘了,囧囧。静下心来把数据结构的五个基础排序算法重新看了,发现以前虽然看过,但是从来没有实现和比较过。正好这个机会用Java 实现并将它们的性能进行比较。里面也有一些疑问:
既然使用Java ,将每个排序算法看成一个类(有点不妥感觉),不过这样看起来代码组织最直观一下。首先是一个抽象类:
package sort;
public abstract class AbstractSort {
protected static boolean less(Comparable v , Comparable w){
return v.compareTo(w) < 0 ;
}
protected static void exch(Comparable[] a , int i,int j){
Comparable temp = a[i];
a[i] = a[j];
a[j] = temp;
}
protected static void show(Comparable[] a){
for(int i = 0 ; i < a.length ; i++){
System.out.print(a[i] + " ");
}
System.out.println();
}
private static boolean isSorted(Comparable[] a){
for(int i = 1 ;i < a.length ; i++)
if(less(a[i],a[i-1])) return false;
return true;
}
}
然后开始
1 选择排序(囧)
选择排序其实思想很简单,就是每次从剩下的里面选择最大或者最小的然后和当前值进行交换。这样的话两重循环可以解决。复杂度是o(n2)
实现代码如下:
package sort;
public class SelectionSort extends AbstractSort {
public static void sort(Comparable[] a){
int N = a.length;
for(int i = 0 ;i < N ;i++){
int min = i;
for(int j = i+1 ; j < N ;j++)
if(less(a[j],a[min])) min = j;
exch(a,i,min);
}
}
}
2 插入排序
插入排序原理也是比较简单,保证前面一部分是有序的,然后从第一个无序的和前面的比较如果小于前面的数值就就行交换。理论上复杂度和插入排序一样,可是随即的数组排序比较得出他的速度应该是选择排序的1.2-1.7倍之间。有一个严重的缺点,如果最小的在最后面,那么移动的次数太多
代码如下:
package sort;
public class InsertionSort extends AbstractSort {
public static void sort(Comparable[] a){
int N = a.length;
for(int i= 1 ;i <N; i++){
for(int j = i; j > 0 && less(a[j],a[j-1]);j--)
exch(a,j,j-1);
}
}
}
3 希尔排序(名字很古怪)
希尔排序时针对插入排序的缺点改进的,其实它有三重循环,外面的循环式每次比较的步长,这样的话就可以消除每次只能移动一步的缺点。
package sort;
import java.util.Random;
public class ShellSort extends AbstractSort {
public static void sort(Comparable[] a){
int N = a.length;
int step = N/2 ;
while(step >= 1){
for(int i = step ; i < N ; i++)
for(int j=i;j>=step && less(a[j],a[j-step]);j-=step)
exch(a,j,j-step);
step /= 2;
}
}
public static void main(String[] args){
Integer[] a = new Integer[10];
Random random = new Random();
for(int i = 0; i < 10; i++)
a[i] = random.nextInt(100);
sort(a);
show(a);
}
}
4 快速排序思想很清晰,手写一个还是有些问题的
代码如下:
package sort;
import java.util.Random;
public class QuickSort extends AbstractSort {
public static void sort(Comparable[] a){
quick_sort(a,0,a.length-1);
}
private static void quick_sort(Comparable[] a, int left, int right) {
if(left >= right ) return ;
int N = right - left + 1 ;
Random random = new Random();
int p = random.nextInt(N) + left ;
Comparable temp = a[p] ;
exch(a,right,p);
int i = left ,
j = right ;
while(i < j ){
while(less(temp,a[j]) && i < j) j-- ;
if(i < j ) a[i++] = a[j];
while(less(a[i],temp) && i < j) i++;
if(i < j )a[j--] = a[i];
}
a[i] = temp ;
quick_sort(a,left,i-1);
quick_sort(a,i+1,right);
}
public static void main(String[] args){
int N = 10 ;
Integer[] a = new Integer[N];
Random random = new Random();
for(int i = 0; i < N; i++)
a[i] = random.nextInt(30);
sort(a);
show(a);
}
}
5 堆排序
我认为最大的问题是实现的时候处理边界的问题,包括了数组和树这种形态结构
代码如下:
package sort;
import java.util.Random;
public class HeapSort extends AbstractSort {
public static void sort(Comparable[] a){
buildHeap(a); // 建立堆
for(int i= a.length - 1 ; i >=1 ; i--){
exch(a,0,i);
adjust(a,0,i); //注意i 的值,开始的时候为length -1
}
}
/**
* 建立大顶堆
* @param a
*/
private static void buildHeap(Comparable[] a){
int length = a.length ;
for(int i = length/2 -1 ; i >= 0 ;i--)
adjust(a,i,length);
}
/*@name adjust
* @Function: 调整以节点 i 为顶点的堆
*/
private static void adjust(Comparable[] a ,int i,int length){
int left = 2*i + 1,
right= 2*i + 2 ;
if(left >= length && right >= length) return ;
int max = i;
if(left < length && less(a[max],a[left]))
max = left;
if(right <length && less(a[max],a[right]))
max = right ;
if(max!=i){
exch(a,i,max);
adjust(a,max,length);
}
}
public static void main(String[] args){
int N = 10 ;
Integer[] a = new Integer[N];
Random random = new Random();
for(int i = 0; i < N; i++)
a[i] = random.nextInt(30);
sort(a);
show(a);
}
}
6 归并排序
这里有四种归并排序方法,(自顶向下,自底向上)*(原地归并,飞原地归并)
package sort;
import java.util.Random;
public class MerageSort extends AbstractSort {
private static Comparable[] temp ;
public static void sort(Comparable[] a){
temp = new Comparable[a.length] ;
sort(a,0,a.length - 1);
}
/**
* 自底向上归并
* @param a
*/
public static void sortBU(Comparable[] a){
temp = new Comparable[a.length] ;
int N = a.length ;
for(int step = 1 ;step < N ;step+=step )
for(int left = 0 ;left < N -step ;left += step +step)
merage(a,left,left+step-1,Math.min(left+step+step-1, N-1));
}
/**
* 自顶向下归并排序
* @param a
* @param left
* @param right
*/
private static void sort(Comparable[] a ,int left ,int right){
if(left >= right) return ;
int mid = left + (right - left ) / 2;
sort(a,left,mid);
sort(a,mid+1,right) ;
// merage(a,left,mid,right);
merageWithoutTemp(a,left,mid,right);
}
/**
* 非原地归并排序,需要辅助数组
* @param a
* @param left
* @param mid
* @param right
*/
private static void merage(Comparable[] a ,int left,int mid,int right){
int i = left , j = mid + 1;
for(int k = left ; k <= right ; k++)
temp[k] = a[k] ;
for(int k = left ; k <= right ; k++){
if(i > mid) a[k] = temp[j++];
else if(j > right ) a[k] = temp[i++] ;
else if(less(temp[i],temp[j])) a[k] = temp[i++];
else a[k] = temp[j++] ;
}
}
/**
* 原地归并排序,不需要辅助数组,节省空间
* @param a
* @param left
* @param mid
* @param right
*/
private static void merageWithoutTemp(Comparable[] a ,int left ,int mid , int right ){
int i = left ,j = mid + 1,k =right ;
int step = 0 ;
while(i < j && j <= k){
while(i < j && less(a[i],a[j])) i++;
while(j <= k && less(a[j],a[i])){j++;step++;}
exchang(a,i,j,step);
}
}
private static void exchang(Comparable[] a, int i, int j, int step) {
reverse(a,j-step,j-1);
reverse(a,i,j-step-1);
reverse(a,i,j-1);
}
private static void reverse(Comparable[] a, int begin ,int end){
while(begin <end)
exch(a,begin++,end--);
}
public static void main(String[] args){
int N = 10 ;
Integer[] a = new Integer[N];
Random random = new Random();
for(int i = 0; i < N; i++)
a[i] = random.nextInt(30);
sortBU(a);
show(a);
}
}
最后是一个用来对各个排序算法进行性能比较的类:
package sort;
import java.util.Random;
import enuminfo.SortEunm;
public class SortCompare {
public static long time(SortEunm sort,Double[] a){
long startMill = System.currentTimeMillis();
if(sort == SortEunm.INSERTION) InsertionSort.sort(a);
else if(sort == SortEunm.SELECTION) SelectionSort.sort(a);
else if(sort == SortEunm.SHELL) ShellSort.sort(a);
else if(sort == SortEunm.QUICK) QuickSort.sort(a);
else if(sort == SortEunm.HEAP) HeapSort.sort(a);
long endMill = System.currentTimeMillis();
return endMill - startMill ;
}
public static long timeRandomInput(SortEunm sort,int N,int T){
long total = 0;
Double[] a = new Double[N];
for(int t = 0 ; t < T ; t++){
Random r = new Random();
for(int i = 0 ; i < N ;i++)
a[i] = r.nextDouble();
total += time(sort,a);
}
return total;
}
/**
* @param args
*/
public static void main(String[] args) {
int N = 1000;
int T = 1000;
long selectionTimes = timeRandomInput(SortEunm.SELECTION,N,T);
long insertionTimes = timeRandomInput(SortEunm.INSERTION,N,T);
long shellTimes = timeRandomInput(SortEunm.SHELL,N,T);
long quickTimes = timeRandomInput(SortEunm.QUICK,N,T);
long heapTimes = timeRandomInput(SortEunm.HEAP,N,T);
System.out.println("For "+ N +" random double and "+ T +" times :");
System.out.println("InsertionSort's time :" + insertionTimes);
System.out.println("SelectionSor's time :" + selectionTimes);
System.out.println("Shell's time : " + shellTimes);
System.out.println("quick's time :" + quickTimes);
System.out.println("heap's time :" + heapTimes);
System.out.println("insertionsTimes/SelectionsTimes = " + 1.0 * insertionTimes/selectionTimes);
System.out.println("shellTimes/insertionTimes = " + 1.0*shellTimes/insertionTimes);
}
}
以及一个枚举 :
package enuminfo;
public enum SortEunm {
SELECTION,
INSERTION,
SHELL,
QUICK,
HEAP;
}
希尔排序的速度大约是选择排序的 7 倍左右:
运行结果如下:
代码见: https://github.com/Turtledove/EnHanceExercises/tree/master/EnHanceExercises/src 待续