几种排序算法-插入、选择、冒泡、快排、堆排等

做者:egg算法

邮箱:xtfggef@gmail.comshell

微博:http://weibo.com/xtfggefapi

博客:http://blog.csdn.net/zhangerqing(转载请说明出处)数组

 

本文就是介绍一些常见的排序算法。排序是一个很是常见的应用场景,不少时候,咱们须要根据本身须要排序的数据类型,来自定义排序算法,可是,在这里,咱们只介绍这些基础排序算法,包括:插入排序、选择排序、冒泡排序、快速排序(重点)、堆排序、归并排序等等。看下图:数据结构

给定数组:int data[] = {9,2,7,19,100,97,63,208,55,78}ide

1、直接插入排序(内部排序、O(n2)、稳定)函数

原理:从待排序的数中选出一个来,插入到前面的合适位置。性能

 

  1. package com.xtfggef.algo.sort;  
  2.   
  3. public class InsertSort {  
  4.   
  5.     static int data[] = { 9, 2, 7, 19, 100, 97, 63, 208, 55, 78 };  
  6.   
  7.     public static void insertSort() {  
  8.         int tmp, j = 0;  
  9.         for (int k = 0; k < data.length; k++) {//-----1-----  
  10.             tmp = data[k];  
  11.             j = k - 1;  
  12.             while (j >= 0 && tmp < data[j]) {//-----2-----  
  13.                 data[j + 1] = data[j];  
  14.                 j--;  
  15.             }  
  16.             data[j + 1] = tmp;//------3-------  
  17.         }  
  18.     }  
  19.   
  20.     public static void main(String[] args) {  
  21.         print();  
  22.         System.out.println();  
  23.         insertSort();  
  24.         System.out.println();  
  25.         print();  
  26.     }  
  27.   
  28.     static void print() {  
  29.         for (int i = 0; i < data.length; i++) {  
  30.             System.out.print(data[i] + " ");  
  31.         }  
  32.     }  
  33.   
  34. }  

我简单的讲解一下过程:思路上从待排序的数据中选出一个,插入到前面合适的位置,耗时点在插入方面,合适的位置意味着咱们须要进行比较找出哪是合适的位置,举个例子:对于9,2,7,19,100,97,63,208,55,78这组数,第一个数9前面没有,不作操做,当第一个数完后,剩下的数就是待排序的数,咱们将要从除去9开始的书中选出一个插入到前面合适的位置,拿到2后,放在tmp上,进行注释中的2处的代码,2处的代码就是经过循环找出这个合适的位置,发现比tmp大的数,当即将该数向后移动一位(这样作的目的是:前面须要空出一位来进行插入),最后经过注释3处的代码将数插入。学习

 

本排序适合:基本有序的数据网站

2、选择排序(O(n2)、不稳定)
与直接插入排序正好相反,选择排序是从待排序的数中选出最小的放在已经排好的后面,这个算法选数耗时。

 

  1. package com.xtfggef.algo.sort;  
  2.   
  3. public class SelectSort {  
  4.   
  5.     static int data[] = { 9, 2, 7, 19, 100, 97, 63, 208, 55, 78 };  
  6.   
  7.     public static void selectSort() {  
  8.         int i, j, k, tmp = 0;  
  9.         for (i = 0; i < data.length - 1; i++) {  
  10.             k = i;  
  11.             for (j = i + 1; j < data.length; j++)  
  12.                 if (data[j] < data[k])  
  13.                     k = j;  
  14.             if (k != i) {  
  15.                 tmp = data[i];  
  16.                 data[i] = data[k];  
  17.                 data[k] = tmp;  
  18.             }  
  19.         }  
  20.     }  
  21.     public static void main(String[] args) {  
  22.         print();  
  23.         System.out.println();  
  24.         selectSort();  
  25.         System.out.println();  
  26.         print();  
  27.     }  
  28.   
  29.     static void print() {  
  30.         for (int i = 0; i < data.length; i++) {  
  31.             System.out.print(data[i] + " ");  
  32.         }  
  33.     }  
  34.   
  35. }  

经过循环,找出最小的数的下标,赋值于k,即k永远保持待排序数据中最小的数的下标,最后和当前位置i互换数据便可。
 

 

3、快速排序(O(nlogn)、不稳定)

快速排序简称快排,是一种比较快的排序,适合基本无序的数据,为何这么说呢?下面我说下快排的思路:

