希尔排序是对插入排序的一种改进
本质是:
- 将数组按照 h 的跨度进行分割
- 分割后将跨度为 h 的数组值进行交换
- 然后向前移动跨度为 h 的距离,再交换跨度为 h 距离值
- 缩小跨度,对跨度内的数据进行插入排序操作
简单说分两步:
- 让数据值大的尽量往后排,减少和前面数据的位置交换次数(减少了内存的操作)
- 缩小 跨度 h 用插入排序进行 h 跨度内的排序
接下来以数组 nums = [6, 5 , 4, 3, 2, 1] 举例说明希尔排序的具体过程,nums 按照倒序排列是为了可以让大家简单,清晰的理解按照跨度进行数据交换,完整算法如下:
public static int[] shell(int[] nums) {
int n = nums.length;
int h = 1;
// 计算跨度 h
while (h < n / 3) {
h = 3 * h + 1;
}
while (h >= 1) {
// 尽量让大的数值往后排
for (int i = h; i < n; i++) {
// 对 h 跨度内的数据进行插入排序操作
for (int j = i; j >= h; j -= h) {
if (nums[j] < nums[j - h]) {
int temp = nums[j];
nums[j] = nums[j - h];
nums[j - h] = temp;
}
}
}
// 缩小跨度
h = h / 3;
}
return nums;
}
1、首先将数组按照 h = 3 * h + 1 计算得到跨度 h = 4;
while (h < n / 3) { h = 3 * h + 1; }
这里可以理解为,设置了一个跨度因子数为 3,根据数据规模n 的大小,可以动态的调整跨度区间,n 越大 跨度 h 越大,为了尽可能减少交换的次数。
2、进入第一次while 循环
- nums = [6, 5 , 4, 3, 2, 1]
- 外层循环初始条件为:i = h = 4, n = 6
- 内层第一次循环:循环初始条件为:j = i = 4, h = 4, j >= h,比较 nums[ j = 4] = 2,nums[ j - h = 0] = 6,2 < 6 交换位置,6和2之间的跨度为4
此时 nums 如下:
nums[0] = 2,nums[1] = 5,nums[2] = 4,nums[3] = 3,nums[4] = 6, nums[5] = 1- 内层循环结束后 j = j - h = 0; 退出内层循环
3、外层第二次循环初始条件为:i 的初始值为4,步骤2完成后进行 i++ 操作,i = 5
- nums = [6, 5 , 4, 3, 2, 1]
- 内层循环条件为:j = i = 5,h = 4,j >= h (5 >= 4),比较 nums[ j = 5 ] = 1, nums[ j - h = 1 ] = 5,1 < 5 交换位置,5和1 之间的跨度为4
此时 nums 如下:
nums[0] = 2,nums[1] = 1,nums[2] = 4,nums[3] = 3,nums[4] = 6,nums[5] = 5- 内层循环条件是 j >= h,h = 4,进行第一轮循环后,j = j - h = 5 - 4 = 1,不满足循环条件,内外层for 循环结束
- 缩小跨度 h
h = h / 3;
上述步骤总结:从 ”标红“ 的描述大家可以看到,按照计算出的跨度 h 值 4,进行跨度区间的数据交换,尽可能的让大的数据往后面排,减少数据交换的次数,也就是减少了内存的操作次数,提升了性能。
4、第一次 while 循环结束,h = h / 3 = 4 / 3 = 1,进行第二轮 while 循环
4.1、第一次外循环条件为:i = h = 1
- nums = [6, 5 , 4, 3, 2, 1]
- 第一次内循环初始条件为:j = i = 1,j >= h(h = 1),比较 nums[ j ] = 1 和 nums[ j - h = 0] = 2 ,1 < 2 交换位置,1和2之间的跨度为 1
此时 nums 如下:
nums[0] = 1, nums[1] = 2,nums[2] = 4,nums[3] = 3,nums[4] = 6,nums[5] = 5
- 最后 j = j - h = 0 不满足循环,内层循环退出
4.2、第二次外循环条件为:i++ 后 i = 2
- 第一次内循环条件为:j = i = 2,j >= h (h = 1),比较 nums[ j = 2] = 4 和 nums[ j - h = 1] = 2,4 不小于2,第一次内循环结束
- 第二次内循环条件为:j = i = 1, j >= h (h = 1), 比较 nums[ j ] = 2 和 nums[ j - h = 0] = 1,2 不小于 1,第二次内循环结束
在步骤4.1,因为nums 索引 2 之前的数据都排序好了,所以插入排序在这里不满足排序条件,什么都不做
4.3、第三次外循环条件为:i++ 后 i = 3
- nums = [6, 5 , 4, 3, 2, 1]
- 第一次内循环条件为:j = i = 3,j >= h (h = 1), 比较 nums[ j ] = 3 和 nums[ j - h = 2] = 4,3 < 4 交换位置,3和4之间的跨度为 1
- 此时 nums 如下:
nums[0] = 1,nums[1] = 2,nums[2] = 3, nums[3] = 4,nums[4] = 6,nums[5] = 5- 之后和上面的步骤 4.2 一样,根据跨度为1,“逆向” 依次比较 j 前面的数,这里大家应该可以看到和插入排序的操作是没什么区别的
- 因为不满足交换条件,所以内层循环结束
4.4、第四次外循环条件为:i++ 后 i = 4
- 第一次内循环条件为:j = i = 4, j >= h (h = 1), 比较 nums[ j ] = 6 和 nums[ j - h = 3] = 4,6 不小于 4 后续循环也不会满足交换条件
- 循环退出
4.5、第五次外循环条件为:i++ 后 i = 5
- nums = [6, 5 , 4, 3, 2, 1]
- 第一次内循环条件为:j = i = 5, j >= h (h = 1), 比较 nums[ j ] = 5 和 nums[ j - h = 4] = 6 ,5 < 6 交换位置,5和5之间跨度为 1
此时 nums 如下:
nums[0] = 1,nums[1] = 2,nums[2] = 3,nums[3] = 4,nums[4] = 5,nums[5] = 6- 排序完成,后续的循环不满足交换条件,退出while 循环
上述总结:每次进行while 循环都是在“缩小”跨度 h ,然后对 “跨度h” 内的数据按照插入排序算法进行排序
😊哈哈,相信聪明的你看完瓜瓜的文章,对希尔排序算法应该有更深的理解了吧~,创作不易,如果觉得有帮助到你,希望点赞收藏哦!若有错误,欢迎指正,非常感谢!!