一,直接插入排序
其原理是,将序列分为一排序的有序子序列,和待排序的无序子序列。将待排序序列中的第一个元素插入到已排序子序列中去。使已排序序列+1,待排序序列-1。
/**
* 直接插入排序
* */
public static void straightInsertionSort(int[] array) {
for(int i = 1;i<array.length;i++) {
int t = array[i];
int j;
for(j = i-1;j>=0&&array[j]>t;j--) {
array[j+1] = array[j];
}
array[j+1] = t;
}
}
二,折半插入排序
折半插入排序是直接插入的一种改进,在查找插入位置时,因为是在有序的序列上查找,所以可以使用折半查找(二分查找)
/**
* 折半插入排序:查找插入位置时,使用二分查找
* */
public static void bInsertSort(int[] array) {
for(int i = 1;i<array.length;i++) {
int low = 0;
int high = i -1;
int t = array[i];
while(high>=low) {//二分查找插入位置
int m = (low + high)/2;
if(t>array[m]) low = m +1;
else high = m -1;
}
for(int j = i-1;j>=low;j--) array[j+1] = array[j];//移动元素
array[low] = t;
}
}
三,希尔排序
希尔排序也是直接插入排序的一种变体,其基本思想是,将一个待排序列分为若干个子序列,分别对若干个子序列进行插入排序,待整个序列基本有序之后,再对全体进行一次直接插入排序。这样可以减少元素移动的次数,从而大大的提高排序效率。
//增量序列计算比较复杂,且不同的增量序列,会大大的影响算法的效率,所以这里直接使用网上推荐的比较好的增量序列
public static int[] dlta = {9814,3280,1093,364,121,40,13,4,1};
/**
* 希尔排序
* */
public static void shellSort(int[] array) {
for(int i = 0;i<dlta.length;i++) {
shellInsert(dlta[i],array);
}
}
/**
* @param dk 增量,普通的插入排序其增量为1
* */
private static void shellInsert(int dk,int[] array) {
for(int i = dk;i<array.length;i++) {
int t = array[i];
int j;
for(j = i - dk;j>=0&&t<array[j];j -= dk) {
array[j+dk] = array[j];
}
array[j+dk] = t;
}
}
优点:速度快
缺点:不稳定,增量序列的取值影响排序效率,且尚未找到最合适的增量序列
4,冒泡排序
其基本思想是,一躺冒泡从序列的最后一个元素开始和倒数第二个元素比较,若小于倒数第二个元素就将两个元素交换位置,依次进行,直到最小的元素被交换到最上为止。进行n-1躺冒泡之后序列整体有序。
/**
* 冒泡排序
* */
public static void bubbleSort(int[] array) {
for(int i = 0;i<array.length;i++) {
for(int j = array.length-1;j>i;j--) {
if(array[j-1]>array[j]) {
int t = array[j];
array[j] = array[j-1];
array[j-1] = t;
}
}
}
}
5,快速排序
快速排序是效率很高的一种排序算法。其基本思想是,通过一躺排序,将序列以一个枢轴为中间点分为两部分,其中枢轴左侧的都要小于枢轴元素,枢轴右侧都要大于枢轴元素。然后分别再对递归的对左右两边的子序列进行排序。
public static void quickSort(int[] array){
qSort(0,array.length -1,array);
}
/**
* 快速排序,对low到high的元素进行排序
* */
private static void qSort(int low,int high,int[] array) {
if(low < high) {
int t = partition(low,high,array);
qSort(low,t-1,array);
qSort(t+1,high,array);
}
}
/**
* 一次快速排序,将序列分为两部分,pivotkey的右边的元素均小于pivotkey
*pivotkey的左边的元素均大于pivotkey
* */
private static int partition(int low,int high,int[] array) {
int pivotkey = array[low];
while(low<high) {
while(low<high && array[high]>=pivotkey) high--;
array[low] = array[high];
while(low<high && array[low]<=pivotkey) low++;
array[high] = array[low];
}
array[low] = pivotkey;
return low;//返回pivotkey的下标
}
优点:平均速度目前最快
缺点:不稳定,若序列有序时退化成冒泡排序,效率大大下降(有改进算法解决此问题)
6,选择排序
选择排序其思想是,每次从待排序子序列中找到最小的元素,然后和待排序子序列的第一个元素进行交换,然后待排序子序列-1,直到待排序子序列=1为止。
/**
* 选择排序
* */
public static void selectSort(int[] array) {
int min;//记录最小值
for(int i = 0;i<array.length-1;i++) {
min = i;
int j;
for(j = i+1;j<array.length;j++) {//寻找最小值
if(array[j]<array[min]) min = j;
}
int t = array[i];//交换位置
array[i] = array[min];
array[min] = t;
}
}
7,归并排序
归并排序也是一种效率很高的排序算法。归并,意思是将两个有序的序列合并为一个新的有序的序列。
而归并排序则则是利用了归并算法的一种排序算法。假设初始序列含有n个记录,则可以看成是n个有序的子序列,每个子序列长度为一,然后两两进行归并,得到n/2个有序子序列,然后在两两归并,直到得到一个长度为n的有序序列为止。
public static void mergingSort(int[] array) {
int temp[] = new int[array.length];
mSort(0,array.length-1,array,temp);
}
/**
* 归并排序
* */
private static void mSort(int low,int high,int[] array,int[] temp) {
if(low == high) {
temp[low] = array[low];
}
else {
int m = (low + high)/2;
mSort(low,m,array,temp);
mSort(m+1,high,array,temp);
merging(low,m,high,array,temp);
}
}
/**
* 将两个有序的子序列,归并为新的有序子序列
* L[low...m]&&L[m+1...high]
* */
private static void merging(int low,int m,int high,int[] array,int[] temp) {
int i = low,j = m+1;
int dt = 0;
while(i<=m && j<=high) {
if(array[i]<array[j]) {
temp[low+dt] = array[i];
i++;
}
else {
temp[low+dt] = array[j];
j++;
}
dt++;
}
while(i<=m) {
temp[low+dt] = array[i];
i++;
dt++;
}
while(j<=high) {
temp[low+dt] = array[j];
j++;
dt++;
}
//将排序好的数据复制回源数组
for(int a = low;a<=high;a++) {
array[a] = temp[a];
}
}
优点:稳定,快速
缺点:需要辅助空间
8,堆排序
堆的定义,一颗完全二叉树其根节点是整个树中最小的或者最大的元素,那么这个完全二叉树就是堆(大根堆/小根堆)。完全二叉树可以对于一个一维数组。第i个节点其左孩子为2i其右孩子为2i+1。
堆排序就是利用堆的一种排序算法,因为堆顶元素是最小或最大的元素,因此输出堆顶元素,之后使得剩下的序列重新建成一个堆,在输出堆顶元素,反复进行即可获得有序序列。
/**
* 调整堆头为i的堆,使其最小的元素成为堆头(大堆)
* 其左孩子下标为 2i 右孩子下标为2i+1;
* 这里数组下标从0开始,所以左右孩子下标为2i+1与2i+2
*
* */
private static void heapAdjust(int s,int m,int[] array) {
int rc = array[s];
for(int j = 2*s+1;j<=m;j=2*j+1) {
if(j<m && array[j+1]>array[j]) j++;//选择较大的孩子
if(rc>array[j]) break;
array[s] = array[j];
s = j;
}
array[s] = rc;
}
/**
* 将一个混乱的序列,构造成一个堆,最后一个非终端节点为(n-1)/2(n为数组最后一个元素下标)
* */
private static void createHeap(int[] array) {
int end = (array.length-2)/2;//计算最后一个非终端节点下标
for(int i = end;i>=0;i--) heapAdjust(i,array.length-1,array);//从最后一个非终端节点开始,往上依次调整堆
}
public static void heapSort(int[] array) {
createHeap(array);
for(int j = array.length-1;j>0;j--) {
int t = array[0];
array[0] = array[j];
array[j] = t;
heapAdjust(0,j-1,array);
}
}
优点:速度快
缺点:不稳定
9,效率测试实验
package cn.sort;
import java.util.Arrays;
import java.util.Random;
public class MainClass {
public static void main(String[] args) {
// TODO Auto-generated method stub
// System.out.println(Arrays.toString(createArray(100000)));
long stime = System.currentTimeMillis();
long etime = System.currentTimeMillis();
int n = 100000;
int[] array1 = createArray(n);
int[] array2 = Arrays.copyOf(array1, array1.length);
int[] array3 = Arrays.copyOf(array1, array1.length);
int[] array4 = Arrays.copyOf(array1, array1.length);
int[] array5 = Arrays.copyOf(array1, array1.length);
int[] array6 = Arrays.copyOf(array1, array1.length);
int[] array7 = Arrays.copyOf(array1, array1.length);
int[] array8 = Arrays.copyOf(array1, array1.length);
stime = System.currentTimeMillis();
Sort.bubbleSort(array4);
etime = System.currentTimeMillis();
System.out.println("冒泡排序\t\t"+n+"数据排序耗时\t"+(etime - stime)+"ms");
stime = System.currentTimeMillis();
Sort.straightInsertionSort(array1);
etime = System.currentTimeMillis();
System.out.println("直接插入排序\t"+n+"数据排序耗时\t"+(etime - stime)+"ms");
stime = System.currentTimeMillis();
Sort.bInsertSort(array2);
etime = System.currentTimeMillis();
System.out.println("二分插入排序\t"+n+"数据排序耗时\t"+(etime - stime)+"ms");
stime = System.currentTimeMillis();
Sort.selectSort(array6);
etime = System.currentTimeMillis();
System.out.println("选择排序\t\t"+n+"数据排序耗时\t"+(etime - stime)+"ms");
stime = System.currentTimeMillis();
Sort.shellSort(array3);
etime = System.currentTimeMillis();
System.out.println("希尔排序\t\t"+n+"数据排序耗时\t"+(etime - stime)+"ms");
stime = System.currentTimeMillis();
Sort.quickSort(array5);
etime = System.currentTimeMillis();
System.out.println("快速排序\t\t"+n+"数据排序耗时\t"+(etime - stime)+"ms");
stime = System.currentTimeMillis();
Sort.mergingSort(array7);
etime = System.currentTimeMillis();
System.out.println("归并排序\t\t"+n+"数据排序耗时\t"+(etime - stime)+"ms");
stime = System.currentTimeMillis();
Sort.heapSort(array8);
etime = System.currentTimeMillis();
System.out.println("堆排序\t\t"+n+"数据排序耗时\t"+(etime - stime)+"ms");
}
//生成长度为n的随机数数组
public static int[] createArray(int n) {
int[] array = new int[n];
Random r = new Random();
for(int i = 0;i<array.length;i++) {
array[i] = r.nextInt(100000);
}
return array;
}
public static boolean isSort(int[] array) {
int p = array[0];
for(int i = 1;i<array.length;i++) {
if(array[i]<p) return false;
else p = array[i];
}
return true;
}
}
实验结果:
冒泡排序 100000数据排序耗时 15483ms
直接插入排序 100000数据排序耗时 3323ms
二分插入排序 100000数据排序耗时 3467ms
选择排序 100000数据排序耗时 2266ms
希尔排序 100000数据排序耗时 20ms
快速排序 100000数据排序耗时 19ms
归并排序 100000数据排序耗时 19ms
堆排序 100000数据排序耗时 22ms
10,不同的增量序列堆希尔排序效率的影响实验
增量序列
{9814,3280,1093,364,121,40,13,4,1}
实验结果
希尔排序 100000数据排序耗时 19ms
增量序列
public static int[] dlta = {10000,1000,100,10,1};
实验结果
希尔排序 100000数据排序耗时 61ms