设置两个指针:i和j,分别指向第一个和最后一个,i像后移动,j向前移动,选第一个数为标准(通常这样作,固然快排的关键就是这个“标准”的选取),从后面开始,找到第一个比标准小的数,互换位置,而后再从前面,找到第一个比标准大的数,互换位置,第一趟的结果就是标准左边的都小于标准,右边的都大于标准(但不必定有序),分红两拨后,继续递归的使用上述方法,最终有序!代码以下:

  1. package com.xtfggef.algo.sort;  
  2.   
  3. public class QuickSortTest {  
  4.   
  5.     static class QuickSort {  
  6.   
  7.         public int data[];  
  8.   
  9.         private int partition(int array[], int low, int high) {  
  10.             int key = array[low];  
  11.             while (low < high) {  
  12.                 while (low < high && array[high] >= key)  
  13.                     high--;  
  14.                 array[low] = array[high];  
  15.                 while (low < high && array[low] <= key)  
  16.                     low++;  
  17.                 array[high] = array[low];  
  18.             }  
  19.             array[low] = key;  
  20.             return low;  
  21.         }  
  22.   
  23.         public int[] sort(int low, int high) {  
  24.             if (low < high) {  
  25.                 int result = partition(data, low, high);  
  26.                 sort(low, result - 1);  
  27.                 sort(result + 1, high);  
  28.             }  
  29.             return data;  
  30.         }  
  31.     }  
  32.   
  33.     static void print(int data[]) {  
  34.         for (int i = 0; i < data.length; i++) {  
  35.             System.out.print(data[i] + " ");  
  36.         }  
  37.     }  
  38.   
  39.     public static void main(String[] args) {  
  40.         int data[] = { 20, 3, 10, 9, 186, 99, 200, 96, 3000 };  
  41.         print(data);  
  42.         System.out.println();  
  43.         QuickSort qs = new QuickSort();  
  44.         qs.data = data;  
  45.         qs.sort(0, data.length - 1);  
  46.         print(data);  
  47.     }  
  48. }  

 

看看上面的图,基本就明白了。

 

4、冒泡排序(稳定、基本有序可达O(n),最坏状况为O(n2))

冒泡排序是一种很简单,不管是理解仍是时间起来都比较容易的一种排序算法,思路简单:小的数一点一点向前起泡,最终有序。

  1. package com.xtfggef.algo.sort;  
  2.   
  3. public class BubbleSort {  
  4.   
  5.     static int data[] = { 9, 2, 7, 19, 100, 97, 63, 208, 55, 78 };  
  6.   
  7.     public static void bubbleSort() {  
  8.         int i, j, tmp = 0;  
  9.         for (i = 0; i < data.length - 1; i++) {  
  10.             for (j = data.length - 1; j > i; j--) {  
  11.                 if (data[j] < data[j - 1]) {  
  12.                     tmp = data[j];  
  13.                     data[j] = data[j - 1];  
  14.                     data[j - 1] = tmp;  
  15.                 }  
  16.             }  
  17.         }  
  18.     }  
  19.   
  20.     public static void main(String[] args) {  
  21.         print();  
  22.         System.out.println();  
  23.         bubbleSort();  
  24.         System.out.println();  
  25.         print();  
  26.     }  
  27.   
  28.     static void print() {  
  29.         for (int i = 0; i < data.length; i++) {  
  30.             System.out.print(data[i] + " ");  
  31.         }  
  32.     }  
  33.   
  34. }  

 

5、堆排序

咱们这里不详细介绍概念,堆的话,你们只要记得堆是一个彻底二叉树(什么是彻底二叉树,请不懂的读者去查资料),堆排序分为两种堆,大顶堆和小顶堆,大顶堆的意思就是堆顶元素是整个堆中最大的,小顶堆的意思就是堆顶元素是整个堆中最小的,知足:任何一非叶节点的关键字不大于或者不小于其左右孩子节点的关键字。堆排序是一个相对难理解的过程,下面我会较为清楚、详细的讲解一下堆排序。堆排序分为三个过程:

建堆:从一个数组顺序读取元素,创建一个堆(彻底二叉树)

初始化:将堆进行调整,使得堆顶为最大(最大堆)或者最小(最小堆)的元素

维护:将堆顶元素出堆后,须要将堆的最后一个节点补充到堆顶,由于这样破坏了堆的秩序,因此须要进行维护。下面咱们图示一下:

