搞不懂的算法-排序篇

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了搞不懂的算法-排序篇相关的知识,希望对你有一定的参考价值。

  最近在学习算法,跟着<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

 

以上是关于搞不懂的算法-排序篇的主要内容,如果未能解决你的问题,请参考以下文章

TF-搞不懂的TF矩阵加法

那些年搞不懂的多线程同步异步及阻塞和非阻塞---多线程简介

搞不懂的github

算法-排序篇选择排序

算法-排序篇选择排序

算法-排序篇选择排序