首页 > 代码库 > 搞不懂的算法-排序篇
搞不懂的算法-排序篇
最近在学习算法,跟着<Algorithms>这本书,可能是自己水平不够吧,看完排序算法后各种,希尔,归并,快排,堆的实现在脑子里乱成一锅粥,所以就打算大概总结一下,不求精确,全面,只想用平白的语言来理一理,如有错误之处,请直言。
为什么所有的算法书籍都重墨介绍排序,一、对一组数据进行排序在生活中是如此的常见,我们常常需要使用它;二、排序是实现很多一些高级算法的基础,一些复杂问题,如果处理的是已经拍过序的数据,那么就容易处理很多;三、排序算法中包含了很多重要的思想和方法,对于其他算法的研究也具有借鉴意义。研究排序算法之前,有一些关于算法的基础是需要掌握的。什么是好的算法?当然是算的快的算法,就是所谓的时间复杂性分析。计算机的内存是有限的,如果算法使用的空间也比较小,那就更好了,所谓的空间复杂度分析。如果代码写起来简单又好理解,这简直就是完美。此外针对不同的输入,同一个算法的性能也有很大的区别,所以也可以分别讨论。一些搞算法的人将这些情况总结起来,形成了科学的方法,甚至抽象成了思想。包括:算法的时间复杂度,该算法的运行时间?这个问题,我们能达到的最快运行时间;空间复杂度,算法解决问题使用多少空间。输入模型,针对不容的输入情况,比如大致排好序的,重复元素多的等等。算的又快,空间利用少,针对不同输入保持稳定高效,这样的算法是完美的算法。
排序-比如,给你一个包含N个随机double元素的数组,想要得到一个按大小排好序的数组。
1,选择排序/selection sort
选择排序大概是最简单易于理解的排序方法了,想象有一群高矮不同的人群,现在要将他们按高矮排一列。你可以从这群人中找出最矮的那个,放到最前面,然后找出第二矮的,依次下去直到排完。
1 public class Selection{ 2 private static boolean less(Comparable[] a,int i,int j){ 3 return a[i].compareTo(a[j])<0; 4 } 5 private static void exch(Comparable[] a,int i,int j){ 6 Comparable temp=a[i]; 7 a[i]=a[j]; 8 a[j]=temp; 9 }10 public static void sort(Comparable[] a){11 int N=a.length; 12 for(int i=0;i<N-1;i++){13 int temp=i;14 for(int j=i+1;j<N;j++)15 if(less(a,j,temp)) temp=j; 16 exch(a,i,temp);17 }18 }19 }
这里面有两个函数:less(),exch(),前者以索引比较数组中的两个元素的大小,后者以索引交换两个元素的位置。之所以将两个操作封装成两个函数,是为了算法分析的方便。以后所有的算法都只使用着两个操作,分析也主要集中于不同算法执行的着两个操作的次数,因为其他的操作都是常数次的,只有这两个操作是与N有关的。
分析:对于选择排序,我们可以看到,比较的次数为(N-1)+(N-2)+...1=N2/2,平方级别;交换的次数为N-1,线性级别。非常稳定,无论输入如何,效率不变。
2.插入排序/Insertion sort
从数组左边到右边遍历元素,如果元素i小于前一个元素,将i与i-1交换,重复这个操作直到i落到合适的位置。
1 public class Insertion{ 2 private static boolean less(Comparable[] a,int i,int j){ 3 return a[i].compareTo(a[j])<0; 4 } 5 private static void exch(Comparable[] a,int i,int j){ 6 Comparable temp=a[i]; 7 a[i]=a[j]; 8 a[j]=temp; 9 }10 public static void sort(Comparable[] a){11 int N=a.length;12 for(int i=1;i<N;i++)13 while(i>0 && less(a,i,i-1)){14 exch(a,i,i-1);15 i--;16 } 17 }18 }
分析:这个算法是依赖于输入的,最好的情况是数组已经排好序,那么只需要经过N-1次比较,0次交换,就能完成。最坏的情况,数组是逆序的,那么需要1+2+...(N-1)=N2/2次比较,同样多次的交换达成目标。平均情况下同样分析可以知道,需要N2/4次比较和同样多次交换达成目标。值得注意的一点是,插入排序对于基本有序的数组排序很有效果,因为只需要线性次数的比较和较少次操作,就能达成目标。
3.冒泡排序/bubble sort
对于很多没学过算法的人,听到这个名字感觉很高大上,但其实这是个很简单的算法,效率也较低,基本上不怎么用。方法就是进行一个N次的循环,每次循环遍历数组元素,将相邻两个元素中较大的放后面,因为元素像泡泡一样浮出得名。有两个优化策略,1、每次重复,遍历数组长度-1,2、当一次遍历没有进行交换,表明已经排好序,跳出循环而不用执行固定的循环次数。
1 public class Bubble{ 2 private static boolean less(Comparable[] a,int i,int j){...代码上同} 3 private static void exch(Comparable[] a,int i,int j){...代码上同} 4 public static void sort(Comparable[] a){ 5 int N=a.length; 6 int temp=1; 7 for(int i=0;i<N-1 && temp==1;i++){ 8 temp=0; 9 for(int j=0;j<N-i-1;j++){10 if(less(a,j+1,j)){11 exch(a,j,j+1);12 temp=1;13 } 14 }15 }16 }17 }
分析:冒泡排序感觉跟插入排序有很多相同,也是依赖于输入的,最好的情况:顺序排列的数组,需要N-1次比较和0次交换,达成目标。最差的情况:逆序排列:需要1+2+...(N-1)=N2/2次比较,同样多次的交换达成目标。
以上是三种最基本的排序方式,可以看到三者一般情况下都是平方级别的,所以相互之间的速度差别为常数级别的,试验发现,插入排序相当是较快的一种,但快的有限,大概比选择排序快0.7倍。
实际上通过分析可以发现,三种排序方法效率都有某种程度的浪费,所以可以对其进行优化,比如一种对插入排序的优化,希尔排序。
4.希尔排序/shell short
搞不懂的算法-排序篇