对于一个排序算法来说,一般从3个方面来衡量算法的优劣。
1、 时间复杂度:主要是分析关键字的比较次数和记录的移动次数
2、 空间复杂度:分析排序算法需要多少辅助内存
3、 稳定性:若两个记录A和B的关键字值相等,但排序后A、B的先后次序保持不变,则称这种排序算法是稳定的;反之,就是不稳定的。
排序算法大致可分为内部排序和外部排序。
如果整个排序过程不需要借助于外部存储器(如磁盘),所有排序操作都在内存中完成,称为内部排序。
如果参与排序的数据元素非常多,数据量非常大,计算机无法把整个排序过程放在内存中完成,必须借助于外部存储器,这种排序称为外部排序。
外部排序最常用的算法是多路归并排序,即将原文件分解成多个能够一次性装入内存的部分,分别把每一个部分调入内存完成排序,接下来再对多个有序的子文件进行归并排序。
内部排序分类
1、 选择排序(直接选择排序、堆排序)
2、 交换排序(冒泡排序、快速排序)
3、 插入排序(直接插入排序、折半插入排序、Shell排序)
4、 归并排序
直接选择排序-----选择排序
直接选择排序算法的关键就是n-1趟比较,每趟比较的目的就是选择出本趟比较中最小的数据,并将该数据放在本趟比较的第1位。只要找到本趟比较中最小的数据,然后拿它和本趟第1位的数据交换。
package com.crazyjava;
public class SelectSort1 {
public static void main(String[] args) {
DataWrap datas[] = new DataWrap[6];
datas[0] = new DataWrap(21);
datas[1] = new DataWrap(30);
datas[2] = new DataWrap(49);
datas[3] = new DataWrap(30,"*");
datas[4] = new DataWrap(16);
datas[5] = new DataWrap(9);
printArray(datas);
selectSort(datas);
printArray(datas);
}
public static void selectSort(DataWrap[] datas){
if(datas==null || datas.length==0)return ;
for(int i=0 ; i<datas.length-1; i++){
int minIndex = i ;
for(int j=i; j<datas.length; j++){
if(datas[minIndex].compareTo(datas[j])>0){
minIndex = j ;
}
}
if(minIndex!=i){
DataWrap tmp = datas[i] ;
datas[i] = datas[minIndex];
datas[minIndex]=tmp;
}
}
}
public static void printArray(DataWrap[] datas){
for(DataWrap data : datas){
System.out.print(data+",");
}
System.out.println();
}
}
时间效率为O(n2),空间效率为O(1),直接选择排序是不稳定的。
堆排序-----选择排序
假设有n个数据元素的序列k0,k1,…,kn-1,当且仅当满足如下关系时,可以将这组数据称为小顶堆(小根堆)
Ki <= k2i+1 且 ki <= k2i+2(其中i=0,2,…,(n-1)/2)
或者,满足如下关系时,可以将这组数据称为大顶堆(大根堆)
Ki>=k2i+1 且 ki >= k2i+2 (其中i=0,2…,(n-1)/2)
堆排序与直接选择排序的差别在于,堆排序可通过树形结构保存部分比较结果,可减少比较次数。
建堆的过程不断重复如下步骤:从最后一个非叶子结点开始,比较该结点和它两个子结点的值;如果某个子结点的值大于父结点的值,把父结点和较大的子结点交换。向前逐步调整直到根结点,即保证每个父结点的值都大于等于其左右子结点的值,建堆完成。
package com.crazyjava;
public class HeapSort2 {
public static void main(String[] args) {
DataWrap[] datas = new DataWrap[6];
datas[0]=new DataWrap(9);
datas[1]=new DataWrap(79);
datas[2]=new DataWrap(46);
datas[3]=new DataWrap(30);
datas[4]=new DataWrap(58);
datas[5]=new DataWrap(49);
heapSort(datas);
heapSort(new DataWrap[]{new DataWrap(23),new DataWrap(2)});
}
public static void heapSort(DataWrap[] datas){
if(datas==null || datas.length<=0)return ;
SelectSort1.printArray(datas);
for(int i=0; i<datas.length-1; i++){
buildHeap(datas,datas.length-1-i);
swap(datas,0,datas.length-1-i);
}
SelectSort1.printArray(datas);
}
public static DataWrap[] buildHeap(DataWrap[] datas,int end){
if(datas==null || end <0)return null;
int startIndex = (end-1)/2;
for(int i=startIndex; i>=0; i--){
int k = i ;
//如果当前k结点的子结点存在
while((2*k+1)<=end){
int biggerIndex = 2*k+1 ; //保存k结点的左右子结点值最大的索引
//代表k结点的右子结点存在
if(biggerIndex < end){
if(datas[biggerIndex+1].compareTo(datas[biggerIndex])>0){
biggerIndex = biggerIndex+1;
}
}
//如果k结点的值小于较大的子结点的值就交换
if(datas[biggerIndex].compareTo(datas[k])>0){
swap(datas,biggerIndex,k);
//循环,保证k结点的值大于其左右子结点
k = biggerIndex;
}else{
break;
}
}
}
return datas;
}
private static void swap(DataWrap[] datas, int biggerIndex, int k) {
DataWrap tmp = datas[k];
datas[k]=datas[biggerIndex];
datas[biggerIndex] = tmp ;
}
}
堆排序,假设有N项数据,需要进行n-1次建堆,每次建堆本身耗时为log2n,则其时间效率为(nlog2n).空间效率为O(1)。堆排序是不稳定的。
冒泡排序------交换排序
交换排序的主体操作是对数据组中的数据不断进行交换操作。
冒泡排序对于一组包含n个数据的一组纪录,最坏的情况下,冒泡排序需要进行n-1趟比较。每一趟比较把最大的一个数通过交换移至尾端。
package com.crazyjava;
public class BubbleSort6 {
public static void main(String[] args) {
DataWrap[] datas = {
new DataWrap(9),
new DataWrap(16),
new DataWrap(21),
new DataWrap(23),
new DataWrap(30),
new DataWrap(49),
new DataWrap(21,"*"),
new DataWrap(30,"*"),
};
bubbleSort(datas);
}
public static void bubbleSort(DataWrap[] datas){
if(datas==null || datas.length<=0)return ;
SelectSort1.printArray(datas);
for(int i=0; i<datas.length-1; i++){
boolean flag = false ;
for(int j=0 ; j< datas.length-1-i ; j++){
if(datas[j].compareTo(datas[j+1])>0){
DataWrap tmp = datas[j] ;
datas[j] = datas[j+1];
datas[j+1] = tmp;
flag = true ;
}
}
SelectSort1.printArray(datas);
if(!flag){
break;
}
}
SelectSort1.printArray(datas);
}
}
时间复杂度为O(n2),空间复杂度为O(1),冒泡排序是稳定的。
快速排序-------交换排序
其思路为从待排的 数据序列中任取一个数据(如第一个数据)作为分界值国,所有比它小的数据元素一律放到左边,所有比它大的数据元素一律放到右边。一趟之后,左边的序列比分界值小,右边的序列比分界值大。接下来对左右子序列进行递归,对两个子序列重新选择中心元素并依此规则调整,直到每个子元素只剩一个,排序完成。
package com.crazyjava;
public class QuickSort7 {
public static void main(String[] args) {
DataWrap[] datas = {
new DataWrap(9),
new DataWrap(-16),
new DataWrap(21),
new DataWrap(23),
new DataWrap(-30),
new DataWrap(-49),
new DataWrap(21,"*"),
new DataWrap(30),
new DataWrap(13),
};
quickSort(datas);
}
public static void quickSort(DataWrap[] datas){
if(datas==null || datas.length<=0)return ;
SelectSort1.printArray(datas);
subSort(datas,0,datas.length-1);
SelectSort1.printArray(datas);
}
public static void subSort(DataWrap[] datas,int start, int end){
if(datas==null)return ;
if(start < end ){
DataWrap tmp = datas[start];
int i = start + 1; int j = end ;
while(i<j ){
while(datas[i].compareTo(tmp)<=0 && i<=end)i++;
while(datas[j].compareTo(tmp)>=0 && j>start)j-- ;
if(i<j){
HeapSort2.swap(datas, i, j);
}else{
break;
}
}
HeapSort2.swap(datas,start, j);
subSort(datas, start, j-1);
subSort(datas, j+1, end);
}
}
}
时间复杂度 O(nlog(n)),空间复杂度为O(log2n),快速排序是不稳定排序.
直接插入排序---------插入排序
其思路为依次将待排序的数据元素按其关键字值的大小插入前面的有序序列。
package com.crazyjava;
public class InsertSort3 {
public static void main(String[] args) {
DataWrap[] datas = {
new DataWrap(9),
new DataWrap(-16),
new DataWrap(21),
new DataWrap(23),
new DataWrap(-30),
new DataWrap(-49),
new DataWrap(21,"*"),
new DataWrap(30),
new DataWrap(30,"*"),
};
insertSort(datas);
}
public static void insertSort(DataWrap[] datas) {
if(datas==null || datas.length<0)return ;
SelectSort1.printArray(datas);
for(int i=1 ; i<datas.length; i++){
if(datas[i-1].compareTo(datas[i])>0){
DataWrap tmp = datas[i];
int j = i-1 ;
//整体往后移一位,直到小于当前数
for(; j>=0 && datas[j].compareTo(tmp)>0;--j){
datas[j+1] = datas[j];
}
datas[j+1] = tmp;
}
}
SelectSort1.printArray(datas);
}
}
时间复杂度为O(n2),空间复杂度为O(1),直接插入排序是稳定的。
折半插入排序---------插入排序
折半插入排序是对直接插入排序进行简单改进,通过二分查找更快的确定有序序列中第i个元素的插入位置,再将要插入位置后的元素全部后移一位。
package com.crazyjava;
public class BinaryInsertSort4 {
public static void main(String[] args) {
DataWrap[] datas = {
new DataWrap(9),
new DataWrap(-16),
new DataWrap(21),
new DataWrap(23),
new DataWrap(-30),
new DataWrap(-49),
new DataWrap(21,"*"),
new DataWrap(30),
new DataWrap(30,"*"),
};
binaryInsertSort(datas);
}
public static void binaryInsertSort(DataWrap[] datas){
if(datas==null || datas.length<=0)return ;
SelectSort1.printArray(datas);
for(int i=1; i<datas.length ; i++){
if(datas[i-1].compareTo(datas[i])>0){
DataWrap tmp = datas[i] ;
int low = 0; int high = i-1 ;
while(low <= high){
int mid = (low+high)/2 ;
if(datas[mid].compareTo(datas[i])>0){
high = mid -1 ;
}else{
low = mid + 1 ;
}
}
for(int j=i ; j>low ; j--){
datas[j] = datas[j-1];
}
datas[low] = tmp ;
}
}
SelectSort1.printArray(datas);
}
}
Shell排序------------插入排序
对于直接插入和折半插入排序,如果一个很小的数据单元位于很靠近右端的位置上,为了把这个数据单元移动到左边正确的位置上,中间所有的数据单元都需要向右移动一格。Shell排序对直接插入排序进行了简单改进:它通过加大插入排序中元素之间的间隔,并在这些有间隔元素中进行插入排序,从而使数据项大跨度地移动。当这些数据项排过一趟序后,Shell排序算法减小数据项的间隔再进行排序,依次进行下去。直到完成以1为增量的Shell排序,此时数据序将会变为有序。
Shell排序算法的关键在于确定h序列的值,常用的h序列由Knuth提出,该序列从1开始,通过如下公式产生:h = h*3+1 ;即1、4、13、40….,反向h =(h-1)/3即40、13、4、1.
package com.crazyjava;
public class ShellSort5 {
public static void main(String[] args) {
DataWrap[] datas = {
new DataWrap(9),
new DataWrap(-16),
new DataWrap(21),
new DataWrap(23),
new DataWrap(-30),
new DataWrap(-49),
new DataWrap(21,"*"),
new DataWrap(30),
new DataWrap(30,"*"),
};
shellSort(datas);
}
public static void shellSort(DataWrap[] datas){
if(datas==null || datas.length <= 0 )return ;
System.out.println("start:");
SelectSort1.printArray(datas);
int h = 1 ;
while( h <= (datas.length/3)){
h = h*3 + 1;
}
while(h>0){
System.out.println("h = "+h);
for(int i =h ; i<datas.length ; i++){
DataWrap tmp = datas[i] ;
if(datas[i-h].compareTo(tmp)>0){
int j = i-h ;
for(; j>=0 && datas[j].compareTo(tmp)>0 ; j-=h){
datas[j+h] = datas[j];
}
datas[j+h] = tmp;
}
SelectSort1.printArray(datas);
}
h = (h-1)/3 ;
}
System.out.println("end:");
SelectSort1.printArray(datas);
}
}
时间复杂度估计在O(N3/2)-O(N7/6),空间复杂度为O(1),它是稳定的。
归并排序
其基本思想是将两个(或以上)有序的序列合并成一个新的有序序列。细化说,归并排序先将长度为n的无序序列看成是n个长度为1的有序子序列,首先做两两合并,得到n/2个长度为2的有序子序列,再做两两合并…….不断重复这 个过程最终可以得到一个长度为n的有序序列。
package com.crazyjava;
public class MergeSort8 {
public static void main(String[] args) {
DataWrap[] datas={
new DataWrap(9),
new DataWrap(-16),
new DataWrap(21),
new DataWrap(23),
new DataWrap(-30),
new DataWrap(-49),
new DataWrap(21,"*"),
new DataWrap(30),
new DataWrap(30,"*"),
};
mergeSort(datas);
}
public static void mergeSort(DataWrap[] datas){
if(datas==null || datas.length<=0)return ;
SelectSort1.printArray(datas);
DataWrap[] tmpDatas = new DataWrap[datas.length];
sort(datas,tmpDatas,0,datas.length-1);
SelectSort1.printArray(datas);
}
public static void sort(DataWrap[] datas,DataWrap[] tmpDatas, int start, int end){
if(start==end)return ;
if(start < end ){
int mid = (start+end)/2;
sort(datas,tmpDatas,start,mid);
sort(datas,tmpDatas,mid+1,end);
merge(datas,tmpDatas,start,mid,end);
}
}
public static void merge(DataWrap[] datas,DataWrap[] tmpDatas, int start, int center, int end){
int i = start ; int j = center+1;
int k = start ;
while(i<=center && j<=end){
if(datas[i].compareTo(datas[j])<=0 ){
tmpDatas[k++] = datas[i++];
}else{
tmpDatas[k++] = datas[j++];
}
}
while(i<=center){
tmpDatas[k++] = datas[i++];
}
while(j<=end){
tmpDatas[k++] = datas[j++];
}
for(int t= start ; t<=end; t++){
datas[t] = tmpDatas[t];
}
}
}
时间复杂度为O(nlog2n),空间复杂度O(n),归并排序是稳定的。