一、折半插入排序
折半插入排序的算法思想:
算法的基本过程:
(1)计算 0 ~ i-1 的中间点,用 i 索引处的元素与中间值进行比较,如果 i 索引处的元素大,说明要插入的这个元素应该在中间值和刚加入i索引之间,反之,就是在刚开始的位置 到中间值的位置,这样很简单的完成了折半;
(2)在相应的半个范围里面找插入的位置时,不断的用(1)步骤缩小范围,不停的折半,范围依次缩小为 1/2 1/4 1/8 …快速的确定出第 i 个元素要插在什么地方;
(3)确定位置之后,将整个序列后移,并将元素插入到相应位置。
折半插入排序升序排列
public static void main(String[] args) {
int source[] = new int[] { 12, 15, 9, 14, 4, 18, 23, 6 };
binarySort(source);
}
public static void binarySort(int[] source) {
int i, j; /*i,j均为循环变量*/
/*low代表要与Array[i]进行比较的有序区间的第一个元素所在位置。
high代表要与Array[i]进行比较的有序区间的最后一个元素所在位置。
mid充当比较区间的中点*/
int high, low, mid;
int temp; /*temp用来存储当前待排序的数据*/
for (i = 1; i < source.length; i++) {
low = 0;// 查找区上界
high = i - 1;// 查找区下界
temp = source[i];//将当前待插入记录保存在临时变量中
while (low <= high) {
// 找出中间值 向下取整
// mid = (low + high) / 2;
mid = (low + high) >> 1;
//如果待插入记录比中间记录小
if (temp < source[mid]) {
// 插入点在低半区
high = mid - 1;
} else {
// 插入点在高半区
low = mid + 1;
}
}
//将前面所有大于当前待插入记录的记录后移
for (j = i - 1; j >= low; j--) {
source[j + 1] = source[j];
}
// for(int j=i;j>low;j--)//把temp放到找到的位 {
// a[j]=a[j-1];
// }
//将待插入记录回填到正确位置.
source[low] = temp;
System.out.print("第" + i + "趟排序:");
System.out.println("soutce==="+ JSON.toJSONString(source));
}
}
执行结果:
第1趟排序:[12,15,9,14,4,18,23,6]
第2趟排序:[9,12,15,14,4,18,23,6]
第3趟排序:[9,12,14,15,4,18,23,6]
第4趟排序:[4,9,12,14,15,18,23,6]
第5趟排序:[4,9,12,14,15,18,23,6]
第6趟排序:[4,9,12,14,15,18,23,6]
第7趟排序:[4,6,9,12,14,15,18,23]
折半插入排序降序排列
public static void main(String[] args) {
int source[] = new int[] { 12, 15, 9, 14, 4, 18, 23, 6 };
binarySort(source);
}
public static void binarySort(int[] source) {
int i, j; /*i,j均为循环变量*/
/*low代表要与Array[i]进行比较的有序区间的第一个元素所在位置。
high代表要与Array[i]进行比较的有序区间的最后一个元素所在位置。
mid充当比较区间的中点*/
int high, low, mid;
int temp; /*temp用来存储当前待排序的数据*/
for (i = 1; i < source.length; i++) {
low = 0;// 查找区上界
high = i - 1;// 查找区下界
temp = source[i];//将当前待插入记录保存在临时变量中
while (low <= high) {
// 找出中间值 向下取整
// mid = (low + high) / 2;
mid = (low + high) >> 1;
//如果待插入记录比中间记录大
if (temp >= source[mid]) {
// 插入点在低半区
high = mid - 1;
} else {
// 插入点在高半区
low = mid + 1;
}
}
//将前面所有大于当前待插入记录的记录后移
for (j = i - 1; j >= low; j--) {
source[j + 1] = source[j];
}
// for(int j=i;j>low;j--)//把temp放到找到的位 {
// a[j]=a[j-1];
// }
//将待插入记录回填到正确位置.
source[low] = temp;
System.out.print("第" + i + "趟排序:");
System.out.println("soutce==="+ JSON.toJSONString(source));
}
}
图示:以i=4时为例:
第一步
第二步
第三步:
第四步:
第五步:
第六步:
第七步:
二、希尔排序
算法思想:
希尔排序是将待排序的数组元素 按下标的一定增量分组 ,分成多个子序列,然后对各个子序列进行直接插入排序算法排序;然后依次缩减增量再进行排序,直到增量为1时,进行最后一次直接插入排序,排序结束。
希尔本人给出的步长序列是n/2^k(其中n为数组长度),例如要对下面的数组进行排序,n为16,步长序列是{1, 2, 4, 8}
一种分组排序的方法,以此对数组进行一定的“粗略调整”。
所谓分组,就是让元素两两一组,同组两个元素之间的跨度,都是数组总长度的一半,也就是跨度为4。
如图所示,元素5和元素9一组,元素8和元素2一组,元素6和元素1一组,元素3和元素7一组,一共4组。
接下来,我们让每组元素进行独立排序,排序方式用直接插入排序即可。由于每一组的元素数量很少,只有两个,所以插入排序的工作量很少。每组排序完成后的数组如下:
这样一来,仅仅经过几次简单的交换,数组整体的有序程度得到了显著提高,使得后续再进行直接插入排序的工作量大大减少。这种做法,可以理解为对原始数组的“粗略调整”。
但是这样还不算完,我们可以进一步缩小分组跨度,重复上述工作。把跨度缩小为原先的一半,也就是跨度为2,重新对元素进行分组:
如图所示,元素5,1,9,6一组,元素2,3,8,7一组,一共两组。
接下来,我们继续让每组元素进行独立排序,排序方式用直接插入排序即可。每组排序完成后的数组如下:
此时,数组的有序程度进一步提高,为后续将要进行的排序铺平了道路。
最后,我们把分组跨度进一步减小,让跨度为1,也就等同于做直接插入排序。经过之前的一系列粗略调整,直接插入排序的工作量减少了很多,排序结果如下:
让我们重新梳理一下分组排序的整个过程:
像这样逐步分组进行粗调,再进行直接插入排序的思想,就是希尔排序
参考样例:
/**
* 希尔排序 针对有序序列在插入时采用交换法
* @param arr
*/
public static void sort(int []arr){
//增量gap,并逐步缩小增量
for(int gap=arr.length/2;gap>0;gap/=2){
//从第gap个元素,逐个对其所在组进行直接插入排序操作
for(int i=gap;i<arr.length;i++){
int j = i;
while(j-gap>=0 && arr[j]<arr[j-gap]){
//插入排序采用交换法
swap(arr,j,j-gap);
j-=gap;
}
}
}
}
参考
https://www.pianshen.com/article/7921137952/
https://www.jianshu.com/p/d730ae586cf3