通常状况,建堆和初始化同步进行,

 

最后为以下所示,即为建堆、初始化成功。

咱们能够观察下这个最大堆,看出堆顶是整个堆中最大的元素,并且除叶子节点外每一个节点都大于其子节点。下面的过程就是当咱们输出堆顶元素后,对堆进行维护。

过程是这样:将堆顶元素出堆后,用最后一个元素补充堆顶元素,这样破坏了以前的秩序,须要从新维护堆,在堆顶元素的左右节点中选出较小的和堆顶互换,而后一直递归下去,因此每次出一个元素,须要一次维护,堆排序适合解决topK问题,能将复杂度降到nlogK。下面是代码:

 

  1. package com.xtfggef.algo.sort;  
  2.   
  3. public class HeapSort {  
  4.     private static int[] sort = new int[] { 1, 0, 10, 20, 3, 5, 6, 4, 9, 8, 12,  
  5.             17, 34, 11 };  
  6.   
  7.     public static void main(String[] args) {  
  8.         buildMaxHeapify(sort);  
  9.         heapSort(sort);  
  10.         print(sort);  
  11.     }  
  12.   
  13.     private static void buildMaxHeapify(int[] data) {  
  14.         // 没有子节点的才须要建立最大堆,从最后一个的父节点开始  
  15.         int startIndex = getParentIndex(data.length - 1);  
  16.         // 从尾端开始建立最大堆,每次都是正确的堆  
  17.         for (int i = startIndex; i >= 0; i--) {  
  18.             maxHeapify(data, data.length, i);  
  19.         }  
  20.     }  
  21.   
  22.     /** 
  23.      * 建立最大堆 
  24.      *  
  25.      * @param data 
  26.      * @param heapSize 
  27.      *            须要建立最大堆的大小,通常在sort的时候用到,由于最多值放在末尾,末尾就再也不纳入最大堆了 
  28.      * @param index 
  29.      *            当前须要建立最大堆的位置 
  30.      */  
  31.     private static void maxHeapify(int[] data, int heapSize, int index) {  
  32.         // 当前点与左右子节点比较  
  33.         int left = getChildLeftIndex(index);  
  34.         int right = getChildRightIndex(index);  
  35.   
  36.         int largest = index;  
  37.         if (left < heapSize && data[index] < data[left]) {  
  38.             largest = left;  
  39.         }  
  40.         if (right < heapSize && data[largest] < data[right]) {  
  41.             largest = right;  
  42.         }  
  43.         // 获得最大值后可能须要交换,若是交换了,其子节点可能就不是最大堆了,须要从新调整  
  44.         if (largest != index) {  
  45.             int temp = data[index];  
  46.             data[index] = data[largest];  
  47.             data[largest] = temp;  
  48.             maxHeapify(data, heapSize, largest);  
  49.         }  
  50.     }  
  51.   
  52.     /** 
  53.      * 排序,最大值放在末尾,data虽然是最大堆,在排序后就成了递增的 
  54.      *  
  55.      * @param data 
  56.      */  
  57.     private static void heapSort(int[] data) {  
  58.         // 末尾与头交换,交换后调整最大堆  
  59.         for (int i = data.length - 1; i > 0; i--) {  
  60.             int temp = data[0];  
  61.             data[0] = data[i];  
  62.             data[i] = temp;  
  63.             maxHeapify(data, i, 0);  
  64.         }  
  65.     }  
  66.   
  67.     /** 
  68.      * 父节点位置 
  69.      *  
  70.      * @param current 
  71.      * @return 
  72.      */  
  73.     private static int getParentIndex(int current) {  
  74.         return (current - 1) >> 1;  
  75.     }  
  76.   
  77.     /** 
  78.      * 左子节点position 注意括号,加法优先级更高 
  79.      *  
  80.      * @param current 
  81.      * @return 
  82.      */  
  83.     private static int getChildLeftIndex(int current) {  
  84.         return (current << 1) + 1;  
  85.     }  
  86.   
  87.     /** 
  88.      * 右子节点position 
  89.      *  
  90.      * @param current 
  91.      * @return 
  92.      */  
  93.     private static int getChildRightIndex(int current) {  
  94.         return (current << 1) + 2;  
  95.     }  
  96.   
  97.     private static void print(int[] data) {  
  98.         int pre = -2;  
  99.         for (int i = 0; i < data.length; i++) {  
  100.             if (pre < (int) getLog(i + 1)) {  
  101.                 pre = (int) getLog(i + 1);  
  102.                 System.out.println();  
  103.             }  
  104.             System.out.print(data[i] + " |");  
  105.         }  
  106.     }  
  107.   
  108.     /** 
  109.      * 以2为底的对数 
  110.      *  
  111.      * @param param 
  112.      * @return 
  113.      */  
  114.     private static double getLog(double param) {  
  115.         return Math.log(param) / Math.log(2);  
  116.     }  
  117. }  

