七大排序算法

  排序算法种类繁多根据处理的数据规模与存储特色,可分为内部排序和外部排序:前者处理的数据规模不大,内存足以容纳;后者处理的数据规模较大,必须将数据存放于外部存储器中,每次排序的时候须要访问外存。根据输入的不一样形式,分为脱机算法和在线算法:前者待排序的数据是以批处理的形式给出的;而在云计算之类的环境中,待排序的数据是实时生成的,在排序算法开始运行时,数据并未彻底就绪,而是随着排序算法自己的进行而逐步给出的。另外,针对不一样的体系结构,又分为串行和并行两大类排序算法。根据算法是否采用随机策略,还有肯定式和随机式之分。linux

冒泡排序(O(n^2))

冒泡排序是比较相邻两数的大小来完成排序的。这里定义比较边界,也就是进行大小比较的边界。对于长度为n的数组,第一趟的比较边界为[0,n-1],也就是说从a[0]开始,相邻元素两两比较大小,若是知足条件就进行交换,不然继续比较,一直到最后一个比较的元素为a[n-1]为止,此时第一趟排序完成。以升序排序为例,每趟排序完成以后,比较边界中的最大值就沉入底部,比较边界就向前移动一个位置。因此,第二趟排序开始时,比较边界是[0,n-2]。对于长度为n的序列,最多须要n趟完成排序,因此冒泡排序就由两层循环构成,最外层循环用于控制排序的趟数,最内层循环用于比较相邻数字的大小并在本趟排序完成时更新比较边界。web

具体代码以下:算法

 1  //冒泡排序
 2     public static void bubbleSort(int[] arr,int len){
 3         int temp=0;
 4         int compareRange=len-1;//冒泡排序中,参与比较的数字的边界。
 5         //冒泡排序主要是比较相邻两个数字的大小,以升序排列为例,若是前侧数字大于后侧数字,就进行交换,一直到比较边界。
 6         for (int i = 0; i <len ; i++) {//n个数使用冒泡排序,最多须要n趟完成排序。最外层循环用于控制排序趟数
 7             for (int j = 1; j <=compareRange ; j++) {
 8                 if(arr[j-1]>arr[j]){
 9                     temp=arr[j-1];
10                     arr[j-1]=arr[j];
11                     arr[j]=temp;
12                 }
13             }
14             compareRange--;//每进行一趟排序,序列中最大数字就沉到底部,比较边界就向前移动一个位置。
15         }
16         System.out.println("排序后数组"+Arrays.toString(arr));
17     }
View Code

  在排序后期可能数组已经有序了而算法却还在一趟趟的比较数组元素大小,能够引入一个标记,若是在一趟排序中,数组元素没有发生过交换说明数组已经有序,跳出循环便可。优化后的代码以下:shell

 1 public static void bubbleSort2(int[] arr,int len){
 2         int temp=0;
 3         int compareRange=len-1;//冒泡排序中,参与比较的数字的边界。
 4         boolean flag=true;//标记排序时候已经提早完成
 5         int compareCounter=0;
 6         //冒泡排序主要是比较相邻两个数字的大小,以升序排列为例,若是前侧数字大于后侧数字,就进行交换,一直到比较边界。
 7        while(flag) {
 8            flag=false;
 9             for (int j = 1; j <=compareRange ; j++) {
10                 if(arr[j-1]>arr[j]){
11                     temp=arr[j-1];
12                     arr[j-1]=arr[j];
13                     arr[j]=temp;
14                     flag=true;
15                 }
16             }
17            compareCounter++;
18            compareRange--;//每进行一趟排序,序列中最大数字就沉到底部,比较边界就向前移动一个位置。
19         }
20         System.out.println("优化后排序次数:"+(compareCounter-1));
21         System.out.println("排序后数组"+Arrays.toString(arr));
22     }
View Code

  还能够利用这种标记的方法还能够检测数组是否有序,遍历一个数组比较其大小,对于知足要求的元素进行交换,若是不会发生交换则数组就是有序的,不然是无序的。数组

  两种方法的排序结果以下所示:数据结构

插入排序(O(n^2))

将待排序的数组划分为局部有序子数组subSorted和无序子数组subUnSorted,每次排序时从subUnSorted中挑出第一个元素,从后向前将其与subSorted各元素比较大小,按照大小插入合适的位置,插入完成后将此元素从subUnSorted中移除,重复这个过程直至subUnSorted中没有元素,总之就时从后向前,一边比较一边移动。app

