七大排序经典的排序算法:冒泡排序、快速排序、直接选择排序、堆排序、直接插入排序、希尔排序、归并排序。
这七大排序算法也许在排序的数据量比较小的时候差别并不是很大,但是当数据量很大的时候相差可以达几十倍,几千倍甚至更高,试想在一个大型程序中也许一个性能比较强的算法需要执行一个小时,而一个性能弱的算法可能需要几十个几百个甚至几千个小时。这是多么恐怖的差距,所以算法在程序设计当中是十分重要的一点。
这里为什么会有这么大的差距主要是涉及到他们比较的次数的问题以及交换数据的问题,交换数据是主要因素,因为涉及到内存操作十分耗时。所以以尽量少的交换数据次数实现排序,就可以得到性能优越的排序算法。
文章借鉴:http://www.cnblogs.com/nnngu/p/8283977.html ,这位大哥写得挺全的。
冒泡排序:
冒泡排序主要通过相邻元素的大小比较,把大的或则小的往前或则往后放,这个根据个人而定,但是算法思想是这样,这个算法的实现很简单主要涉及到值得比较以及元素交换。下面请看编码实现:
package com.test; public class maopao_sort { public static void maopaosort(int[] list) { int pair;//临时变量 //一次选出一个所以需要length-1次选择 for(int i=0;i<list.length-1;i++) { //选出一个过后放在数组末尾,之后不用再比较选出的所以是length-1-i,这里其实也可以改成放在前面,至于怎么改很简单了。 for(int k=0;k<list.length-1-i;k++) { if(list[k]>list[k+1]) {//大小比较决定排序顺序 pair=list[k]; list[k]=list[k+1]; list[k+1]=pair; } } } } public static void main(String[] args) { int[] b= {10,2,54,3,5445,5,44,80,65,45,21,88,554,1}; maopaosort(b); for(int m:b) { System.out.print(m+" "); } System.out.println("\n"); int[] a=new int[30000]; for(int i=0;i<a.length;i++) { a[i]=(int) (Math.random()*300000); } double start=System.currentTimeMillis(); maopaosort(a); double end=System.currentTimeMillis(); System.out.println(end-start); } }
输出结果:
处理30000条数据需要1438ms,这个算法实现简答但是速度是相当慢的。
快速排序:
快速排序(Quick Sort) 是对冒泡排序的一种改进方法,在冒泡排序中,进行元素的比较和交换是在相邻元素之间进行的,元素每次交换只能移动一个位置,所以比较次数和移动次数较多,效率相对较低。而在快速排序中,元素的比较和交换是从两端向中间进行的,较大的元素一轮就能够交换到后面的位置,而较小的元素一轮就能交换到前面的位置,元素每次移动的距离较远,所以比较次数和移动次数较少,速度较快,故称为“快速排序”。
快速排序的基本思想是:通过一轮排序将待排序元素分割成独立的两部分, 其中一部分的所有元素均比另一部分的所有元素小,然后分别对这两部分的元素继续进行快速排序,以此达到整个序列变成有序序列。快速排序的最坏时间复杂度为O(n2),平均时间复杂度为O(n*log2n)。
代码实现:
package com.test; public class kuaisu_sort { public static void quiksort(int[] list,int left,int right) { //left<right是用来判断两边的索引是否还在走,如果相等了说明不能继续排序就结束了。同时也是左右位置的一个标记。 if(left<right) { //获取索引位置 int point=divider(list, left, right); //递归排序左右索引两边的数组 quiksort(list, left, point-1); quiksort(list, point+1, right); } } //排序并返回本轮的索引位置 public static int divider(int[] list,int left,int right) { int pair=list[left]; //持续循环知道两边的索引相遇,也就是查完了数组的所有元素,就结束,返回当前的索引位置。 while(left<right) { //先从右边开始查找,找到比基准元素pair小的就停止,当然这里从左还是从右开始都是可行的。 while(left<right&&list[right]>=pair) { right--; } swap(list, left, right); //查找左边,找到比基准元素大的就停止,然后交换数据。 while(left<right&&list[left]<=pair) { left++; } swap(list, left, right); } return left; } public static void swap(int[] list,int a,int b) { if(list!=null&&list.length>0) { int mm=list[a]; list[a]=list[b]; list[b]=mm; } } public static void main(String[] args) { int[] b= {10,2,54,3,5445,5,44,80,65,45,21,88,554,1}; quiksort(b, 0, b.length-1); for(int m:b) { System.out.print(m+" "); } System.out.println("\n"); int[] a=new int[30000]; for(int i=0;i<a.length;i++) { a[i]=(int) (Math.random()*300000); } double start=System.currentTimeMillis(); quiksort(a, 0, a.length-1); double end=System.currentTimeMillis(); System.out.println(end-start); } }
运行结果:
这里处理30000条数据只用了6ms,比冒泡排序快了差不多240倍,要是数据量更大这差距可想而知了。下面我们继续看其他排序。
直接选择排序:
直接选择排序(Straight Select Sort) 是一种简单的排序方法,它的基本思想是:通过length-1 趟元素之间的比较,从length-i+1个元素中选出最小的元素,并和第i个元素交换位置。直接选择排序的最坏时间复杂度为O(n2),平均时间复杂度为O(n2)。
这里的i从0开始也就是说第一轮找到一个最小元素放在第一个位置,然后第二轮从剩下的元素中继续找最小的元素放在第二个位置。然后到length-1轮就放在length-1的位置,然后整个排序结束。
代码实现:
package com.test; public class zhijiexuanzepaixu { public static void sort(int[] list) { int min;//最小元素下标 int mm;//临时变量 //从0到length-1轮排序, for(int i=0;i<list.length-1;i++) { min=i; //遍历剩下的所有元素找到最小的或则最大的元素。 for(int k=i;k<list.length;k++){ //这里是找小的元素,如果当前元素比之前找到的最小元素还小就进行交换 if(list[k]<list[min]) { min=k; } } //这里如果最小元素已经发生了改变则进行元素交换,如果没改变就交换的话会出问题。所以这里是很关键的。 if(min!=i) { mm=list[i]; list[i]=list[min]; list[min]=mm; } } } public static void main(String[] args) { int[] b= {10,2,54,3,5445,5,44,80,65,45,21,88,554,1}; sort(b); for(int m:b) { System.out.print(m+" "); } System.out.println("\n"); int[] a=new int[30000]; for(int i=0;i<a.length;i++) { a[i]=(int) (Math.random()*300000); } double start=System.currentTimeMillis(); sort(a); double end=System.currentTimeMillis(); System.out.println(end-start); } }
运行结果:
这里处理30000条数据用时250ms。相对来说还是算比较好的算法了。接下来介绍一种性能十分优秀的算法,但是实现起来相对更加复杂,而且也不是那么通俗易懂。
堆排序:
堆排序(Heap Sort) 利用堆(一般为大根堆)进行排序的方法。它的基本思想是:将待排序的元素构造成一个大根堆。此时,整个序列的最大值就是堆顶的根节点。将它移走(其实就是将它与数组的末尾元素进行交换,此时末尾元素就是最大值),然后将剩余的length-1 个元素重新构造成一个大根堆,这样就会得到length个元素中的次大值。如此反复执行,便能得到一个有序的序列。
堆是具有下列性质的完全二叉树:每个节点的值都大于或等于其左右孩子节点的值,称为大根堆;每个节点的值都小于或等于其左右孩子节点的值,称为小根堆。堆排序的最坏时间复杂度为O(n*log2n),平均时间复杂度为O(n*log2n)。
这个算法的主要问题就是堆的构建,设计到一些位置的处理,这些问题请看具体的代码中的注释。下面是堆的整个调整过程。
图一:
图二:
图三:
图四:
图五:
图六:
代码实现:
package com.test; public class heap_Sort { public static void heapsort(int[] list) { //这里第一个父节点位置length/2-1,然后前面的所有节点都是父节点。这样顺序进行堆处理,生成最大或则最小堆 for(int i=list.length/2-1;i>=0;i--) { heapAjust(list,i,list.length); } //交换末尾元素与第一个元素,交换过后对第一个元素处理一次,这样又变成了一个堆,然后继续循环知道结束 for(int i=list.length-1;i>0;i--) { swap(list, i, 0); //这里的长度为i是因为已经排序好的元素不在处理它,如果处理了排序就会乱掉 heapAjust(list, 0, i); } } /** * * @param parent 父元素位置 * @param length 数组长度 */ private static void heapAjust(int[] list, int parent, int length) { //获取当前父元素的值 int temp=list[parent]; //对于完全二叉树父元素的左孩子位置是当前位置*2+1 int leftchild=parent*2+1; //这里leftchild<length的作用是判断当前的父元素的左孩子是否存在,超出了length也就是不存在了循环终止 while(leftchild<length) { //判断有无右孩子,如果有并且左孩子的值小与右孩子就让左孩子索引指向右孩子。这里的左孩子索引其实就是当前值较大值得一个索引 //也就是一个标记,不必在意其名称。理解了这一点就问题就简单了。这里的大小比较用于排序顺序处理。 if((leftchild+1)<length&&list[leftchild]<list[leftchild+1]) { leftchild++; } //在子节点中找到了更大的值,下面当然就是和父元素做比较了,如果父元素大直接跳出循环因为不需要进行数据交换 if(temp>=list[leftchild]) { break; } //否则进行数据交换,并继续往子树判断是否满足堆的条件。 list[parent]=list[leftchild]; parent=leftchild; leftchild=parent*2+1; } //这里的parent实际上已经变成了子节点。所以需要将原父节点的值赋值给它。 list[parent]=temp; } public static void swap(int[] list,int a,int b) { int temp=list[a]; list[a]=list[b]; list[b]=temp; } public static void main(String[] args) { int[] b= {10,2,54,3,5445,5,44,80,65,45,21,88,554,1}; heapsort(b); for(int m:b) { System.out.print(m+" "); } System.out.println("\n"); int[] a=new int[30000]; for(int i=0;i<a.length;i++) { a[i]=(int) (Math.random()*300000); } double start=System.currentTimeMillis(); heapsort(a); double end=System.currentTimeMillis(); System.out.println(end-start); } }
这里其实还有另一种实现方式,就是在比较出父元素和子元素大小的时候如果满足条件就调用交换函数,但是这样反倒会多执行交换,得不偿失。这里只是提供想法,但是效果并不可观。
这里堆排序用时6ms,和快速排序差不多,都是速度比较快的排序方式。
直接插入排序:
直接插入排序的算法思想是从数组当中按顺序取出元素然后插入到已经排好序的序列当中。也就说假如有10个元素,第一个元素先不动,直接从第二个元素开始,然后判断第二个元素和之前元素的大小关系,插入对应的位置,然后执行后一个元素,后一个元素又继续判断他在前面已经排序好的序列中应该在哪个位置,然后插入。这样length-1次循环就可以完成整个排序。
代码实现:
package com.test; public class zhijiecharupaixu { public static void sort(int[] list) { //这里从第一个元素开始 for(int i=1;i<list.length;i++) { //保存当前位置的元素 int temp=list[i]; int j; //从当前位置往前查找,找到对应的位置插入 for(j=i-1;j>=0&&list[j]>temp;j--) { list[j+1]=list[j]; } list[j+1]=temp; } } public static void main(String[] args) { int[] b= {10,2,54,3,5445,5,44,80,65,45,21,88,554,1}; sort(b); for(int m:b) { System.out.print(m+" "); } System.out.println("\n"); int[] a=new int[30000]; for(int i=0;i<a.length;i++) { a[i]=(int) (Math.random()*300000); } double start=System.currentTimeMillis(); sort(a); double end=System.currentTimeMillis(); System.out.println(end-start); } }
运行结果:
直接插入排序的处理30000条数据用时297ms算是比较好的排序算法了。
希尔排序:
希尔排序的算法思想先把排序序列分成length/2组,对每一组进行排序,然后把真个续联继续分为length/2/2组依次往后知道成为一个组。每分一次就对内部的所有序列进行排序。对内部进行排序可以采用直接插入排序。相当于在直接插入排序的基础上进行了一定的优化。
代码如下:
package com.test; public class xier_sort { public static void shellSort(int[] list) { //gap用于保存当前的分组数量。 int gap = list.length / 2; //gap>=1也就是说不能继续分了就停止 while (gap >= 1) { //这里就相当于一个直接插入排序。只是排序的间隙变成了gap所以减少了比较次数。 for (int i = gap; i < list.length; i++) { int temp = list[i]; int j; for (j = i - gap; j >= 0 && list[j] > temp; j = j - gap) { list[j + gap] = list[j]; } list[j + gap] = temp; } gap = gap / 2; } } public static void main(String[] args) { int[] b= {10,2,54,3,5445,5,44,80,65,45,21,88,554,1}; shellSort(b); for(int m:b) { System.out.print(m+" "); } System.out.println("\n"); int[] a=new int[30000]; for(int i=0;i<a.length;i++) { a[i]=(int) (Math.random()*300000); } double start=System.currentTimeMillis(); shellSort(a); double end=System.currentTimeMillis(); System.out.println(end-start); } }
运行结果:
处理30000条数据用时7ms所以希尔排序也是一个比较优秀的排序方法。
归并排序:
归并排序的原理是假设初始序列有n个元素,则可以看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到n/2个长度为2或1的有序子序列;再两两归并,…… ,如此重复,直至得到一个长度为n的有序序列为止,这种排序方法就称为归并排序。
实际的代码运行是将整个数组序列不断的分,分到最后只有两个的时候开始排序,然后网上,继续排序。归并排序采用的是递归排序,也就是或如果有如果有10个元素,先分成两组,左边一组再递归调用自身,右边同样如此,这样一直递归下去就分成了两两一组,然后底层结束了可能就是四个一组,再对这四个进行排序,就这样继续往上最后排序完成。分组方式如下:
代码实现:
package com.test; public class mergeSort { public static void mergeSort(int[] list, int[] tempList, int head, int rear) { if (head < rear) { // 取分割位置 int middle = (head + rear) / 2; // 递归划分列表的左序列 mergeSort(list, tempList, head, middle); // 递归划分列表的右序列 mergeSort(list, tempList, middle + 1, rear); // 列表的合并操作 merge(list, tempList, head, middle + 1, rear); } } public static void merge(int[] list, int[] tempList, int head, int middle, int rear) { // 左指针尾 int headEnd = middle - 1; // 右指针头 int rearStart = middle; // 临时列表的下标 int tempIndex = head; // 列表合并后的长度 int tempLength = rear - head + 1; // 先循环两个区间段都没有结束的情况 while ((headEnd >= head) && (rearStart <= rear)) { // 如果发现右序列大,则将此数放入临时列表 if (list[head] < list[rearStart]) { tempList[tempIndex++] = list[head++]; } else { tempList[tempIndex++] = list[rearStart++]; } } // 判断左序列是否结束 while (head <= headEnd) { tempList[tempIndex++] = list[head++]; } // 判断右序列是否结束 while (rearStart <= rear) { tempList[tempIndex++] = list[rearStart++]; } // 交换数据 for (int i = 0; i < tempLength; i++) { list[rear] = tempList[rear]; rear--; } } public static void main(String[] args) { int[] b= {10,2,54,3,5445,5,44,80,65,45,21,88,554,1}; mergeSort(b, new int[b.length], 0, b.length-1); for(int m:b) { System.out.print(m+" "); } System.out.println("\n"); int[] a=new int[30000]; for(int i=0;i<a.length;i++) { a[i]=(int) (Math.random()*300000); } double start=System.currentTimeMillis(); mergeSort(a, new int[a.length], 0, a.length-1); double end=System.currentTimeMillis(); System.out.println(end-start); } }
运行结果:
归并排序处理30000条数据用时7ms。
现在写一个统一的比较函数,虽然不可能保证完全的没有影响因素,但把其他的影响因素降到了最低。函数如下:
package com.test; public class compair { public static void main(String[] args) { maopao_sort maopao=new maopao_sort(); xier_sort xier=new xier_sort(); mergeSort merge=new mergeSort(); kuaisu_sort kuaisu=new kuaisu_sort(); heap_Sort heap=new heap_Sort(); zhijiecharupaixu charu=new zhijiecharupaixu(); zhijiexuanzepaixu xuanze=new zhijiexuanzepaixu(); int[] a=new int[300000]; int[] b=new int[300000]; int[] c=new int[300000]; int[] d=new int[300000]; int[] e=new int[300000]; int[] f=new int[300000]; int[] g=new int[300000]; for(int i=0;i<a.length;i++) { a[i]=b[i]=c[i]=d[i]=e[i]=f[i]=g[i]=(int) (Math.random()*30000000); } double start=System.currentTimeMillis(); kuaisu.quiksort(a, 0, a.length-1); double end=System.currentTimeMillis(); System.out.println(end-start); start=System.currentTimeMillis(); maopao.maopaosort(b); end=System.currentTimeMillis(); System.out.println(end-start); start=System.currentTimeMillis(); merge.mergeSort(c, new int[c.length], 0, c.length-1); end=System.currentTimeMillis(); System.out.println(end-start); start=System.currentTimeMillis(); xier.shellSort(d); end=System.currentTimeMillis(); System.out.println(end-start); start=System.currentTimeMillis(); xuanze.sort(e); end=System.currentTimeMillis(); System.out.println(end-start); start=System.currentTimeMillis(); heap.heapsort(f); end=System.currentTimeMillis(); System.out.println(end-start); start=System.currentTimeMillis(); charu.sort(g); end=System.currentTimeMillis(); System.out.println(end-start); } }
运行结果:
从这一点可以看出如果数据量很大冒泡排序是不能采用的。其他的排序方法可以视情况而定。