慢慢理解一下,仍是容易明白的!
 

 

6、归并排序

归并排序是创建在归并操做上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个很是典型的应用。
首先考虑下如何将将二个有序数列合并。这个很是简单,只要从比较二个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。而后再进行比较,若是有数列为空,那直接将另外一个数列的数据依次取出便可。

 

  1. package com.xtfggef.algo.sort;  
  2.   
  3. public class SortTest {  
  4.   
  5.     // 将有序数组a[]和b[]合并到c[]中  
  6.     static void MemeryArray(int a[], int n, int b[], int m, int c[]) {  
  7.         int i, j, k;  
  8.   
  9.         i = j = k = 0;  
  10.         while (i < n && j < m) {  
  11.             if (a[i] < b[j])  
  12.                 c[k++] = a[i++];  
  13.             else  
  14.                 c[k++] = b[j++];  
  15.         }  
  16.   
  17.         while (i < n)  
  18.             c[k++] = a[i++];  
  19.   
  20.         while (j < m)  
  21.             c[k++] = b[j++];  
  22.     }  
  23.       
  24.     public static void main(String[] args) {  
  25.         int a[] = { 2, 7, 8, 10, 299 };  
  26.         int b[] = { 5, 9, 14, 20, 66, 88, 92 };  
  27.         int c[] = new int[a.length + b.length];  
  28.         MemeryArray(a, 5, b, 7, c);  
  29.         print(c);  
  30.     }  
  31.   
  32.     private static void print(int[] c) {  
  33.         for (int i = 0; i < c.length; i++) {  
  34.             System.out.print(c[i] + " ");  
  35.         }  
  36.     }  
  37. }  

能够看出合并有序数列的效率是比较高的,能够达到O(n)。解决了上面的合并有序数列问题,再来看归并排序,其的基本思路就是将数组分红二组A,B,若是这二组组内的数据都是有序的,那么就能够很方便的将这二组数据进行排序。如何让这二组组内数据有序了?能够将A,B组各自再分红二组。依次类推,当分出来的小组只有一个数据时,能够认为这个小组组内已经达到了有序,而后再合并相邻的二个小组就能够了。这样经过先递归的分解数列,再合并数列就完成了归并排序。下面是归并排序代码:

 

  1. package com.xtfggef.algo.sort;  
  2.   
  3. public class MergeSort {  
  4.   
  5.     private static void mergeSort(int[] data, int start, int end) {  
  6.         if (end > start) {  
  7.             int pos = (start + end) / 2;  
  8.             mergeSort(data, start, pos);  
  9.             mergeSort(data, pos + 1, end);  
  10.             merge(data, start, pos, end);  
  11.         }  
  12.     }  
  13.   
  14.     private static void merge(int[] data, int start, int pos, int end) {  
  15.         int len1 = pos - start + 1;  
  16.         int len2 = end - pos;  
  17.         int A[] = new int[len1 + 1];  
  18.         int B[] = new int[len2 + 1];  
  19.         for (int i = 0; i < len1; i++) {  
  20.             A[i] = data[i + start - 1];  
  21.         }  
  22.         A[len1] = Integer.MAX_VALUE;  
  23.         for (int i = 0; i < len2; i++) {  
  24.             B[i] = data[i + pos];  
  25.         }  
  26.         B[len2] = Integer.MAX_VALUE;  
  27.         int m = 0, n = 0;  
  28.         for (int i = start - 1; i < end; i++) {  
  29.             if (A[m] > B[n]) {  
  30.                 data[i] = B[n];  
  31.                 n++;  
  32.             } else {  
  33.                 data[i] = A[m];  
  34.                 m++;  
  35.             }  
  36.         }  
  37.     }  
  38.   
  39.     private static void print(int[] data) {  
  40.         for (int i = 0; i < data.length; i++) {  
  41.             System.out.print(data[i] + " ");  
  42.         }  
  43.     }  
  44.   
  45.     public static void main(String args[]) {  
  46.         int data[] = { 8, 16, 99, 732, 10, 1, 29, 66 };  
  47.         print(data);  
  48.         System.out.println();  
  49.         mergeSort(data, 1, data.length);  
  50.         print(data);  
  51.     }  
  52. }  

 

 

