八种排序算法总结(Java实现)

        排序算法有很多,在特定情景中使用哪一种算法很重要。本文对几种常见排序算法做了简单的总结。

一、冒泡排序

        冒泡排序(BubbleSort)是一种简单的排序算法。它重复地走访要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

        1、原理:

  1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
  3. 针对所有的元素重复以上的步骤,除了最后一个。
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

八种排序算法总结(Java实现)_第1张图片 

        2、代码:

        从小到大排序数列

public class Sort {
    public static void main(String[] args){
       int score[] = { 100, 99, 90, 89, 87, 75, 69, 67 };
       for (int i = 0; i < score.length -1; i++){   //最多做n-1趟排序
           for(int j = 0 ;j < score.length - i - 1; j++){    //对当前无序区间score[0......length-i-1]进行排序(j的范围很关键,这个范围是在逐步缩小的)
              if(score[j] > score[j + 1]){    //把小的值交换到前面
                  int temp = score[j];
                  score[j] = score[j + 1];
                  score[j + 1] = temp;
              }
           }
           System.out.print("第" + (i + 1) + "次排序结果:");
           for(int a = 0; a < score.length; a++){
           System.out.print(score[a] + "\t");
            }
            System.out.println("");
       }
       System.out.print("最终的排序结果:");
       for(int a = 0; a < score.length; a++){
       System.out.print(score[a] + "\t");
       }
    }
}

        执行结果:

第1次排序结果:99   90  89  87  75  69  67  100
第2次排序结果:90   89  87  75  69  67  99  100
第3次排序结果:89   87  75  69  67  90  99  100
第4次排序结果:87   75  69  67  89  90  99  100
第5次排序结果:75   69  67  87  89  90  99  100
第6次排序结果:69   67  75  87  89  90  99  100
第7次排序结果:67   69  75  87  89  90  99  100
最终的排序结果:67   69  75  87  89  90  99  100

        3、效率:

        时间复杂度为O(n²),适用于排序小列表;最佳情况(序列原本就是正序)时间复杂度为O(n),此时需要对代码进行改进(以从大到小排列为例):

public class Sort {
    public static void main(String[] args){
       int score[] = { 100, 99, 90, 89, 87, 75, 69, 67 };
       boolean didSwap;
       for (int i = 0; i < score.length -1; i++){   //最多做n-1趟排序
           didSwap=false;
           for(int j = 0 ;j < score.length - i - 1; j++){    //对当前无序区间score[0......length-i-1]进行排序(j的范围很关键,这个范围是在逐步缩小的)
              if(score[j] < score[j + 1]){    //把大的值交换到前面
                  int temp = score[j];
                  score[j] = score[j + 1];
                  score[j + 1] = temp;
                  didSwap=true;
              }
           }
           if (didSwap==false) {
              System.out.print("第" + (i + 1) + "次排序结果:");
              for(int a = 0; a < score.length; a++){
              System.out.print(score[a] + "\t");
                }
               System.out.println("");
               System.out.print("最终的排序结果:");
              for(int a = 0; a < score.length; a++){
              System.out.print(score[a] + "\t");
              }
              return;
           }         
       }     
    }
}

        执行结果:

第1次排序结果:100  99  90  89  87  75  69  67 
最终的排序结果:100  99  90  89  87  75  69  67 

二、快速排序

        1、原理

        选择一个基准元素,通常选择第一个元素或者最后一个元素,通过一趟扫描,将待排序列分成两部分,一部分比基准元素小,一部分大于等于基准元素,此时基准元素在其排好序后的正确位置,然后再用同样的方法递归地排序划分的两部分。

        2、代码:

public class Sort {
    public static void main(String[] args) {
       int a[] = { 37, 38, 66, 97, 76, 13, 27, 49, 78, 34, 12, 64,5, 4, 62,
              99, 98, 54, 56, 18, 17, 23, 34, 15, 35, 25, 53, 51 };
       quick(a);
       for (int i = 0; i < a.length; i++){
           System.out.print(a[i]+"");
       }
    }
 
