冒泡排序经过两两比较相邻记录的关键字,反序则交换,从而达到排序的效果java
for(int i=0; i<o.length; i++){ for(int j=0; j<o.length-i-1; j++){ if(o[j]>o[j+1]){ swap(o,j,j+1); } } }
上面的代码有个小问题,设想存在待排序序列{1,3,5,7,11,9},通过第一遍遍历交换11和9以后已经达到排序效果,接下来的遍历已无心义。考虑增长一个标志位,若上一轮遍历没有引发序列任何变化,则退出整个遍历,再也不作无心义的比较。算法
boolean flag = true; for(int i=0; i<o.length&&flag; i++){ flag = false; for(int j=0; j<o.length-i-1; j++){ if(o[j]>o[j+1]){ swap(o,j,j+1); flag = true; } } }
冒泡排序还有几种变形,或是向上冒泡、向下冒泡,但都差异不大。分析时间复杂度:最好状况下无需交换,只要进行n-1次比较,时间复杂度O(n);最坏状况下,即待排序序列为逆序,须要比较n*(n-1)/2次,并做等数量级的交换,时间复杂度O(n^2)。数组
与冒泡排序区别的是,冒泡排序只要发现反序就会交换相邻的两个数,而简单选择排序是先找出最小或最大的数,与特定位置交换,能够看出其优势是交换移动次数至关少。数据结构
int min; for(int i=0; i<o.length-1; i++){ min = i; for(int j=i+1; j<o.length; j++){ if(o[min]>o[j]){ min = j; } } if(min != i){ swap(o,min,i); } }
分析简单选择排序的复杂度,不管最好最坏状况下,比较次数都是n*(n-1)/2,但最好状况下交换0次,最坏状况也只有n-1次,总的复杂度还是O(n^2)。测试
直接插入排序的思想是向一个有序序列逐步插入记录,获得记录数不断增长的序列,若初始为乱序序列,则选取第一个元素做为一个有序序列,其余元素依次插入这个有序序列,从而达到排序效果。优化
int j,temp; for(int i=1; i<o.length; i++){ if(o[i] < o[i-1]){ temp = o[i]; for(j=i-1; j>=0&&o[j]>temp; j--){ o[j+1] = o[j]; } o[j+1] = temp; } }
直接插入与理扑克牌有点类似,最好状况下须要比较n-1次,无需交换;最坏状况下比较(n+2)(n-1)/2次,移动(n+4)(n-1)/2次。设计
希尔排序是基于直接插入排序的,直接插入排序在元素较少和元素基本有序时效率是不错的,但随着元素增多和有序性破坏,效率会降低的很明显。希尔排序经过分组实现跳跃式移动,保证待排序序列基本有序后再排序,就能提升效率。code
int j,temp; int increment = o.length; do{ increment = increment/3+1; for(int i=increment; i<o.length; i++){ temp = o[i]; for(j=i-increment; j>=0&&o[j]>temp; j-=increment){ o[j+increment] = o[j]; } o[j+increment] = temp; } }while(increment>1);
希尔排序的关键在于增量increment的选择,最坏状况下,能够取得时间复杂度O(n^3/2)的算法。排序
堆是特殊的彻底二叉树,每一个节点的值都大于等于(小于等于)其左右孩子节点的值。堆排序其实相似简单选择排序,每次找出最大最小元素,移到特定位置完成排序。堆排序相对于简单选择排序的优势是,以前排序的结果得以保存,能被后面的排序利用。rem
//堆排序 for(int i=o.length/2-1; i>=0; i--){ //从最后一个有子节点的节点开始依次往前调整对应节点来生成大顶堆 heapAdjust(o,i,o.length-1); } for(int i=0; i<o.length-1; i++){ //交换堆顶元素与未排序堆最后一个元素 swap(o,0,o.length-i-1); //根据调整节点从新生成大顶堆 heapAdjust(o,0,o.length-2-i); } //构建大顶堆 void heapAdjust(int[] o,int i,int j){ for(int m=i*2+1; m<=j; m=2*i+1){ if(m<j&&o[m]<o[m+1]){ m ++; } if(o[m]>o[i]){ swap(o,m,i); } i = m; } }
堆排序的时间复杂度主要取决于构建堆和重建堆两部分,构建堆的时间复杂度O(n^2),重建堆的时间复杂度为O(nlgn),总的时间复杂度为O(nlgn)。
归并排序的思想是逐层将序列子元素两两归并成有序序列,每一个子序列长度经历一、二、四、...、n,最后获得完整有序序列。
int[] sort(int[] o,int m,int n){ int mid; int[] result = new int[o.length]; if(o.length == 1|| m==n){ result[0] = o[0]; }else{ mid = (m+n)/2; int[] temp1 = new int[mid-m+1]; int[] temp2 = new int[o.length-mid+m-1]; System.arraycopy(o,0,temp1,0,mid-m+1); System.arraycopy(o,mid-m+1,temp2,0,o.length-mid+m-1); int[] result1 = sort(temp1,m,mid); int[] result2 = sort(temp2,mid+1,n); result = Merge(result1,result2); } return result; } int[] Merge(int[] i,int[] j){ int m=0,n=0,k=0; int[] result = new int[i.length+j.length]; for(; m<i.length&&n<j.length; k++){ if(i[m]<j[n]){ result[k] = i[m++]; }else{ result[k] = j[n++]; } } if(m<i.length){ while(m<i.length){ result[k++] = i[m++]; } } if(n<j.length){ while(n<j.length){ result[k++] = j[n++]; } } return result; }
上面的算法用了大量临时数组,是一种很差的设计,其实只须要一个与原数组大小相同的临时数组(必须有,若是只有数组自己,那么排序时可能覆盖原有元素),就能完成归并排序。
void sort(int[] o, int lo, int high){ if(hi <= lo) return; int mid = lo+(high-lo)/2; sort(o,lo,mid); sort(o,mid+1,hi); merge(o,lo,mid,hi); } void merge(int[] o,int lo,int mid,int high){ int i = lo,j = mid+1; int[] aux = new int[hi-lo+1]; for(int k=lo; k<=hi, k++) if(i>mid) a[k] = aux[j++]; else if(j>hi) a[k] = aux[i++]; else if(aux[j]<aux[i]) a[k] = aux[j++]; else a[k] = aux[i++]; }
归并排序须要扫描序列中全部记录,耗费O(n)时间,整个归并排序须要logn,所以,总的时间复杂度为O(nlgn),空间复杂度为O(n+logn)。
归并排序能够从几个方面优化:对小规模子数组使用插入排序;测试数组是否已经有序,有序就能够跳过merge方法;不将元素复制到辅助数组(复制改成排序)。
快速排序的思想是分割,确保一个元素左边的元素都小于这个元素,右边的元素都大于这个元素,而后对这两部分分别继续进行分割,从而达到排序的效果。
void sort(int[] i,int low,int high){ int temp; if(low<high){ temp = partition(i,low,high); sort(i,low,temp-1); sort(i,temp+1,high); } } int partition(int[] i,int low,int high){ int privotkey = i[low]; while(low<high){ while(low<high&&i[high]>=privotkey) high --; swap(i,low,high); while(low<high&&i[low]<=privotkey) low ++; swap(i,low,high); } return low; }
快速排序的时间复杂度最优和平均都是O(nlgn),最坏状况下为O(n^2),空间复杂度为O(lgn),快速排序很脆弱,有一些优化手段,好比转插入等;希尔排序效率高,代码简单,不须要额外空间,较为经常使用;
直接插入排序在序列基本有序时效率较高,对于随机排序序列,插入排序和选择排序的运行时间是平方级别的,二者之比应该是一个较小的常数。
排序算法 | 平均状况 | 最好状况 | 最坏状况 | 辅助空间 | 稳定性 |
---|---|---|---|---|---|
冒泡排序 | O(n^2) | O(n) | O(n^2) | O(1) | 稳定 |
简单选择排序 | O(n^2) | O(n^2) | O(n^2) | O(1) | 稳定 |
直接插入排序 | O(n^2) | O(n) | O(n^2) | O(1) | 稳定 |
希尔排序 | O(nlogn)~O(n^2) | O(n^1.3) | O(n^2) | O(1) | 不稳定 |
堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(1) | 不稳定 |
归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 |
快速排序 | O(nlogn) | O(nlogn) | O(n^2) | O(logn)~O(n) | 不稳定 |
参考:《大话数据结构》、《算法》。