7、希尔排序(不稳定、O(nlogn))

d1 = n/2,d2 = d1/2 ...

举例一下:{9,8,7,6,5,4,3,2,1,0} 10个数,现分为5组(9,4),(8,3),(7,2),(6,1),(5,0),而后分别对每组进行直接插入排序获得:

(4,9),(3,8),(2,7),(1,6),(0,5),再将这5组分为2组(4,3,2,1,0),(9,8,7,6,5)分别对这两组进行直插排序,得:(0,1,2,3,4),(5,6,7,8,9)最终有序。

 

  1. package com.xtfggef.algo.sort;  
  2.   
  3. public class ShellSort {  
  4.   
  5.     static void shellsort(int[] a, int n) {  
  6.         int i, j, temp;  
  7.         int gap = 0;  
  8.         while (gap <= n) {  
  9.             gap = gap * 3 + 1;  
  10.         }  
  11.         while (gap > 0) {  
  12.             for (i = gap; i < n; i++) {  
  13.                 j = i - gap;  
  14.                 temp = a[i];  
  15.                 while ((j >= 0) && (a[j] > temp)) {  
  16.                     a[j + gap] = a[j];  
  17.                     j = j - gap;  
  18.                 }  
  19.                 a[j + gap] = temp;  
  20.             }  
  21.             gap = (gap - 1) / 3;  
  22.         }  
  23.     }  
  24.   
  25.     static void print(int data[]) {  
  26.         for (int i = 0; i < data.length; i++) {  
  27.             System.out.print(data[i] + " ");  
  28.         }  
  29.     }  
  30.   
  31.     public static void main(String[] args) {  
  32.         int data[] = { 2, 68, 7, 19, 1, 28, 66, 200 };  
  33.         print(data);  
  34.         System.out.println();  
  35.         shellsort(data, data.length);  
  36.         print(data);  
  37.     }  
  38. }  

 

 

8、多路快排

JDK1.8中Arrays.sort()采用的排序算法,具备较快的时间复杂度和稳定性,基本思路为:

 

1. 选取两个中轴P1, P2。

2. 假设P1<P2,不然交换。

3. 过程当中原数组会分为四个部分:小于中轴1,大于中轴2,介于两个中轴之间,未排序部分(刚开始除了两个中轴,其它元素都属于这部分)。

4. 开始后,从未排序部分选取一个数,和两个中轴做比较,而后放到合适的位置,一直到未排序部分无数据,结束一趟排序。

5. 递归地处理子数组,稳定排序,时间复杂度稳定为O(nlogn)。

详情能够参见个人另外一篇博文《Java之美[从菜鸟到高手演练]之Arrays类及其方法分析

 

9、其余排序

下面的一段转载自博友@清蒸水皮 --- 补充于2015年1月14日

==============================================

计数排序

 

当输入的元素是 n 个 0 到 k 之间的整数时,它的运行时间是 O(n + k)。计数排序不是比较排序,排序的速度快于任何比较排序算法。

因为用来计数的数组C的长度取决于待排序数组中数据的范围(等于待排序数组的最大值与最小值的差加上1),这使得计数排序对于数据范围很大的数组,须要大量时间和内存。例如:计数排序是用来排序0到100之间的数字的最好的算法,可是它不适合按字母顺序排序人名。可是,计数排序能够用在基数排序中的算法来排序数据范围很大的数组。

算法的步骤以下:

  1. 找出待排序的数组中最大和最小的元素
  2. 统计数组中每一个值为i的元素出现的次数,存入数组C的第i项
  3. 对全部的计数累加(从C中的第一个元素开始,每一项和前一项相加)
  4. 反向填充目标数组:将每一个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1