对应代码以下: 数据结构和算法

 1  //直接插入排序
 2     public static void straightInsertSort(int[] arr,int len){
 3         int temp=0;
 4         int j=0;
 5         for (int i = 1; i <len ; i++) {//待插入的数字
 6             for (j =i-1; j>=0; j--) {//有序区间
 7                 if(arr[i]>arr[j]){
 8                     break;
 9                 }
10             }
11                 if(j!=i-1){
12                     temp=arr[i];
13                     for (int k =i-1; k >j ; k--) {
14                         arr[k+1]=arr[k];//从后向前移动数组
15                     }
16                     arr[j+1]=temp;
17                 }
18            // System.out.println("直接插入排序后数组" + Arrays.toString(arr));
19 
20         }
21         System.out.println("直接插入排序后数组" + Arrays.toString(arr));
22 
23     }
24     //直接插入排序简洁版
25     public static void straightInsertSort2(int[] arr,int len){
26         int temp=0;
27         int j=0;
28         for (int i = 1; i <len ; i++) {
29             if(arr[i]<arr[i-1]){
30                 temp=arr[i];
31                 for (j = i-1; j>=0&&temp<arr[j] ; j--) {
32                     arr[j+1]=arr[j];//从后向前移动数组
33                 }
34                 arr[j+1]=temp;
35             }
36         }
37         System.out.println("直接插入排序后数组" + Arrays.toString(arr));
38     }
View Code

添加一个易于理解的版本:ide

 1  //插入排序易理解版
 2     public static void straightInsertSort3(int[] arr,int len){
 3         int high=0;//有序区间的上界(包括)
 4         int insertValue=0,i=0,j=0;
 5         while (high<len-1){
 6             i=high;
 7             insertValue=arr[i+1];
 8             while (i>=0&&insertValue<arr[i]){
 9                 --i;
10             }
11             for (j =high; j>=i+1 ; j--) {
12                 arr[j+1]=arr[j];
13             }
14             arr[i+1]=insertValue;
15             ++high;
16         }
17         System.out.println("直接插入排序后数组" + Arrays.toString(arr));
18     }
View Code

新版本: 优化

 1  public static void insertSort(int arr[],int len){
 2         int tmp=-1;
 3         int soretdIndex=0;
 4         for (int i = 1; i <len ; i++) {
 5             tmp=arr[i];
 6             soretdIndex=i-1;
 7             while (soretdIndex>=0&&arr[soretdIndex]>tmp){
 8                 arr[soretdIndex+1]=arr[soretdIndex];
 9                 soretdIndex--;
10             }
11             arr[soretdIndex+1]=tmp;
12         }
13     }
View Code

 哨兵版本:

 1 public static void insertSort2(int[] arr,int len){//arr[0]做为哨兵,arr[1...len]是待排序元素,len是其个数
 2         for (int i = 2; i <=len ; i++) {
 3             arr[0]=arr[i];
 4             int j;
 5             for(j=i-1;arr[0]<arr[j];--j){
 6                 arr[j+1]=arr[j];
 7             }
 8             arr[j+1]=arr[0];
 9         }
10     }
View Code

希尔排序(O(n*(log n)^2)

由希尔在1959年提出,基于插入排序发展而来。希尔排序的思想基于两个缘由:

1)当数据项数量很少的时候,插入排序能够很好的完成工做。

2)当数据项基本有序的时候,插入排序具备很高的效率。

基于以上的两个缘由就有了希尔排序的步骤:

a.将待排序序列依据步长(增量)划分为若干组,对每组分别进行插入排序。初始时,step=len/2,此时的增量最大,所以每一个分组内数据项个数相对较少,插入排序能够很好的完成排序工做(对应1)。

