《算法4》排序算法总结

来源:互联网 发布:禅道 linux 启动 编辑:程序博客网 时间:2024/05/23 11:17

最近在学算法,就以排序算法的总结作为第一篇博客

  • 选择排序
  • 插入排序
  • 希尔排序
  • 归并排序
  • 快速排序
  • 三向切分快速排序
  • 总结

选择排序

选择排序的思路很简单 ,不断遍历整个数组,将未排序数组中最小的数放到左边,直到整个数组排序完成。选择排序速度较慢,每次都必须遍历整个数组未排序的部分,所以总的比较次数为

C(N)=N+(N1)++1N22

交换的次数为N
这对于较大的数据集来说基本是不能忍的。

插入排序

插入排序可以用数学归纳法的方式来描述,首先N=1 的情况是平凡的,即一定是有序的。然后假设数组的前k个元素已经是有序的,那么对于第k+1个元素,可以通过与前面的元素逐个交换,直到碰到一个比它小的元素。那么现在在这个有k+1个元素的数组中,新插入之前的元素都是比这个新元素小的,他后面的都是比它大的,显然,这个k+1元素的数组也是有序的。因此插入排序是正确的,最后能够得到一个有序的数组。
插入排序的性能:最坏的情况下,是排序一个反序(降序)的数组,那么每次都要将元素移到数组最左边,总的比较次数和交换次数都是N22,最理想的情况是数组已经有序了,那么每次插入只需要比较一次,不需要移动数据,因此总的比较和交换次数分别为N1和0,而平均的比较和交换次数都是N24
但是插入排序在小数组的情况下是很适合的,尤其是数组已经部分有序的情况下,它可以很快,并且可以作为高级排序算法的中间过程。

希尔排序

插入排序在数组较大的情况下很慢,因为它只能一步步地移动到正确的位置,同时上面也提到了,对于部分有序数组,插入排序是比较快的。所以希尔排序先对数组进行排序使之成为“h-有序”的,所谓“h-有序”就是数组中任意相距h的元素都是有序的。之后减小h的值再次排序,直到h减小到1,就退化成插入排序,但是此时整个数组已经是部分有序的,所以速度会提高很多。
对于h的取值可以采用Knuth提出的序列1,4,13,40,,(3k+1),计算时根据数组长度先计算出最大的h,然后逐渐递减至1。
排序的代码如下所示。
希尔排序的准确性能是不知道的,根据Sedgewick的《算法4》所说,下面的Shell Sort 算法的性能在最坏条件下是Θ(N3/2)的,但是可以知道的是它的运行时间达不到平方级别。

//Shell Sortpackage first;import edu.princeton.cs.algs4.StdOut;public class Shell {    public static void sort(Comparable[] a){        int N=a.length;        int h=1;        while(h<N/3) h=3*h+1;//找出最大的h        while(h>=1){//相当于是对间隔为h的子数组进行插入排序,直到h=1            for(int i=h;i<a.length;i++){                for(int j=i;j>=h&&less(a[j],a[j-h]);j-=h)                    exch(a,j,j-h);            }            h=h/3;        }    }    public static boolean less(Comparable v,Comparable w){//比较两个元素        return v.compareTo(w)<0;    }    public static void exch(Comparable[] a,int i,int j){//交换两个元素        Comparable t=a[i];        a[i]=a[j];        a[j]=t;    }    public static void show(Comparable[] a){//打印数组        for(int i=0;i<a.length;i++)            StdOut.print(a[i]+" ");        StdOut.println();    }    public static boolean isSorted(Comparable[] a){//判断数组是否有序        for(int i=1;i<a.length;i++)            if(less(a[i],a[i-1])) return false;        return true;    }    public static void main(String[] args) {        // TODO 自动生成的方法存根        Character[] s={'S','H','E','L','L','S','O','R','T','E','X','A','M','P','L','E'};        for(int i=0;i<s.length;i++)            System.out.print(s[i]+" ");        sort(s);        StdOut.println();        for(int i=0;i<s.length;i++)            System.out.print(s[i]+" ");    }}

归并排序

