常见排序算法总结(java实现)
所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。常见的排序算法有选择排序,插入排序,希尔排序,归并排序和快速排序
由于在排序的过程中不可避免的要涉及到比较和交换,所以将他们抽取为两个单独的函数,如下所示
//为了排序代码的通用性,这里假定待排序的元素实现了Comparable接口 private static boolean less(Comparable v ,Comparable w){ 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; }
选择排序
算法思想:首先,找到数组中最小的那个元素,然后将其和数组中第一个元素交换位置。再从剩下的元素中找到最小的元素,将其和数组中的第二个元素交换位置,如此这般,直到整个数组全部有序。
算法特点:1.运行时间和输入无关。不管输入的数据是一个已经有序的数组,或者是一个随机的数组,其所需要的比较次数是一样的。
2.数组的移动是最少的。由于每一次都是选择一个最小的元素然后交换其位置,所以每次交换都能排定一个元素,因此总的交换次数是N。
public static void selectionSort(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); //交换位置 } }
插入排序
算法思想:首先,假定数组中第一个元素是有序的,然后将第二个元素插入到前面有序数组的合适位置,组成一个新的有序数组,再将第三个元素插入到前面的有序数组中,如此这般,知道数组整体有序。
算法特点:运行时间和初始输入有关。如果初始输入的数据已经有序或者部分有序的时候,插入排序的性能会比较好。
public static void insertionSort(Comparable[] a){ int N = a.length; for(int i=1;i<N;i++){ //待插入的元素下标 for(int j=i;j>0;j--) //在前面有序的数组中寻找合适的插入位置 if(less(a[j],a[j-1])) exch(a, j, j-1); } }
希尔排序
算法思想:希尔排序的思想是使数组中任意间隔为h的元素都是有序的,这样的数组被称之为h-有序数组。我们不断减少h的值,使其最终变为一个1-有序数组。1-有序数组,也就是数组整体有序,这样排序就算完成了。
希尔排序最难的地方在于h序列的选择。目前还没有方法证明某一种序列是最好的。
public static void sort(Comparable[] a){ int N = a.length; int h = 1; //用来记录步长 while(h<N/3) h=3*h+1; //步长为1,4,13,40…… while(h>=1){ for(int i=h;i<N;i++) for(int j=i;j>=h;j-=h) //将a[i]插入到a[i-h],a[i-2*h],a[i-3*h]……之中 if(less(a[j],a[j-h])) exch(a,j,j-h); h=h/3; //将前面记录的步长反过来,最终使其为1-有序数组 } }
归并排序
算法思想:要将一个数组进行排序,可以递归的将其分为两半分别进行排序,最后将结果归并起来,使其整体有序。归并排序基于算法设计中的分治思想。我们将一个大问题分解成小问题分别解决,然后用所有小问题的答案来解决整个大问题。
算法特点:1.任意长度为N的数组排序所需的时间和NlogN成正比。这个可以通过归并排序的排序树得到
2.它所需要的额外空间和N成正比。因为归并过程中需要一个长度为N的辅助数组。
private static Comparable[] temp; //辅助数组,定义为类的成员变量 public static void mergeSort(Comparable[] a){ temp = new Comparable[a.length]; sort(a,0,a.length-1); } //将数组a[lo..hi]进行排序 private static void sort(Comparable[] a,int lo ,int hi){ if(lo>=hi) return; int mid = lo+(hi-lo)/2; sort(a,lo,mid);//递归的对左边进行排序 sort(a,mid+1,hi); //递归的对右边进行排序 merge(a,lo,mid,hi); //合并左右两边 } //将两个有序的数组a[lo..mid]和a[mid+1..hi]合并为一个有序的数组,用到一个辅助数组temp public static void merge(Comparable[] a,int lo,int mid,int hi){ int i=lo,j=mid+1; for(int k=lo;k<=hi;k++) //将数组a复制到辅助数组temp中 temp[k] = a[k]; for(int k=lo;k<=hi;k++) if(i>mid) a[k]=temp[j++]; //左半边取尽,直接复制右半边剩余的元素到数组中 else if(j>hi) a[k] = temp[i++]; //右半边取尽,直接复制左半边剩余的元素到数组中 else if(less(temp[j],temp[i])) a[k]=temp[j++]; //右半边的当前元素比左半边的当前元素小,取右半边 else a[k]=temp[i++]; //左半边的当前元素比右半边的当前元素小,取左半边 }
快速排序
算法思想:快速排序是一种基于分治的排序算法。它通过一种划分,将一个数组划分为两个字数组,并将两个字数组独立的进行排序。
public static void sort(Comparable[] a){ sort(a,0,a.length-1); } private 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); //右半部分排序 } private static int partition(Comparable[] a,int lo,int hi){ int i=lo,j=hi+1; //左右扫描指针 Comparable v = a[lo]; //切分元素,该元素完成切分后位置将固定并返回 while(true){ //循环扫描左右指针 while(less(a[++i],v)) if(i==hi) break; while(less(v,a[--j])) if(j==lo) break; if(i>=j) break; exch(a,i,j); } exch(a,lo,j); //将v=a[j]放入正确位置 return j; //返回切分元素所在的位置 }
在待排序的文件中,若存在多个关键字相同的记录,经过排序后这些具有相同关键字的记录之间的相对次序保持不变,该排序方法是稳定的;若具有相同关键字的记录之间的相对次序发生改变,则称这种排序方法是不稳定的。
排序法 |
平均时间 |
最差情形 |
稳定度 |
额外空间 |
备注 |
选择 |
O(n2) |
O(n2) |
不稳定 |
O(1) |
n小时较好 |
插入 |
O(n2) |
O(n2) |
稳定 |
O(1) |
大部分已排序时较好 |
Shell |
O(nlogn) |
O(ns) 1<s<2 |
不稳定 |
O(1) |
s是所选分组 |
快速 |
O(nlogn) |
O(n2) |
不稳定 |
O(nlogn) |
n大时较好 |
归并 |
O(nlogn) |
O(nlogn) |
稳定 |
O(1) |
n大时较好 |
本算法过程只注重实现,并没有用栈等机制消除递归,