b.以上只是完成了一次排序,更新步长step=step/2,每一个分组内数据项个数相对增长,不过因为已经进行了一次排序,数据项基本有序,此时插入排序具备更好的排序效率(对应2)。直至增量为1时,此时的排序就是对这个序列使用插入排序,这次排序完成就代表排序已经完成。

  能够看出,每次排序的步长逐渐缩小,新的一轮排序就是在上轮已排好序的分组中,添加一个新元素,而后对这个已基本有序的序列使用插入排序,这种条件下,插入排序具备最高的排序效率。实现代码以下:

 1 //希尔排序
 2     public static void shellSort(int[] arr,int len){
 3         int step=len/2;//step既是组数又是步长。
 4         int temp=0;
 5         int k=0;
 6         while (step>0){
 7             for (int i = 0; i <step ; i++) {//将待排序序列分组
 8 
 9                 for (int j = i+step; j <len;j+=step) {//每一个分组使用直接插入排序
10                     if(arr[j]<arr[j-step]){
11                         temp=arr[j];//待插入元素
12                         for (k =j-step;k>=0&&temp<arr[k];k-=step) {//后移较大的元素
13                             arr[k+step]=arr[k];
14                         }
15                         arr[k+step]=temp;
16                     }
17                 }
18             }
19             step/=2;//更新步长。
20         }
21         //System.out.println("希尔排序后的数组为:"+Arrays.toString(arr));
22     }
View Code

  以上代码不够简洁,还能够进一步改进。事先没必要分组,能够从第step个元素开始,从左向右扫描余下的序列,与索引值相差step的元素比较大小,也就是说将[step,2*step-1]与[0,step-1]区间内对应的元素比较,较大就保持不动,较小就移动至相关位置。而后再将[2*step,3*step-1]与[step,2*step-1]相比,依此类推,制止扫描到最后一个元素。实现代码以下:

 1  public static void shellSort2(int[] arr,int len){
 2         int temp=0;
 3         int step=len/2;
 4         int j=0;
 5         while (step>0){
 6             for (int i = step; i <len ; i++) {//从第setp个元素开始,将其与以前的元素相比
 7                 if (arr[i]<arr[i-step]){//使用直接插入排序
 8                     temp=arr[i];
 9                     j=i-step;
10                     while (j>=0&&arr[j]>temp){
11                         arr[j+step]=arr[j];
12                         j-=step;
13                     }
14                     arr[j+step]=temp;
15                 }
16             }
17             step/=2;
18         }
19         //System.out.println("希尔排序2后的数组为:"+Arrays.toString(arr));
20     }
View Code

新版本: 

 1 public static void shellSort3(int[] arr,int len){
 2         int j=0,tmp=0;
 3         for (int d = len/2; d >0 ; d/=2) {//d是增量,也是排序时的分组数。
 4             for (int i = d; i <len; i++) {//0~d-1是各分组的第一个元素,做为初始时插入排序的有序序列。
 5                 j=i-d;      //获得i所在的分组中,其前一个元素(有序的)
 6                 tmp=arr[i];
 7                 while (j>=0&&arr[j]>tmp){
 8                     arr[j+d]=arr[j];
 9                     j-=d;
10                 }
11                 arr[j+d]=tmp;
12             }
13         }
14     }
View Code

   希尔排序中等大小规模表现良好,对规模很是大的数据排序不是最优选择。可是比O(n^2)复杂度的算法快得多。而且希尔排序很是容易实现,算法代码短而简单。 此外,希尔算法在最坏的状况下和平均状况下执行效率相差不是不少,与此同时快速排序在最坏的状况下执行的效率会很是差。几乎任何排序工做在开始时均可以用希尔排序,若在实际使用中证实它不够快,再改为快速排序这样更高级的排序算法

选择排序(O(n^2))

像插入排序那样,将待排序序列划分为有序区和无序区(整个待排序序列)。

1)不过不一样的是,初始时,有序区为空,无序区是整个待排序序列。

2)经过比较在无序区中获得最小的记录值,将其与无序区第一个位置的元素交换,有序区就增长了一个元素,同时无序区减小了一个元素。

3)重复上述操做,直至无序区中元素个数为0。