贴上代码:

 

  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <time.h>  
  4.   
  5. //对于排序的关键字范围,必定是0-99  
  6. #define NUM_RANGE (100)  
  7.   
  8. void print_arr(int *arr, int n)  
  9. {  
  10.        int i;  
  11.        for(i=0; i<n; i++){  
  12.                if(!i){  
  13.                        printf("%d", arr[i]);  
  14.                }else{  
  15.                        printf(" %d", arr[i]);  
  16.                }  
  17.        }  
  18.        printf("\n");  
  19. }  
  20.   
  21. /* 
  22. 算法的步骤以下: 
  23.     1.找出待排序的数组中最大和最小的元素 
  24.     2.统计数组中每一个值为i的元素出现的次数,存入数组C的第i项 
  25.     3.对全部的计数累加(从C中的第一个元素开始,每一项和前一项相加) 
  26.     4.反向填充目标数组:将每一个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1 
  27. */  
  28.   
  29. void counting_sort(int *ini_arr, int *sorted_arr, int n)  
  30. {  
  31.        int *count_arr = (int *)malloc(sizeof(int) * NUM_RANGE);  
  32.        int i, j, k;  
  33.   
  34.        //统计数组中,每一个元素出现的次数  
  35.        for(k=0; k<NUM_RANGE; k++){  
  36.                count_arr[k] = 0;  
  37.        }  
  38.          
  39.        for(i=0; i<n; i++){  
  40.                count_arr[ini_arr[i]]++;  
  41.        }  
  42.   
  43.   
  44.        for(k=1; k<NUM_RANGE; k++){  
  45.                count_arr[k] += count_arr[k-1];  
  46.        }  
  47.   
  48.        for(j=n-1 ; j>=0; j--){  
  49.            int elem = ini_arr[j];  
  50.            int index = count_arr[elem]-1;  
  51.            sorted_arr[index] = elem;  
  52.            count_arr[elem]--;  
  53.        }  
  54.        free(count_arr);  
  55. }  
  56.   
  57.   
  58. int main(int argc, char* argv[])  
  59. {  
  60.        int n;  
  61.        if(argc < 2){  
  62.                n = 10;  
  63.        }else{  
  64.                n = atoi(argv[1]);  
  65.        }  
  66.        int i;  
  67.        int *arr = (int *)malloc(sizeof(int) * n);  
  68.        int *sorted_arr = (int *)malloc(sizeof(int) *n);  
  69.        srand(time(0));  
  70.   
  71.          
  72.        for(i=0; i<n; i++){  
  73.                arr[i] = rand() % NUM_RANGE;  
  74.        }  
  75.   
  76.        printf("ini_array: ");  
  77.        print_arr(arr, n);  
  78.        counting_sort(arr, sorted_arr, n);  
  79.        printf("sorted_array: ");  
  80.        print_arr(sorted_arr, n);  
  81.        free(arr);  
  82.        free(sorted_arr);  
  83.        return 0;  
  84. }  

 

 

桶排序

桶排序的基本思想

假设有一组长度为N的待排关键字序列K[1....n]。首先将这个序列划分红M个的子区间(桶) 。而后基于某种映射函数 ,将待排序列的关键字k映射到第i个桶中(即桶数组B的下标 i) ,那么该关键字k就做为B[i]中的元素(每一个桶B[i]都是一组大小为N/M的序列)。接着对每一个桶B[i]中的全部元素进行比较排序(可使用快排)。而后依次枚举输出B[0]....B[M]中的所有内容便是一个有序序列。假如待排序列K= {4九、 38 、 3五、 97 、 7六、 73 、 2七、 49 }。这些数据所有在1—100之间。所以咱们定制10个桶,而后肯定映射函数f(k)=k/10。则第一个关键字49将定位到第4个桶中(49/10=4)。依次将全部关键字所有堆入桶中,并在每一个非空的桶中进行快速排序。
 

桶排序代价分析
桶排序利用函数的映射关系,减小了几乎全部的比较工做。实际上,桶排序的f(k)值的计算,其做用就至关于快排中划分,已经把大量数据分割成了基本有序的数据块(桶)。而后只须要对桶中的少许数据作先进的比较排序便可。
 
对N个关键字进行桶排序的时间复杂度分为两个部分:
(1) 循环计算每一个关键字的桶映射函数,这个时间复杂度是O(N)。
(2) 利用先进的比较排序算法对每一个桶内的全部数据进行排序,其时间复杂度为 ∑ O(Ni*logNi) 。其中Ni 为第i个桶的数据量。
 
