直接插入排序
思想:
第一个数默认有序。从第二个数开始,从后往前扫描。若取出数 < 当前数,则指针不断前移,直到取出数 >= 当前数时,把取出数插入到当前位置。
public static int[] insertSort(int[] arr){
int pre,cur; //pre:当前数指针,cur: 取出数
for(int i = 1; i < arr.length; i++){ //从第二个数(索引为1)开始遍历
pre = i - 1;
cur = arr[i];
while(pre >= 0 && arr[pre] > cur){ //防止索引越界,并当当前数 > 取出数时
arr[pre+1] = arr[pre];
--pre;
}
arr[pre+1] = cur;
}
return arr;
}
以下过程具体说明了两个数是怎么实现有序的
pre
index: 1 2
data: 5 2 cur = 2
->
arr[pre+1] = arr[pre]
pre
index: 1 2
data: 5 5 cur = 2
->
--pre
pre
index: 0 1 2
data: 5 5 cur = 2
->
arr[pre+1] = cur;
pre
index: 0 1 2
data: 2 5 cur = 2
复杂度分析:
最好的情况是序列已完全有序,则程序只for循环一次,while循环不执行,复杂度为O(n);最坏的情况是序列完全倒序,for循环扫描一轮,while循环也从后往前全部两两交换了一轮,复杂度为O(n^2); 平均复杂度为O(n^2);
稳定性:
该排序算法不改变相同元素的相对位置,所以是稳定的。
直接插入第二种写法
public void insert(int[] arr){
for(int i = 1; i < arr.length; i++){
for(int j = i; j > 0; j--){
if(arr[j] < arr[j-1])
swap(arr,j,j-1);
}
}
}
二分插入排序
思想:
利用二分查找,能够更快的定位元素,不用一个个向前移动寻找了
public void insert(int[] arr){
for(int i = 1; i < arr.length; i++){
int key = arr[i];
int low = BinarySearch(arr,key,0,i-1);
for(int j = i-1; j >= low; j--)
arr[j+1] = arr[j];
arr[low] = key;
}
}
public int BinarySearch(int[] arr, int key, int low, int high){
while(low <= high){
int mid = ((high-low) >> 1) + low;
if(key < arr[mid])
high = mid - 1;
else
low = mid + 1;
}
return low;
}
希尔排序(本质是直接插入排序)
思想:
使用了一个增量,初始值为一般设为Math.floor(arr.length / 2);该增量把序列划分为几个gap,对跨gap的数进行直接插入排序,而后gap为 Math.floor(gap / 2),直到最后一轮 gap=1
public static int[] insertSort(int[] arr){
for(int gap = (int)Math.floor(arr.length / 2); gap > 0; gap = Math.floor(gap / 2)){ //设置增量,划分gap
for(int i = gap; i < arr.length; i++){ //对每个gap的数进行直接插入排序
int j = i;
int cur = arr[j]; //arr[j]是相对位置在后面的数,arr[j-gap]是相对位置在前面的数
while(j-gap >= 0 && arr[j-gap] > cur){
arr[j] = arr[j-gap];
j = j - gap;
}
arr[j] = cur;
}
}
return arr;
}
复杂度分析:
外层for循环不是在做循环。内层for循环从初始gap值处开始遍历数组,复杂度情况与直接插入排序一致,若序列已有序,复杂度为O(n);若完全倒序,复杂度为O(n^2);平均复杂度问题未解决,尚为数学界难题。
稳定性:
该排序算法改变相同元素的相对位置,所以是不稳定的。