实现代码以下:

 1 public static void selectSort(int[] arr,int len){
 2         int temp=0;
 3         int minIndex=-1;
 4         for (int i = 0; i <len ; i++) {//i是有序区最后一个位置的右侧
 5             minIndex=i;
 6             for (int j =i; j <len-1 ; j++) {//无序区
 7                 if(arr[minIndex]>arr[j+1]){
 8                     minIndex=j+1;
 9                 }
10             }
11             temp=arr[i];//有序区的最后一个位置的右侧
12             arr[i]=arr[minIndex];//将最小值放至有序区的最后一个位置上的右侧,覆盖原先值。
13             arr[minIndex]=temp;//将有序区最后一个位置的右侧的原先值赋值给无序区的最小值处。
14         }
15         System.out.println("选择排序后的数组为:"+Arrays.toString(arr));
16     }
View Code

  这里补充不使用临时变量对两个数值进行交换的方法。实现代码以下:

 1     //使用加减法来完成不使用临时变量进行交换的目的,不过当a、b很大时,可能会溢出。
 2     public int[] swap1(int a,int b){
 3         a=a+b;
 4         b=a-b;
 5         a=a-b;
 6         return new int[]{a,b};
 7     }
 8     //使用异或运算完成不使用临时变量进行交换,使用异或进行两数交换时,两数不能相等
 9     public int[] swap2(int a,int b){
10         if(a!=b) {//使用异或进行两数交换时,两数不能相等
11             a ^= b;
12             b ^= a;
13             a ^= b;
14         }
15         return new int[]{a,b};
16     }
View Code

  解释下使用异或进行交换的原理。异或位运算,当两位相同时,结果为1,不然为0。使用异或进行交换的原理以下图所示:

 

堆排序(O(n*log n))

堆的概述

堆排序是基于选择排序的改进,目的是较少比较次数。一趟选择排序中,仅保留了最小值,而堆排序排序不只保留最小值,还把较小值保留下来,减小了比较小次数。

堆是这样一种彻底二叉树:根节点的值大于等于左右孩子节点的值(最大堆)或者根节点的值小于等于左右孩子节点的值(最小堆)。堆也是递归定义的,即堆的孩子节点自己也是堆。使用数组存储堆。

堆具备如下性质:

1)彻底二叉树A[0:n-1]中的任意节点,索引为i的节点,其左右孩子节点是2*i+1和2*i+2。

2)非叶子节点最大索引是⌊n/2⌋-1,叶子节点最小索引是⌊n/2⌋。

3)最大(最小)堆的左右子树也是最大(小)堆。

若是是升序排列,就使用最大堆,反之使用最小堆。如下假设是升序排列。

排序方法

堆排序能够分为两个过程:构建初始堆和重建堆。

构建初始堆

因为每一个叶子节点自己就是以这个叶节点做为根节点的堆,而构建堆的目的就是使以每一个节点做为根节点的树都知足堆的定义,所以从堆(彻底二叉树)的最下侧非叶子节点开始构建初始堆,根据堆的性质,这个节点的索引是⌊n/2⌋-1。从下向上,一直到堆顶节点也知足堆的定义,表示完成堆的初始化。把以某个节点为根节点的树调整为堆的方法以下:

1)设这个节点为i,其左孩子为j(彻底二叉树中某个节点若是只有一个子节点,那么必定是左节点)。

2)若是arr[j]<arr[j+1],那么++j(指向右孩子)。

3)若是arr[i]>arr[j],说明这个以节点为根的树已经知足堆的定义,算法结束。

4)不然,swap(arr[i],arr[j]),因为交换过程当中破坏了原来以j为根节点的树的堆结构,因此以j为当前调整节点转步骤1,若是j为叶子节点则迭代结束(叶子节点自己就是堆)。

调整节点的方法接口是adjust(int[] arr,int k,int m),其中arr是待排序序列,k是待调整节点的索引值,m是堆的最大索引值。实现代码以下: 

 1  public static void adjust(int[] arr,int k,int m){
 2         int tmp;
 3         int i=k;//要调整的节点
 4         int j=2*k+1;//调整节点的左孩子
 5         while (j<=m){//最新的调整节点的左孩子索引值不能超过堆的最大索引
 6          if(j<m&&arr[j]<arr[j+1])
 7              ++j;//获得左右孩子中的最大值节点
 8          if(arr[i]>arr[j]){
 9              break;
10          }
11          else {
12              tmp = arr[i];
13              arr[i] = arr[j];
14              arr[j] = tmp;
15              i = j;
16              j = 2 * i + 1;
17          }
18         }
19     }
View Code

 重建堆

