排序算法有多种分类方式,按照是否需要外存储器分为内部排序和外部排序,按照稳定性分为稳定排序和不稳定排序。今天我这里只是涉及到内部排序的几种经典算法,也供自己以后复习所用。
内部排序基本分为四大类:插入排序(Insertion),交换排序(exchange),选择排序(selection)和归并排序(merging)。
插入排序:直接插入排序(straight Insert)、希尔排序(shell)
交换排序:冒泡(buddle)、快速排序(Quick)
选择排序:直接(简单)选择排序、堆排序(Heap)
归并排序:只有自己咯
今天不讨论buddle sort和simple selection,主要讨论其他几种。先来个表,让各位有个直观的印象:
序号 |
排序类别 |
平均时间复杂度 |
最好情况 |
最坏情况 |
空间复杂度(辅助存储) |
稳定 |
1 |
直接插入排序 |
O(n2) |
O(n) |
O(n2) |
1 |
√ |
2 |
希尔排序 |
O(n2) |
|
|
1 |
× |
3 |
冒泡排序 |
O(n2) |
|
|
1 |
√ |
4 |
快速排序 |
O(Nlogn) |
|
O(n2) |
O(logn) |
× |
5 |
简单选择排序 |
O(n2) |
|
O(n2) |
1 |
× |
6 |
堆排序 |
O(Nlogn) |
|
O(Nlogn) |
1 |
× |
7 |
归并排序 |
O(Nlogn) |
|
O(Nlogn) |
O(n) |
√ |
插入排序:
1.直接插入排序
直接插入排序的基本思想为:将一个记录插入到一个已经有序的列表中,从而得到新的有序的列表。
整个过程分为n-1趟插入,即:先将序列的第一个元素看作是一个有序的子序列。
public class StraightInsertion {
public static void insertSort(int[] data) {
int length = data.length;
//从数组的第二个元素开始:相当于第一个元素为以排序的数组array2
for (int i = 1; i < length; i++) {
//tmp即为待插入的元素
int tmp = data[i];
//由于是逐次的,所以若tmp大于有序的array2的第一个元素,则tmp为最大元素
if ( data[i - 1] > tmp) {
int j = i - 1;
//当待插入元素逐次和array2比较(没有交换,这里仅仅是后移,给tmp预留空间)
//当tmp大于data[j]时,跳出循环,则数据插入到j + 1的位置上
for (; j >= 0 && data[j] > tmp; j--) {
//元素后移操作
data[j + 1] = data[j];
}
//
data[j + 1] = tmp;
}
}
}
}
2.希尔排序
shell sort又称Diminishing Increment Sort(缩小增量排序)。
希尔排序是改良版的直接插入排序,原因有二:1.当记录基本有序时,直接插入排序的时间复杂度,从O(n2)提高至O(n).2.当n值很小时效率也比较高。
希尔排序的基本思想是:先将整个待排序记录分割为若干子序列分别进行直接插入排序,待整个记录基本有序时,再对整体进行一次直接插入排序。
/**
* shell排序,这里的增量(delta value),使用的
* <br>1.首先確定轮数,公式为2^n < length, 通过轮数确定第一个增量为2^n - 1.
* <br>2.
*
*/
public class Shell {
public static void loop(Integer[] data){
if(data == null) return;
//先找出最大圈数和最大的delta
int times = 1;
int delta = -1;
while(Math.pow(2, times) < data.length){
delta = (int) (Math.pow(2, times) - 1);
times++;
}
do{
for (int i = 0; i < delta; i++) {
insertSort(data, i, delta);
}
times = (int) ((Math.log(delta + 1)/Math.log(2)) - 1);
delta = (int) (Math.pow(2, times) - 1);
}while(delta > 0);
}
/**
* 这里就是插入排序的实现,不同点是待排序array为非连续的
* @param data
* @param from 从下表from开始
* @param delta 增量值
*/
public static void insertSort(Integer[] data, int from , int delta) {
if(data == null) return;
int length = data.length;
//从数组的第二个元素开始:相当于第一个元素为以排序的数组array2
int size = (length - from) % delta == 0 ? (length - from) / delta : ((length - from) / delta) + 1 ;
for (int i = 1; i < size; i++) {
//tmp即为待插入的元素
int tmp = data[from + i * delta];
//由于是逐次的,所以若tmp大于有序的array2的第一个元素,则tmp为最大元素
if ( data[from + (i - 1) * delta] > tmp) {
int j = i - 1;
//当待插入元素逐次和array2比较(没有交换,这里仅仅是后移,给tmp预留空间)
//当tmp大于data[j]时,跳出循环,则数据插入到j + 1的位置上
for (; from + j * delta >= from && data[from + j * delta] > tmp; j--) {
//元素后移操作
data[from + (j + 1) * delta] = data[from + j * delta];
}
//
data[from + (j + 1) * delta] = tmp;
}
}
}
}
交换排序:快速排序
快速排序思想:通过一趟排序,将待排序记录分为独立的两部分(找到枢轴),其中一部分的均比另一部分小(通过和枢轴比较,大于枢轴的放在枢轴的后面,否则反之,让后将枢轴放在移动后的空缺位置,即枢轴位置),则可分别对这两部分记录继续进行排序。
/**
* 快速排序,同样为不稳定排序
* 循环去寻找一个数组中的枢轴,使得这个枢轴的左端元素小于等于枢轴,右端元素大于等于枢轴
*/
public class Quick {
public static void sort(Integer[] data, int low, int high){
if(low < high){
int pivotLoc = partition(data, low, high);
if(pivotLoc > low)
sort(data, low, pivotLoc - 1);
if(pivotLoc < high)
sort(data, pivotLoc + 1, high);
}
}
/**
* 寻找枢轴
* @param data
* @param low
* @param high
* @return
*/
public static int partition(Integer[] data, int low , int high){
int pivot = data[high];
//high -=1;
while(low < high){
while(low < high && pivot >= data[low])
low++;
data[high] = data[low];
while(low < high && pivot <= data[high])
high--;
data[low] = data[high];
}
data[high] = pivot;
return high;
}
}
选择排序:
选择排序的基本思想:每一趟在n-i+1(i=1,2,、、n-1)个记录中,选取最小的记录作为有序序列的第i个元素。
简单选择排序:
通过n-i次比较,从n-i+1个记录中选择最小的记录,并和第i个记录进行交换。
堆排序
将序列排为大顶堆,将第一个元素输出之后,n-1个元素再次调整为大顶堆,得到次小值,如此反复执行,得到有序序列,成为堆排序。
n个节点的完全二叉树的深度为(log2n向下取整+1)
public class Heap {
public static void heapSort(Integer[] data){
//第一轮,从最后一个非终端节点开始,将数组建成大顶堆
for (int i = data.length / 2 - 1 ; i >= 0 ; i--) {
heapAdjust(data, i, data.length - 1);
}
//将交换过后的堆,继续筛选为大顶堆
for (int last = data.length - 1; last > 0; last--) {
//将堆顶和堆的最后一个元素进行交换
int tmp = data[last];
data[last] = data[0];
data[0] = tmp;
heapAdjust(data, 0, last - 1);
}
}
/**
*
* @param data 待排序的数组
* @param s 非终端节点的下标
* @param m 带排序的数组的length
*/
public static void heapAdjust(Integer[] data, int s , int m){
int parent = data[s];
int child = -1;
for ( child = s * 2 + 1; child <= m; child = child * 2 + 1) {
if(child < m && data[child] < data[child + 1]) child++;
if(parent >= data[child]) break;
data[s] = data[child];
s = child;
}
data[s] = parent;
}
}
归并排序
归并的含义是:将两个或两个以上的有序表,组合成为一个新的有序表。
思路:假设含有n个记录,则可看为是n个有序的子序列,每个子序列长度为1,两两归并,得到长度为n/2向上取整个长度为2或者1的有序子序列,再两两归并……
public class Merging {
public static Integer[] sort(Integer[] data){
if( data.length <= 1){
return data;
}
Integer[] left = Arrays.copyOfRange(data, 0, data.length / 2);
Integer[] right = Arrays.copyOfRange(data, data.length / 2, data.length);
left = sort(left);
right = sort(right);
Integer[] result = merge(left, right);
return result;
}
public static Integer[] merge(Integer[] left, Integer[] right){
Integer[] result = new Integer[left.length + right.length];
int i = 0;
while(left.length > 0 && right.length > 0){
if(left[0] > right[0]){
result[i++] = left[0];
left = Arrays.copyOfRange(left, 1, left.length);
}else{
result[i++] = right[0];
right = Arrays.copyOfRange(right, 1, right.length);
}
}
if(left.length > 0){
System.arraycopy(left, 0, result, i, result.length - i);
}
if(right.length > 0){
System.arraycopy(right, 0, result, i, result.length - i);
}
return result;
}
}
这个归并排序用了性能很差的递归,一般情况下都不这样用,会调整为消耗更大存储空间的循环来做。