归并排序是一种应用较为广泛的排序方法。起源于归并这个操作,即如果我们有两个有序数组如何将其合在一起变成一个更大的有序数组。
归并的思想很简单,首先得有一个辅助数组。比较两个有序数组的最小值,其中更小的那一个一定是将来更大的那个数组中最小的那个,将这个值复制到辅助数组中去。然后排除这个最小值再进行这种比较,直到两个数组都空了,最后得到的辅助数组就是一个有序的大数组。
归并排序就是从上到下,先分割再对子数组进行归并排序
归并排序的优点是不论最好还是最坏一定能够保证NlgN 的复杂度,并且它还是一种稳定的排序算法。不足就是需要一个辅助数组,并不是原址排序。
下面是归并的代码片段;

private static void merge(Comparable[] a, int lo, int mid,int hi){        int i=lo,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 (less(aux[j], aux[i])) a[k] = aux[j++];            else                           a[k] = aux[i++];        }    }

下面是排序的代码

private static void sort(Comparable[] 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);    }

可以对排序予以优化,就是在,不断递归到子数组足够小的时候改成插入排序,比如在长度小于15的时候,这样做能将时间减少10%~15%(来自《算法4》)

快速排序

快速排序是应用最广泛的排序算法了,它所需的平均时间为NlgN 级别的,并且是原址排序,主要的缺点就是比较脆弱,排序时间容易变成最差的平方时间,但是通过在排序之前将数组随机打乱可以很好地解决这个问题。
快速排序其实也很好理解,我们先选择一个元素v(通常是数组边界上的),然后以v为界对整个数组进行“切分”,“切分”就是将小于v的元素放在数组一边,大于它的放在另一边,然后再把v放到两种元素之间。其实此时元素v的位置就是它最终的位置,所以“切分”一方面得到了两个子数组,一方面确定了这个用于切分的元素的最终位置。将切分递归地对子数组进行下去,一定可以将整个数组排序。

public class QuickSort {    private static void sort(Comparable[] a, int lo, int hi){        if (hi<=lo) return ;// 这句话忘记了的话就会栈溢出,主要是单个元素的情况下,会造成hi 比lo 小,如果不直接返回的话,就完了.        int j = partition(a, lo, hi);        sort(a, lo, j-1);        sort(a, j+1, hi);    }    public static void sort (Comparable[] a){        StdRandom.shuffle(a);        sort(a, 0, a.length-1);    }    private static int  partition(Comparable[] a, int lo, int hi){        if (lo==hi ) return lo;        Comparable v = a[lo];        int i=lo+1, j=hi;        while(i<j){            while(a[i].compareTo(v)<0){//找一个比v大的元素                i++;                if (i==hi) break;            }            while(a[j].compareTo(v)>0){//找一个比v小的元素                j--;                if (j==lo) break;            }            if(i>=j ){break;}//最后停下的条件,j一定挨着i并且j<i            exch(a, i, j);        }        exch(a, j, lo);        return j;    }    private static boolean less(){}//方法同前面Shell Sort    private static void exch(){}//方法同前面Shell Sort}

三向切分快速排序

对快速排序的一种改进就是三向切分快速排序,这种排序方式在重复元素较多的情况下甚至有可能达到线性级别。
算法实现过程中,先确定一个元素v,再维护两个指针,lt和gt。在lt左边的都是小于v的,在gt右边的都是大于v的,在数组排序完之后,在lt和gt之间的都应该是等于v的。
之所以能够提高速度,是因为在重复元素较多的时候,能够减少切分的次数。
下面是三向切分排序的排序函数。

private static void sort(Comparable[] a, int lo, int hi){        if (hi<=lo) return ;//单个元素直接返回        int lt=lo, gt = hi, i =lo+1;        Comparable v= a[lo];        while(i<=gt){//一直比较到i大于gt            int cmp = a[i].compareTo(v);            if (cmp<0) exch(a,i++,lt++);//小于v的交换到lt的左边            else if (cmp>0) exch(a, i, gt--);//大于v的交换到gt的右边            else i++;//等于v的扩充lt到i之间的区域        }        sort(a, lo, lt-1);        sort(a, gt+1, hi);

总结

各种算法总结(来自《算法》)

上图来自于《算法4》网站 ,Java中使用的系统排序函数是三向快速切分排序和归并排序。

原创粉丝点击