完成了初始堆的建立以后,就能够经过不断的重建堆进行堆排序,每次重建堆就是一趟排序,每次重建时都将堆顶节点与堆无序区的最后一个元素交换,所以每趟堆排序后堆的有序区就增长了一个元素(从数组最后与各元素开始,向前排列),下轮就使用无序区组成的堆进行重建,每次重建都只是对堆顶节点的调整,由于初始堆建成以后,其余节点都知足堆的定义。实现代码以下:

 1  //堆排序,m是待排序序列的大小
 2     public static void heapSort(int [] arr,int m){
 3         //建立初始堆
 4         int lastEleIndex=m-1;//无序区的最后一个元素的索引值
 5         int tmp;
 6         //从最下侧的非叶子节点开始,向上建立初始堆
 7         for (int i = m/2-1; i >=0 ; i--) {
 8             adjust(arr,i,lastEleIndex);
 9         }
10         //重建堆,每趟将堆顶节点与堆无序区最后一个元素交换,而后再调整新堆顶节点。每趟完成以后完成了一个元素的排序
11         for (int i = 0; i <m ; i++) {
12             tmp=arr[0];
13             arr[0]=arr[lastEleIndex];
14             arr[lastEleIndex]=tmp;
15             adjust(arr,0,--lastEleIndex);
16         }
17         System.out.println();
18     }
View Code

堆排序特色

建立初始堆的时间复杂度是O(n),简单的解释是有n/2个节点须要调整,每次调整节点时只是上写移动常数个节点,所以建立初始堆的时间复杂度是O(n)。而实际进行堆排序时,须要进行n趟,每趟进行堆重建时就是调整堆顶节点,最多移动次数不会超过书的高度O(log n),所以时间复杂度是O(n*log n)。

堆排序对数据的原始排列状态并不敏感,因此其最坏时间复杂度、最好时间复杂度、平均时间复杂度均是O(n*log n),堆排序不是一种稳定的排序算法。

归并排序(O(n*log n))

概述

归并的含义就是将两个或多个有序序列合并成一个有序序列的过程,归并排序就是将若干有序序列逐步归并,最终造成一个有序序列的过程。以最多见的二路归并为例,就是将两个有序序列归并。归并排序由两个过程完成:有序表的合并和排序的递归实现。

 

有序表的合并

虽说是两个有序表的合并,不过这里并非使用两个数组进行合并,而是经过数组索引的形式“描述”两个待合并的有序表,合并的方法签名如右所示mergeArray(int arr[],int tmp,int low,int mid,int high),其中low是合并有序表t1的起始位置,mid是t1的终止位置,mid+1是t2的起始位置,high是t2的终止位置,最后tmp是存储合并后元素的临时数组。有序表合并完成后,将临时数组tmp中元素复制到原数组相应位置。两个有序数组合并,其代码实现以下: 

 1 public static void mergeArray2(int[] arr,int tmp[],int low,int mid,int high){
 2         int i=low;
 3         int j=mid+1;
 4         int k=low;
 5         //将合并后的元素存到临时数组中
 6         while (i<=mid&&j<=high){
 7             if(arr[i]<arr[j]){
 8                 tmp[k++]=arr[i++];
 9             }
10             else {
11                 tmp[k++]=arr[j++];
12             }
13         }
14         while (i<=mid){
15             tmp[k++]=arr[i++];
16         }
17         while (j<=high){
18             tmp[k++]=arr[j++];
19         }
20         //将临时数组中内容赋值给原数组
21         for (int l =low ; l <=high ; l++) {
22             arr[l]=tmp[l];
23         }
24     }
View Code

 非递归形式

   非递归形式的归并排序的实现中,关键是假设每一个part1都有一个与之对应的part2,以part2的右边界high为两序列进行合并的检测条件。以part2的左边界mid为判断整个待排序序列最尾部的part1是否有对应的part2的检测条件。对于没有对应part2的有序表不作任何处理,对应代码以下:

 1  //二路归并排序,非递归版本
 2     public static void mergeSort(int[] array,int len){
 3         int eachGroupNumbers=1;
 4         int[] temp=new int[len];
 5         int high=-1;
 6         int low;
 7         while (eachGroupNumbers<=len){
 8             low=0;
 9             high=low+2*eachGroupNumbers-1;
10             //两两合并数组的两个有序序列
11             //假设每一个part1都有对应的part2
12 
13             //以high做为边界检测条件,若是part2的右边界high小于整个待排序序列的右边界,则两个有序序列进行合并。
14             for (; high<len ; high=low+2*eachGroupNumbers-1) {//以high做为边界检测条件
15                 mergeArray(array,low,low+eachGroupNumbers-1,high,temp);
16                 low=high+1;
17             }
18             /*
19             跳出循环,说明part2的右边界已经超出了整个待排序序列的右边界。
20             若是part2的左边界mid还在整个序列的右边界内,将两序列进行合并,
21              */
22             if(low+eachGroupNumbers-1<len){//以mid做为边界检测条件
23                 mergeArray(array,low,low+eachGroupNumbers-1,len-1,temp);
24             }
25             /*
26             若是part2的左边界也不在整个序列的右边界范围内,说明这个part1并无对应的part2,不作任何处理。
27              */
28             //本轮合并完成,继续划分数组
29             eachGroupNumbers=eachGroupNumbers<<1;
30             //System.out.println("本轮的结果:"+Arrays.toString(array));
31             }
32         System.out.println("归并排序后的数组为:"+Arrays.toString(array));
33     }
View Code   