    public static int getMiddle(int[] list, int low, int high) {
       int tmp = list[low]; // 数组的第一个数作为中轴
       while (low < high) {
           while (low < high && list[high] >= tmp) {
              high--;
           }
           list[low] = list[high]; // 比中轴小的记录移到低端
           while (low < high && list[low] <= tmp) {
              low++;
           }
           list[high] = list[low]; // 比中轴大的记录移到高端
       }
       list[low] = tmp; // 中轴记录到尾
       return low; // 返回中轴的位置
    }
 
    public static void _quickSort(int[] list, int low, int high) {
       if (low < high) {
           int middle = getMiddle(list, low, high); // 将list数组进行一分为二
           _quickSort(list, low, middle - 1); // 对低字表进行递归排序
           _quickSort(list, middle + 1, high); // 对高字表进行递归排序
       }
    }
 
    public static void quick(int[] a2) {
       if (a2.length > 0) { // 查看数组是否为空
           _quickSort(a2, 0, a2.length - 1);
       }
    }
}

        执行结果:

4 5 12 13 15 17 1823 25 27 34 34 35 37 38 49 51 53 54 56 62 64 66 76 78 97 98 99

        3、效率:

        快速排序是通常被认为在同数量级(O(nlog2n))的排序方法中平均性能最好的。但若初始序列按关键码有序或基本有序时,快排排序反而蜕化为冒泡排序。为改进之,通常以“三者取中法”来选取基准记录,即将排序区间的两个端点与中点三个记录关键码居中的调整为支点记录。快速排序是一个不稳定的排序方法。

三、直接选择排序

        选择排序是常用内部排序的一种,常见的实现算法有直接选择排序算法和堆排序算法,选择排序的基本思想是每次从待排数据中选择第n小的数据放到排序列表的第n个位置,假如共有N个数据待排,那么经过N-1次排序后,待排数据就已经按照从小到大的顺序排列了。

        1、原理

        直接选择排序(Selectionsort)是一种简单直观的排序算法。它的工作原理如下。首先在未排序序列中找到最小元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小元素,然后放到排序序列末尾(目前已被排序的序列)。以此类推,直到所有元素均排序完毕。

        假设数据放在一个数组a中,且数组的长度是N,则直接选择排序的流程为:

  1. 从a[0]-a[N-1]中选出最小的数据,然后与a[0]交换位置
  2. 从a[1]-a[N-1]中选出最小的数据,然后与a[1]交换位置(第1步结束后a[0]就是N个数的最小值)
  3. 从a[2]-a[N-1]中选出最小的数据,然后与a[2]交换位置(第2步结束后a[1]就是N-1个数的最小值)
  4. 以此类推,N-1次排序后,待排数据就已经按照从小到大的顺序排列了。

八种排序算法总结(Java实现)_第2张图片 

        2、代码:

public class Sort {
    public static void main(String[] args){
       int score[] = { 49,38,65,97,76,13,27,14,10 };
       for (int i = 0; i < score.length -1; ++i){
           int k=i;
           for(int j = i ;j < score.length; ++j){   
              if(score[k] < score[j]){   
                  k=j;
              }
           }
           if (k!=i) {//交换元素
              int temp = score[i];
              score[i] = score[k];
              score[k] = temp;
           }
           System.out.print("第" + (i + 1) + "次排序结果:");
           for(int a = 0; a < score.length; a++){
           System.out.print(score[a]+ "\t");
            }
           System.out.println("");
       }
       System.out.print("最终的排序结果:");
       for(int a = 0; a < score.length; a++){
       System.out.print(score[a]+ "\t");
       }
    }
}

        执行结果:

第1次排序结果:97   38  65  49  76  13  27  14  10 
第2次排序结果:97   76  65  49  38  13  27  14  10 
第3次排序结果:97   76  65  49  38  13  27  14  10 
第4次排序结果:97   76  65  49  38  13  27  14  10 
第5次排序结果:97   76  65  49  38  13  27  14  10 
第6次排序结果:97   76  65  49  38  27  13  14  10 
第7次排序结果:97   76  65  49  38  27  14  13  10 
第8次排序结果:97   76  65  49  38  27  14  13  10 
最终的排序结果:97   76  65  49  38  27  14  13  10 

        3、效率:

        时间复杂度为(O(n*n)),适用于排序小的列表。

