稳定排序:冒泡排序O(n2) ;插入排序O(n2);归并排序O(n log n),需要 O(n) 额外空间
不稳定排序:快速排序O(n log n) ,希尔排序O(n log n)
冒泡排序示例:
package gss.study.sort;
import java.util.Random;
import gss.util.S;
/**
* n个数从头开始每次与下一个数对比,将大的数字移到大的索引,一直到本次比较的结尾
* 第一次比较到索引n,则n就是最大的数
* 第二次比较到索引n-1,则n-1就是次大的数
* 时间复杂度为o(n^2)
*
* @author gss
* 10^3数量级,耗时在10^2数量级毫秒
*/
public class BubbleSort implements Sort {
public int[] sort(int[] input) {
int loopCount = 0;
int changeCount = 0;
if(S.isUsable(input)) {
int length = input.length;
while(length>1) {
int i=0;
for(int j=i+1;j<length;) {
loopCount++;
int currentNo = input[i];
int nextNo = input[j];
int tempNo;
if(currentNo > nextNo) {
tempNo = currentNo;
input[i]=input[j];
input[j]=tempNo;
i=j;
j++;
changeCount++;
continue;
}else {
i=j;
j++;
continue;
}
}
length--;
}
}
S.echo("共执行了"+loopCount+"次循环!");
S.echo("共执行了"+changeCount+"次交换!");
return input;
}
public static void main(String[] args) {
int[] input = new int[5000];
Random random = new Random();
for(int i=0;i<5000;i++) {
input[i] = random.nextInt(10000);
}
// S.echo("输入序列:"+S.toString(input));
Sort sort = new BubbleSort();
long beginTime = System.currentTimeMillis();
sort.sort(input);
long endTime = System.currentTimeMillis();
S.echo("冒泡耗时:"+(endTime-beginTime)+"毫秒");
// S.echo("结果序列:"+S.toString(input));
}
}
插入排序示例:
package gss.study.sort;
import java.util.Random;
import gss.util.S;
/**
* 插入排序每次把当前的数据排好序,再取下一个看插入何处
* @author gss
*
*/
public class InsertionSort implements Sort {
int[] input = null;
public static void main(String[] args) {
int[] input = new int[50000];
Random random = new Random();
for(int i=0;i<50000;i++) {
input[i] = random.nextInt(100000);
}
long beginTime;
long endTime;
Sort sort;
// S.echo("输入序列:"+S.toString(input));
int[] insertionArr = input.clone();
sort = new InsertionSort();
beginTime = System.currentTimeMillis();
sort.sort(insertionArr);
endTime = System.currentTimeMillis();
S.echo("插入排序耗时:"+(endTime-beginTime)+"毫秒");
// S.echo("结果序列:"+S.toString(insertionArr));
}
public int[] sort(int[] input) {
long loopCount = 0;
long changeCount = 0;
if(S.isUsable(input)) {
//采用内外双指针
//外层指针遍历全输入,内层指针从外层指针逆序查找该值应该在的位置
for(int i=1;i<input.length;i++) {
loopCount++;
int position = i;
int current = input[i];
while(position>0 && (input[position-1]>current)) {
input[position] = input[position-1];
position--;
changeCount++;
loopCount++;
}
input[position] = current;
}
}
S.echo("共执行了"+loopCount+"次循环!");
S.echo("共执行了"+changeCount+"次交换!");
return input;
}
}
归并排序示例:
package gss.study.sort;
import gss.util.S;
import java.util.Random;
/**
* 归并排序,时间复杂度为o(n logn),需要辅助空间
* 递归,可以是稳定的
* 将待排序输入划分成区间,相邻区间合并排列有序,递归至全数据
* @author gss
* 经测试,本机快速排序在10^6的数据下仍然有不错的效率,耗时在10^2毫秒
*/
public class MergeSort implements Sort {
public static void main(String[] args) {
int[] input = new int[5000000];
Random random = new Random();
for(int i=0;i<5000000;i++) {
input[i] = random.nextInt(10000000);
}
long beginTime;
long endTime;
Sort sort;
sort = new MergeSort();
// S.echo("输入序列:"+S.toString(input));
int[] quickArr = input.clone();
beginTime = System.currentTimeMillis();
sort.sort(quickArr);
endTime = System.currentTimeMillis();
S.echo("归并排序耗时:"+(endTime-beginTime)+"毫秒");
// S.echo("结果序列:"+S.toString(quickArr));
}
@Override
public int[] sort(int[] input) {
this.recurse(input, 0, input.length-1);
return input;
}
/**
* 递归方法,递归条件没有快速排序那么容易理解
* 注意这里使用的都是索引
* @param input 输入索引
* @param begin 起始索引
* @param end 结束索引
* @return
*/
public int[] recurse(int[]input,int begin,int end) {
//递归结束条件是,起始索引大于 等于结束索引,即归并的粒度为1则停止
if(begin>=end) {
return input;
}
//归并排序是要将输入均分的,所以要求得中值的[索引]
int mid = (end+begin)/2;
//如果输入是基数,则左边比右边多1
this.recurse(input, begin, mid);
this.recurse(input, mid+1, end);
//一次归并之后的子序列,这里要占用空间
int[] temp = this.merge(input, begin, end, mid);
for(int i=0;i<temp.length;i++) {
input[begin+i]=temp[i];
}
return input;
}
/**
* 归并方法
* 比较输入的作用区间,因为左和右本身是有序的,所以左右都用指针顺序遍历,将小的置前,大的置后,直到一方没有数据,则另一方都是大的,直接放入
* @param input 输入
* @param begin 起始索引
* @param end 结束索引
* @param mid 中位索引
* @return
*/
public int[] merge(int[] input,int begin,int end,int mid) {
//划分辅助空间
int[] result = new int[end-begin+1];
int x=begin;//左边起始索引
int y=mid+1;//右边起始索引
int z=0;//辅助空间起始索引
//循环结束条件:直到归并的一方全部合并至辅助空间
while(x<mid+1 && y<end+1) {
if(input[x]<=input[y]) {
result[z++] = input[x++];
}else
result[z++] = input[y++];
}
//如果左边还有值未归并,直接放入辅助空间最后
while(x<=mid) {
result[z++]=input[x++];
}
//如果右边有值未归并,直接放入辅助空间最后
while(y<=end) {
result[z++]=input[y++];
}
return result;
}
}
快速排序示例:
package gss.study.sort;
import gss.util.S;
import java.util.Random;
/**
* 快速排序,时间复杂度o(n logn)
* 不稳定
*
* 采用分而治之的方式,首先在目标中随机选取一个对象作为基准值
* 剩余数据与此基准值对比,将序列分为两部分,所有比基准小的放在左边,比基准大的放在右边,然后这两部分再作为输入
* 上述递归过程的终结是直到开头不小于结尾,就实际情况而言递归终结的时候输入"原子"的长度只有1或2
* @author gss
* 经测试,本机快速排序在n*10^6的数据下仍然有不错的效率,耗时在n*10^3毫秒数量级
*/
public class QuickSort implements Sort{
private Random random = new Random();
public static void main(String[] args) {
int[] input = new int[5000000];
Random random = new Random();
for(int i=0;i<5000000;i++) {
input[i] = random.nextInt(10000000);
}
long beginTime;
long endTime;
Sort sort;
sort = new QuickSort();
// S.echo("输入序列:"+S.toString(input));
int[] quickArr = input.clone();
beginTime = System.currentTimeMillis();
sort.sort(quickArr);
endTime = System.currentTimeMillis();
S.echo("快速排序耗时:"+(endTime-beginTime)+"毫秒");
// S.echo("结果序列:"+S.toString(quickArr));
}
@Override
public int[] sort(int[] input) {
this.quickSort(input, 0, input.length-1);
return input;
}
/**
* 递归主方法
* @param input
* @param begin
* @param end
*/
public void quickSort(int[]input,int begin,int end) {
if(end>begin) {
//返回一次快速排序之后的基准索引
int partitionKey = this.partition(input,begin,end);
quickSort(input,begin,partitionKey-1);
quickSort(input,partitionKey+1,end);
}
}
/**
* 快速排序的核心,排列一个序列,并且返回选定的基准数的索引
* @param input
* @param begin
* @param end
* @return
*/
public int partition(int[]input,int begin,int end) {
int key = begin + random.nextInt(end-begin);//选一个随机的基准索引
int pivot = input[key];//随机基准值
//将基准与结尾换位,其实只是为了将它找个地方放置
//即便不对换,将基准剥离出来也可以,但要改变序列的大小,所以对换到结尾是最简单的
this.swap(input, key, end);
//这个循环的核心思想是,采用双指针
//外指针顺序迭代所有数据,内指针负责指向所有大数据的开头,内指针结束的索引就是最终基准所在的索引
//开始的时候,内外指针是相等的,外指针一直向后,内指针停在大数据的开头;外指针遇到比基准小的值,就将其与内指针的当前值互换,内指针只有在这种情况下才会+1后移
//注意,结尾是放的基准值,不参与循环
for(int i=key=begin;i<end;i++) {
if(input[i]<pivot){
swap(input,key++,i);
}
}
//循环结束将基准值换回内指针当前的位置
this.swap(input, key, end);
return key;
}
/**
* 交换两个元素
* @param input
* @param i
* @param j
*/
public void swap (int[]input,int i,int j) {
int temp = input[i];
input[i] = input[j];
input[j] = temp;
}
}
希尔排序示例:
package gss.study.sort;
import gss.util.S;
import java.util.Random;
/**
* 希尔排序,就是改进的插入排序
*
* @author gss
*
*/
public class ShellSort implements Sort {
public static void main(String[] args) {
int[] input = new int[5];
Random random = new Random();
for(int i=0;i<5;i++) {
input[i] = random.nextInt(100);
}
long beginTime;
long endTime;
Sort sort;
sort = new ShellSort();
S.echo("输入序列:"+S.toString(input));
int[] quickArr = input.clone();
beginTime = System.currentTimeMillis();
sort.sort(quickArr);
endTime = System.currentTimeMillis();
S.echo("希尔排序耗时:"+(endTime-beginTime)+"毫秒");
S.echo("结果序列:"+S.toString(quickArr));
}
@Override
public int[] sort(int[] input) {
//步长数组
int[] stepArr = new int[]{ 1,5,13,43,113,297,815,1989,4711,11969,27901,84801,
213331,543749,1355339,3501671,8810089,21521774,
58548857,157840433,410151271,1131376761,2147483647 };
//选定初始步长
int stepIndex=0;
for(;stepArr[stepIndex]<input.length;stepIndex++);
//以步长迭代,步长是逐步缩小至1的
for(int m=stepIndex;m>=0;--m) {
int step = stepArr[m];
//已本次选定的步长为起点开始遍历至结尾
for (int i=step; i<input.length; i++) {
int temp = input[i];//
//j的起始为i,每次以步长为单位缩进
int j = i;
//以步长为单位进行插入排序,循环第一个判断是找的是第二步长对应位置数据,然后是第三步长跟前两个进行插入
//再是第四步长跟前三个步长内相应数据对比插入排序,依次类推到最后一个步长
while (j>=step && input[j-step]>temp) {
input[j] = input[j-step];
j = j-step;
}
input[j] = temp;//j最终停在一个步长内之相应的位置
}
}
return input;
}
}
简单测试各排序性能:
package gss.study.sort;
import gss.util.S;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Random;
public class SortAdmain {
public int[] readInput() {
S.echo("请输入待排序数组:");
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader reader = new BufferedReader(isr);
String input = null;
try {
input = reader.readLine();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String[] inputArr = input.trim().split("\\s+");
int[] result = null;
if (S.isUsable(inputArr)) {
result = new int[inputArr.length];
for (int i = 0; i < inputArr.length; i++) {
result[i] = Integer.parseInt(inputArr[i]);
}
}
return result;
}
public static void main(String[] args) {
int[] input = new int[100000];
Random random = new Random();
for(int i=0;i<100000;i++) {
input[i] = random.nextInt(100000);
}
long beginTime;
long endTime;
Sort sort;
// S.echo("输入序列:"+S.toString(input));
sort = new BubbleSort();
S.echo("\n");
int[] bubbleArr = input.clone();
beginTime = System.currentTimeMillis();
sort.sort(bubbleArr);
endTime = System.currentTimeMillis();
S.echo("冒泡排序耗时:"+(endTime-beginTime)+"毫秒");
// S.echo("结果序列:"+S.toString(bubbleArr));
S.echo("\n");
int[] insertionArr = input.clone();
sort = new InsertionSort();
beginTime = System.currentTimeMillis();
sort.sort(insertionArr);
endTime = System.currentTimeMillis();
S.echo("插入排序耗时:"+(endTime-beginTime)+"毫秒");
// S.echo("结果序列:"+S.toString(insertionArr));
sort = new MergeSort();
S.echo("\n");
int[] mergeArr = input.clone();
beginTime = System.currentTimeMillis();
sort.sort(mergeArr);
endTime = System.currentTimeMillis();
S.echo("归并排序耗时:"+(endTime-beginTime)+"毫秒");
// S.echo("结果序列:"+S.toString(mergeArr));
S.echo("\n");
sort = new QuickSort();
int[] quickArr = input.clone();
beginTime = System.currentTimeMillis();
sort.sort(quickArr);
endTime = System.currentTimeMillis();
S.echo("快速排序耗时:"+(endTime-beginTime)+"毫秒");
// S.echo("结果序列:"+S.toString(quickArr));
sort = new MergeSort();
S.echo("\n");
int[] shellArr = input.clone();
beginTime = System.currentTimeMillis();
sort.sort(shellArr);
endTime = System.currentTimeMillis();
S.echo("希尔排序耗时:"+(endTime-beginTime)+"毫秒");
// S.echo("结果序列:"+S.toString(shellArr));
}
}
结果为:
冒泡排序耗时:30224毫秒
插入排序耗时:23928毫秒
归并排序耗时:54毫秒
快速排序耗时:19毫秒
希尔排序耗时:26毫秒
结论:数组长度不大的情况下不宜使用归并排序,其它排序差别不大。数组长度很大的情况下Shell最快,Quick其次,冒泡最慢。
归并虽然速度次于Shell和Quick但是唯一的稳定排序算法