递归形式

将待排序序列分为A和B两部分,若是A和B都是有序的,只须要调用有序序列的合并算法mergeArray就完成了排序,但是A和B不是有序的,再分别将A和B一分为二,直至最终的序列只有一个元素,咱们认为只有一个元素的序列是有序的,合并这些序列,就获得了新的有序序列,而后返回给上层调用者,上上层调用这再合并这些序列,获得更长的有序序列,这就是递归形式的归并排序,示意图以下图所示

 

图片来自:http://alinuxer.sinaapp.com/?p=141)。使用上述递归树分析归并排序的时间复杂度,以递归实现归并排序时,是自顶向下将待排序序列一分为二,直至每一个子序列元素为1。因此递归树高度为log n。因为每层元素个数为n个,因此每层中,两个有序表合并为一个新有序表时的比较次数不超过n,所以归并排序的时间复杂度是O(n*log n),而且好像无所谓最好状况、最差状况,全部状况下时间复杂度都是O(n*log n)。

实现代码以下: 

1  public static void mergeSort2(int[] arr,int[] tmp,int low,int high){
2         if(low<high){
3             int mid=low+(high-low)/2;
4             mergeSort2(arr,tmp,low,mid);
5             mergeSort2(arr,tmp,mid+1,high);
6             mergeArray2(arr,tmp,low,mid,high);
7         }
8     }
View Code

 归并排序是一种稳定的排序。 

快速排序(O(n*log n))

快速排序是图灵奖得主 C. R. A. Hoare 于 1960 年提出的一种划分交换排序。它采用了一种分治的策略,一般称其为分治法(Divide-and-ConquerMethod)。快速排序由分区和递归排序两个过程完成。

分区

分区分为三个步骤:

1.在数组中,选择一个元素做为“基准”(pivot),通常选择第一个元素做为基准元素。设置两个游标i和j,初始时i指向数组首元素,j指向尾元素。
2.从数组最右侧向前扫描,遇到小于基准值的元素中止扫描,将二者交换,而后从数组左侧开始扫描,遇到大于基准值的元素中止扫描,一样将二者交换。

3.i==j时分区完成,不然转2。(参考)

  

分区的实现代码,以下: 

 1 public static int partition2(int[] arr,int low,int high){
 2         int i=low;//左游标,选择数组第一个元素做为基准值
 3         int j=high;//右游标
 4         //i==j表示分区过程的结束
 5             while (i <j) {
 6                 //从右侧向左扫描,找到小于基准值的元素,使用i<j防止数组越界(原数组可能升序排列)
 7                 while (i<j&&arr[j] >=arr[i]) {
 8                     --j;
 9                 }
10                 //上述循环结束多是由于i==j,原数组升序排列致使。若是是这样的话,分区就能够结束了
11                if(i<j)
12                 {
13                     arr[i] ^= arr[j];
14                     arr[j] ^= arr[i];
15                     arr[i] ^= arr[j];
16                     ++i;
17                 }
18                 //从左侧向右扫描,找到大于基准值arr[j]的元素,使用i<j防止数组越界(只是此时原数组可能降序排列)
19                 while (i<j&&arr[i] <= arr[j]) {
20                     i++;
21                 }
22                 //上述循环结束多是由于i==j,原数组降序排列致使。若是是这样的话,分区就能够结束了
23                 if(i<j)
24                 {
25                     arr[i] ^= arr[j];
26                     arr[j] ^= arr[i];
27                     arr[i] ^= arr[j];
28                     --j;
29                 }
30             }
31 
32         return i;
33     }
View Code

 递归形式排序

   每次分区以后,基准值所处的位置(storeIndex)就是最终排序后它的位置,而且,一次分区以后,数据集一分为二,在分别对两侧的新分区进行分区,直至最后每一个子数据集中只剩下一个元素,代码实现以下: 

