一. 排序算法概述
所谓排序,就是根据排序码的递增或者递减顺序把数据元素依次排列起来,使一组任意排列的元素变为一组按其排序码线性有序的元素。本文将介绍八种最为经典常用的内部排序算法,包括插入排序(直接插入排序,希尔排序)、选择排序(直接选择排序,堆排序)、交换排序(冒泡排序,快速排序)、归并排序、分配排序(基数排序)。实际上,无论是基本排序方法(直接插入排序,直接选择排序,冒泡排序),还是高效排序方法(快速排序,堆排序,归并排序)等,它们各有所长,都拥有特定的使用场景。因此,在实际应用中,我们必须根据实际任务的特点和各种排序算法的特性来做出最合适的选择。一般地,我们衡量一个算法的指标包括:
-
时间复杂度 (在排序过程中需要比较和交换的次数)
-
空间复杂度 (在排序过程中需要的辅助存储空间)
-
稳定性 (该算法的实现是否可以保证排序后相等元素的初始顺序)
-
内部排序/外部排序 (在排序过程中数据元素是否完全在内存)
笔者将在本文着重探讨上述八种排序算法的思想和实现,并就各算法根据以上指标进行分析和归类,以便进一步熟悉它们各自的应用场景。
二. 插入排序
插入排序的基本思想:每步将一个待排序元素,按其排序码大小插入到前面已经排好序的一组元素中,直到元素全部插入为止。在这里,我们介绍三种具体的插入排序算法:直接插入排序,希尔排序与折半插入排序。
1、直接插入排序
直接插入排序的思想:当插入第i(i>=1)个元素时,前面的V[0],…,V[i-1]等i-1个 元素已经有序。这时,将第i个元素与前i-1个元素V[i-1],…,V[0]依次比较,找到插入位置即将V[i]插入,同时原来位置上的元素向后顺移。在这里,插入位置的查找是顺序查找。直接插入排序是一种稳定的排序算法,其实现如下:
/**
* Title: 插入排序中的直接插入排序,依赖于初始序列
* Description: 在有序序列中不断插入新的记录以达到扩大有序区到整个数组的目的
* 时间复杂度:最好情形O(n),平均情形O(n^2),最差情形O(n^2)
* 空间复杂度:O(1)
* 稳 定 性:稳定
* 内部排序(在排序过程中数据元素完全在内存)
* @author rico
* @created 2017年5月20日 上午10:40:00
*/
public class StraightInsertionSort {
public static int[] insertSort(int[] target){
if(target != null && target.length != 1){ // 待排序数组不为空且长度大于1
for (int i = 1; i < target.length; i++) { // 不断扩大有序序列,直到扩展到整个数组
for (int j = i; j > 0; j--) { // 向有序序列中插入新的元素
if(target[j] < target[j-1]){ // 交换
int temp = target[j];
target[j] = target[j-1];
target[j-1] = temp;
}
}
}
}
return target;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
2、希尔排序
希尔排序的思想:设待排序序列共n个元素,首先取一个整数gap<n作为间隔,将全部元素分为间隔为gap的gap个子序列并对每一个子序列进行直接插入排序。然后,缩小间隔gap,重复上述操作,直至gap缩小为1,此时所有元素位于同一个序列且有序。由于刚开始时,gap较大,每个子序列元素较少,排序速度较快;待到排序后期,gap变小,每个子序列元素较多,但大部分元素基本有序,所以排序速度仍较快。希尔排序是一种不稳定的排序方法,其实现如下:
/**
* Title: 插入排序中的希尔排序,依赖于初始序列
* Description: 分别对间隔为gap的gap个子序列进行直接插入排序,不断缩小gap,直至为 1
*
* 刚开始时,gap较大,每个子序列元素较少,排序速度较快;
* 待到排序后期,gap变小,每个子序列元素较多,但大部分元素基本有序,所以排序速度仍较快。
*
* 时间复杂度:O(n) ~ O(n^2)
* 空间复杂度:O(1)
* 稳 定 性:不稳定
* 内部排序(在排序过程中数据元素完全在内存)
* @author rico
* @created 2017年5月20日 上午10:40:00
*/
public class ShellSort {
public static int[] shellSort(int[] target){
if(target != null && target.length != 1){
int gap = target.length; // gap个大小为gap的子序列
do{
gap = gap/3 + 1; // 不断缩小gap直至为1
for (int i = 0 + gap; i < target.length; i++) { // 对每个子序列进行直接插入排序
if(target[i] < target[i-gap]){
int j = i - gap;
int temp = target[i]; // 待插入值
do{
target[j + gap] = target[j]; // 后移元素
j = j - gap; // 再比较前一个元素
}while(j >= 0 && target[j] > temp); // 向前比较的终止条件
target[j + gap] = temp; // 将待插入值插入合适的位置
}
}
}while(gap > 1);
}
return target;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
3、折半插入排序
折半插入排序的思想:当插入第i(i>=1)个元素时,前面的V[0],…,V[i-1]等i-1个 元素已经有序。这时,折半搜索第i个元素在前i-1个元素V[i-1],…,V[0]中的插入位置,然后直接将V[i]插入,同时原来位置上的元素向后顺移。与直接插入排序不同的是,折半插入排序比直接插入排序明显减少了关键字之间的比较次数,但是移动次数是没有改变。所以,折半插入排序和插入排序的时间复杂度相同都是O(N^2),但其减少了比较次数,所以该算法仍然比直接插入排序好。折半插入排序是一种稳定的排序算法,其实现如下:
/**
* Title: 插入排序中的折半插入排序,依赖于初始序列
* Description: 折半搜索出插入位置,并直接插入;与直接插入搜索的区别是,后者的搜索要快于顺序搜索
* 时间复杂度:折半插入排序比直接插入排序明显减少了关键字之间的比较次数,但是移动次数是没有改变。所以,
* 折半插入排序和插入排序的时间复杂度相同都是O(N^2),在减少了比较次数方面它确实相当优秀,所以该算法仍然比直接插入排序好。
* 空间复杂度:O(1)
* 稳 定 性:稳定
* 内部排序(在排序过程中数据元素完全在内存)
* @author rico
* @created 2017年5月25日 下午12:03:23
*/
public class BinaryInsertSort {
public static int[] binaryInsertSort(int[] target) {
if (target != null && target.length > 1) {
for (int i = 1; i < target.length; i++) {
int left = 0;
int right = i - 1;
int mid;
int temp = target[i];
if(temp < target[right]){ // 当前值小于有序序列的最大值时,开始查找插入位置
while(left <= right){
mid = (left + right)/2;
if(target[mid] < temp){
left = mid + 1; // 缩小插入区间
}else if(target[mid] > temp){
right = mid - 1; // 缩小插入区间
}else{ // 待插入值与有序序列中的target[mid]相等,保证稳定性的处理
left = left + 1;
}
}
// left及其后面的数据顺序向后移动,并在left位置插入
for (int j = i; j > left; j--) {
target[j] = target[j-1];
}
target[left] = temp;
}
}
}
return target;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42