四、堆排序

        堆排序算法和直接选择排序算法最大的不同在于,堆排序算法充分利用大顶堆和完全二叉树的性质,保留每次排序后的结构,同时由于每次比较只是比较根节点和它的子节点,因此大大降低了比较的次数和交换的次数,从而提高效率。

        1、原理

        假设数据放在一个数组a中,且数组的长度是N:

  1. 以数组a为数据,建立一个大顶堆(这样对于二叉树的每个节点,根节点总是比子节点大,其实没必要要求二叉树的每个子树也是大顶堆)
  2. 交换大顶堆的根节点和数组a中的最后一个节点(最后一个节点不在参与后边的工作)
  3. 重复上边的工作,经过N-1次后,数组a已经排好序。

 八种排序算法总结(Java实现)_第3张图片

        2、代码:

public class Sort {
    public static void main(String[] args){
       int[] score = { 49,38,65,97,76,13,27,14,10 };
       for (int i = score.length -1; i>0;i--){
           buildHeap(score,i);//建堆
            swap(score,0,i);//交换根节点和最后一个节点
          
            System.out.print("第" + (score.length -i) + "次排序结果:");
           for(int a = 0; a < score.length; a++){
           System.out.print(score[a] + "\t");
            }
           System.out.println("");
       }
       System.out.print("最终的排序结果:");
       for(int a = 0; a < score.length; a++){
       System.out.print(score[a] + "\t");
       }
    }
    private static void buildHeap(int[] elements,int lastIndex){
        int lastParentIndex = (lastIndex-1)/2;//获得最后一个父节点
        for(int i = lastParentIndex; i >=0; i--){
            int parent = elements[i];
            int leftChild = elements[i*2+1];//左节点肯定存在
            int rightChild = leftChild;
            if(i*2+2 <=lastIndex){
                rightChild = elements[i*2+2];//右节点不一定存在
            }
            int maxIndex = leftChild

        执行结果:

第1次排序结果:10   49  65  38  76  13  27  14  97 
第2次排序结果:14   10  65  38  49  13  27  76  97 
第3次排序结果:27   49  14  38  10  13  65  76  97 
第4次排序结果:13   27  14  38  10  49  65  76  97 
第5次排序结果:10   13  14  27  38  49  65  76  97 
第6次排序结果:13   10  14  27  38  49  65  76  97 
第7次排序结果:13   10  14  27  38  49  65  76  97 
第8次排序结果:10   13  14  27  38  49  65  76  97 
最终的排序结果:10   13  14  27  38  49  65  76  97

        3、效率:

        时间复杂度为(O(nlogn),以2为底)。

五、直接插入排序

        插入排序是一种通过不断地把新元素插入到已排好序的数据中的排序算法,常用的插入排序算法包括直接插入排序和希尔(Shell)排序,直接插入排序实现比较简单,但是直接插入没有充分的利用已插入的数据已经排序这个事实,因此有很多针对直接插入排序改进的算法。

        1、原理

        在要排序的一组数中,假设前面(n-1)[n>=2] 个数已经是排好顺序的,现在要把第n个数插到前面的有序数中,使得这n个数也是排好顺序的。如此反复循环,直到全部排好顺序。

        也就是说,先从无序区拿第一个记录出来,它是有序的,然后把无序区中的记录一个一个插入到其中,那么插入之后是有序的,所以直到最后都是有序的。

        2、代码:

public class Sort {
    public static void main(String[] args) {
       int a[] = { 37, 38, 66, 97, 76, 13, 27, 49, 78, 34, 12, 64,5, 4, 62,
              99, 98, 54, 56, 18, 17, 23, 34, 15, 35, 25, 53, 51 };
       int temp = 0;
       for (int i = 1; i < a.length; i++) {
           int j = i - 1;
           temp = a[i];
           for (; j >= 0 && temp < a[j]; j--) {
              a[j + 1] = a[j]; // 将大于temp的值整体后移一个单位
           }
           a[j + 1] = temp;
       }
       for (int i = 0; i < a.length; i++) {
           System.out.print(a[i] + "");
       }
    }
}

        执行结果:

4 5 12 13 15 17 1823 25 27 34 34 35 37 38 49 51 53 54 56 62 64 66 76 78 97 98 99

        3、效率:

        时间复杂度是O(n)

六、希尔(Shell)排序

        希尔排序(ShellSort)是插入排序的一种。是针对直接插入排序算法的改进。该方法又称缩小增量排序,因DL.Shell于1959年提出而得名。

        1、原理

        先取定一个小于n的整数d1作为第1个增量,把文件的全部记录分成d1个组,所有距离为d1的倍数的记录放在同一个组中,在各组内进行直接插入排序;然后,取第2个增量d2

        2、代码:

public class Sort {
    public static void main(String[] args) {
       int a[] = { 37, 38, 66, 97, 76, 13, 27, 49, 78, 34, 12, 64,5, 4, 62,
              99, 98, 54, 56, 18, 17, 23, 34, 15, 35, 25, 53, 51 };
       double d1 = a.length;
       int temp = 0;
       while (true) {
           d1 = Math.ceil(d1 / 2);//增量d1为n/2,n为要排序数的个数
           int d = (int) d1;
           for (int x = 0; x < d; x++) {
              for (int i = x + d; i < a.length; i += d) {
                  int j = i - d;
                  temp = a[i];
                  for (; j >= 0 && temp < a[j]; j -= d) {
                     a[j + d] = a[j];
                  }
                  a[j + d] = temp;
              }
           }
           if (d == 1)
              break;
       }
       for (int i = 0; i < a.length; i++) {
           System.out.print(a[i]+"");
       }
    }
}

        执行结果:

4 5 12 13 15 17 1823 25 27 34 34 35 37 38 49 51 53 54 56 62 64 66 76 78 97 98 99

        3、效率:

        适用于排序小列表。

        效率估计O(nlog2n)~O(n1.5),取决于增量值的最初大小。建议使用质数作为增量值,因为如果增量值是2的幂,则在下一个通道中会再次比较相同的元素。

        希尔(Shell)排序改进了插入排序,减少了比较的次数。是不稳定的排序,因为排序过程中元素可能会前后跳跃。

七、归并排序

        归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divideand Conquer)的一个非常典型的应用。

        1、原理

        归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。

        2、代码:

public class Sort {
    public static void main(String[] args) {
       int a[] = { 37, 38, 66, 97, 76, 13, 27, 49, 78, 34, 12, 64,5, 4, 62 };
       sort(a,0,a.length-1); 
        for(int i=0;i

        执行结果:

[37, 38, 66, 97, 76, 13, 27,49, 78, 34, 12, 64, 5, 4, 62]
[37, 38, 66, 97, 76, 13, 27,49, 78, 34, 12, 64, 5, 4, 62]
[37, 38, 66, 97, 76, 13, 27,49, 78, 34, 12, 64, 5, 4, 62]
[37, 38, 66, 97, 13, 76, 27,49, 78, 34, 12, 64, 5, 4, 62]
[37, 38, 66, 97, 13, 76, 27,49, 78, 34, 12, 64, 5, 4, 62]
[37, 38, 66, 97, 13, 27, 49,76, 78, 34, 12, 64, 5, 4, 62]
[13, 27, 37, 38, 49, 66, 76,97, 78, 34, 12, 64, 5, 4, 62]
[13, 27, 37, 38, 49, 66, 76,97, 34, 78, 12, 64, 5, 4, 62]
[13, 27, 37, 38, 49, 66, 76,97, 34, 78, 12, 64, 5, 4, 62]
[13, 27, 37, 38, 49, 66, 76,97, 12, 34, 64, 78, 5, 4, 62]
[13, 27, 37, 38, 49, 66, 76,97, 12, 34, 64, 78, 4, 5, 62]
[13, 27, 37, 38, 49, 66, 76,97, 12, 34, 64, 78, 4, 5, 62]
[13, 27, 37, 38, 49, 66, 76,97, 4, 5, 12, 34, 62, 64, 78]
[4, 5, 12, 13, 27, 34, 37, 38,49, 62, 64, 66, 76, 78, 97]
4 5 12 13 27 34 3738 49 62 64 66 76 78 97

        3、效率:

        时间复杂度为O(nlogn)这是该算法中最好、最坏和平均的时间性能。

八、基数排序

        基数排序(radixsort)属于“分配式排序”(distribution sort),又称“桶子法”(bucketsort)或bin sort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些"桶"中,藉以达到排序的作用。

        1、原理

        将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。

        2、代码:

public class Sort {
    public static void main(String[] args) {
       int a[] = { 37, 38, 66, 97, 76, 13, 27, 49, 78, 34, 12, 64,5, 4, 62 };
       sort(a);
       for (int i = 0; i < a.length; i++)
           System.out.print(a[i] + "");
    }
 
    public static void sort(int[] array) {
       int max = array[0];
       for (int i = 1; i < array.length; i++) {
           if (array[i] > max) {
              max = array[i];
           }
       }
 
       int time = 0;
       // 判断位数;
       while (max > 0) {
           max /= 10;
           time++;
       }
       // 建立10个队列;
       List queue = new ArrayList();
       for (int i = 0; i < 10; i++) {
           ArrayList queue1 = newArrayList();
           queue.add(queue1);
       }
       // 进行time次分配和收集;
       for (int i = 0; i < time; i++) {
           // 分配数组元素;
           for (int j = 0; j < array.length; j++) {
              // 得到数字的第time+1位数;
              int x = array[j] % (int) Math.pow(10, i + 1)
                     / (int) Math.pow(10, i);
              ArrayList queue2 = queue.get(x);
              queue2.add(array[j]);
              queue.set(x, queue2);
           }
           int count = 0;// 元素计数器;
           // 收集队列元素;
           for (int k = 0; k < 10; k++) {
              while (queue.get(k).size() > 0) {
                  ArrayList queue3 = queue.get(k);
                  array[count] = queue3.get(0);
                  queue3.remove(0);
                  count++;
              }
           }
       }
    }
}

        执行结果:

4 5 12 13 27 34 3738 49 62 64 66 76 78 97

        3、效率:

        基数排序法是属于稳定性的排序,其时间复杂度为O(nlog(r)m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。

总结

        1、各种排序的稳定性,时间复杂度和空间复杂度总结

        见下图:

八种排序算法总结(Java实现)_第4张图片

        2、时间复杂度说明

八种排序算法总结(Java实现)_第5张图片

        当原表有序或基本有序时,直接插入排序和冒泡排序将大大减少比较次数和移动记录的次数,时间复杂度可降至O(n);

        而快速排序则相反,当原表基本有序时,将蜕化为冒泡排序,时间复杂度提高为O(n2);

        原表是否有序,对简单选择排序、堆排序、归并排序和基数排序的时间复杂度影响不大。

        3、选择排序算法的依据

        影响排序的因素有很多,平均时间复杂度低的算法并不一定就是最优的。相反,有时平均时间复杂度高的算法可能更适合某些特殊情况。同时,选择算法时还得考虑它的可读性,以利于软件的维护。一般而言,需要考虑的因素有以下四点:

  1. 待排序的记录数目n的大小;
  2. 记录本身数据量的大小,也就是记录中除关键字外的其他信息量的大小;
  3. 关键字的结构及其分布情况;
  4. 对排序稳定性的要求。

        设待排序元素的个数为n.

        1)当n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序、堆排序或归并排序序。

        快速排序:是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;

        堆排序:如果内存空间允许且要求稳定性的,

        归并排序:它有一定数量的数据移动,所以我们可能过与插入排序组合,先获得一定长度的序列,然后再合并,在效率上将有所提高。

        2)当n较大,内存空间允许,且要求稳定 → 归并排序

        3)当n较小,可采用直接插入或直接选择排序。

        直接插入排序:当元素分布有序,直接插入排序将大大减少比较次数和移动记录的次数。

        直接选择排序 :元素分布有序,如果不要求稳定性,选择直接选择排序

        5)一般不使用或不直接使用传统的冒泡排序。

        6)基数排序

        它是一种稳定的排序算法,但有一定的局限性:

    1、关键字可分解。

    2、记录的关键字位数较少,如果密集更好

    3、如果是数字时,最好是无符号的,否则将增加相应的映射复杂度,可先将其正负分开排序。

你可能感兴趣的:(八种排序算法总结(Java实现))