1   public static void quickSort2(int [] arr,int low,int high){
2         if(low<high){
3             int mid=partition2(arr,low,high);
4             quickSort2(arr,low,mid-1);
5             quickSort2(arr,mid+1,high);
6         }
7     }
View Code

   快速排序最好状况是每次分区后,都将序列等分为两个长度基本相等的子序列(也就是分区后基准元素都位于序列中间位置)。第一次分区后,子序列长度为n/2,第二次分区后,子序列长度为n/4,第i次分区后子序列长度为n/(2^i),直到子序列长度为1。设通过x次分区后子序列长度为1,则有n/2^x=1,则x=log n,也就是说最好状况下通过log n次分区完成排序。使用递归树来理解快速排序的最好时间复杂度。递归树的高度就是分区次数,由上述计算可知,递归树的高度是log n。在递归树的每一层总共有n个节点,而且各子序列在分区的时候关键字的比较次数不超过n,因此就有基本操做次数不超过n*log n。因此,快排在理想状况下的时间复杂度是O(n*log n)。

           快速排序理想状况下的递归树

最坏状况

  当咱们每次进行分区划分时,若是每次选择的基准元素都是当前序列中最大或最小的记录,这样每次分区的时候只获得了一个新分区,另外一个分区为空,而且新分区只是比分区前少一个元素,这是快速排序的最坏状况,时间复杂度上升为O(n^2),由于递归树的高度为n。因此,有人提出随机选择基准元素,这样在必定程度上能够避免最坏状况的发生,可是理论上最坏状况仍是存在的。参考

  因为快速排序是使用递归实现的,因此其空间复杂度就是栈的开销,最坏状况下的递归树高度是n,此时空间复杂度是O(n),通常状况下递归树的长度是log n,此时空间复杂度是O(log n)。

快速排序适用于待排序记录个数不少且分布随机的状况,而且快拍是目前内排序中排序算法最好的一种。

总结

各排序方法接口形式  

冒泡排序、插入排序、希尔排序、选择排序和堆排序的排序方法接口均是sort(int[] arr,int len)的形式,其中len是序列长度。归并排序因为须要临时数组存放两有序表合并的结果,排序方法接口是sort(int[] arr,int [] tmp,int low,int high),而快速排序不须要临时数组,其排序方法接口是sort(int[] arr,int low,int high)。

各排序算法的比较 

排序算法的稳定性是指排序先后具备相同关键字的记录,相对顺序保持不变。形式化的定义就是,排序以前有ri=rj,ri在rj以前,而在排序以后ri仍然在rj以前,就说这种算法是稳定的。各排序算法的比较以下所示:

1)排序时,能够先尝试一种较慢但简单的排序,例如插入排序,若是仍是慢,能够选择希尔排序(数据量在5000如下时颇有用),仍是慢的话,就使用快速排序,堆排序和归并排序在某些程度上较快速排序慢。

2)通常在不要求稳定性的场景下,使用快速排序就能够了。可是,当咱们每次进行分区划分时,选择的基准元素都是最小(排序目的是升序)/最大(排序目的是降序),这是快速排序的最坏状况,时间复杂度上升为O(n^2),这时可使用堆排序和归并排序。在n较大时,相对堆排序来讲,归并排序使用时间较少,但辅助空间较多(合并两个有序序列为一个有序序列)。

3)序列基本有序且n较小时,插入排序具备很高的排序效率。所以常将它和其余的排序方法,如快速排序、归并排序等结合在一块儿使用。

4)能够基于决策树证实基于比较排序算法的时间下限为O(n*log n),也便是说比较排序算法最快就是时间复杂度为O(n*log n)。比较排序算法包括:冒泡排序、插入排序、选择排序、希尔排序、归并排序、快速排序。

最好状况和最坏状况

 若是是升序排列,那么排序的最好状况就是待排序序列是升序的,最坏状况待排序序列是降序的。若是目的是降序排列,状况正好相反。

参考

1)数据结构和算法C++版第二版 王红梅

2)http://wuchong.me/blog/2014/02/09/algorithm-sort-summary/

相关文章
相关标签/搜索