很显然,第(2)部分是桶排序性能好坏的决定因素。尽可能减小桶内数据的数量是提升效率的惟一办法(由于基于比较排序的最好平均时间复杂度只能达到O(N*logN)了)。所以,咱们须要尽可能作到下面两点:
(1) 映射函数f(k)可以将N个数据平均的分配到M个桶中,这样每一个桶就有[N/M]个数据量。
(2) 尽可能的增大桶的数量。极限状况下每一个桶只能获得一个数据,这样就彻底避开了桶内数据的“比较”排序操做。 固然,作到这一点很不容易,数据量巨大的状况下,f(k)函数会使得桶集合的数量巨大,空间浪费严重。这就是一个时间代价和空间代价的权衡问题了。
 
对于N个待排数据,M个桶,平均每一个桶[N/M]个数据的桶排序平均时间复杂度为:
O(N)+O(M*(N/M)*log(N/M))=O(N+N*(logN-logM))=O(N+N*logN-N*logM)
当N=M时,即极限状况下每一个桶只有一个数据时。桶排序的最好效率可以达到O(N)。
 
总结: 桶排序的平均时间复杂度为线性的O(N+C),其中C=N*(logN-logM)。若是相对于一样的N,桶数量M越大,其效率越高,最好的时间复杂度达到O(N)。 固然桶排序的空间复杂度 为O(N+M),若是输入数据很是庞大,而桶的数量也很是多,则空间代价无疑是昂贵的。此外,桶排序是稳定的。
我我的还有一个感觉:在查找算法中,基于比较的查找算法最好的时间复杂度也是O(logN)。好比折半查找、平衡二叉树、红黑树等。可是Hash表却有O(C)线性级别的查找效率(不冲突状况下查找效率达到O(1))。你们好好体会一下:Hash表的思想和桶排序是否是有一曲同工之妙呢?

 

基数排序

上面的问题是多关键字的排序,但单关键字也仍然可使用这种方式。
好比字符串“abcd” “aesc” "dwsc" "rews"就能够把每一个字符当作一个关键字。另外还有整数 42五、32一、23五、432也能够每一个位上的数字为一个关键字。
 
基数排序的思想就是将待排数据中的每组关键字依次进行桶分配。好比下面的待排序列:
27八、10九、06三、930、58九、18四、50五、26九、00八、083
咱们将每一个数值的个位,十位,百位分红三个关键字: 278 -> k1(个位)=8 ,k2(十位)=7 ,k3=(百位)=2。
而后从最低位个位开始(从最次关键字开始),对全部数据的k1关键字进行桶分配(由于,每一个数字都是 0-9的,所以桶大小为10),再依次输出桶中的数据获得下面的序列。
930、06三、08三、18四、50五、27八、00八、10九、58九、269
再对上面的序列接着进行针对k2的桶分配,输出序列为:
50五、00八、10九、930、06三、26九、27八、08三、18四、589
最后针对k3的桶分配,输出序列为:
00八、06三、08三、10九、18四、26九、27八、50五、58九、930
 
性能分析
很明显,基数排序的性能比桶排序要略差。每一次关键字的桶分配都须要O(N)的时间复杂度,并且分配以后获得新的关键字序列又须要O(N)的时间复杂度。假如待排数据能够分为d个关键字,则基数排序的时间复杂度将是O(d*2N) ,固然d要远远小于N,所以基本上仍是线性级别的。基数排序的空间复杂度为O(N+M),其中M为桶的数量。通常来讲N>>M,所以额外空间须要大概N个左右。
 
可是,对比桶排序,基数排序每次须要的桶的数量并很少。并且基数排序几乎不须要任何“比较”操做,而桶排序在桶相对较少的状况下,桶内多个数据必须进行基于比较操做的排序。所以,在实际应用中,基数排序的应用范围更加普遍。

 

=============================================


文章最后,给你们推荐一个数据结构学习的网站,有flash演示的:http://www.tyut.edu.cn/kecheng1/site01/index.asp

本章的全部代码我已经上传到了个人资源,有须要的本身去下载:http://download.csdn.net/detail/zhangerqing/5288624 没有积分的,微博上私信我。

 

做者:egg

邮箱:xtfggef@gmail.com

微博:http://weibo.com/xtfggef

博客:http://blog.csdn.net/zhangerqing(转载请说明出处)

欢迎你们持续阅读本博客,同时提出好的意见和建议!

相关文章
相关标签/搜索