尚硅谷算法与数据结构学习笔记07 -- 排序算法2
Posted exodus3
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了尚硅谷算法与数据结构学习笔记07 -- 排序算法2相关的知识,希望对你有一定的参考价值。
6、希尔排序
6.1、简单插入排序问题
-
我们看简单的插入排序可能存在的问题,数组 arr = 2, 3, 4, 5, 6, 1 这时需要插入的数 1(最小),简单插入排序的过程如下
-
结论: 当需要插入的数是较小的数时, 后移的次数明显增多, 对效率有影响
2,3,4,5,6,6
2,3,4,5,5,6
2,3,4,4,5,6
2,3,3,4,5,6
2,2,3,4,5,6
1,2,3,4,5,6
6.2、希尔排序基本介绍
- 希尔排序是希尔(Donald Shell) 于 1959 年提出的一种排序算法。 希尔排序也是一种插入排序, 它是简单插入排序经过改进之后的一个更高效的版本, 也称为缩小增量排序。
6.3、希尔排序基本思想
- 希尔排序按照增量将数组进行分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至 1 时,整个文件恰被分成一组,算法便终止
6.4、希尔排序图解(交换法)
-
第一次:gap = arr.length/5 = 5 , 将数组分为五组,每个数组元素的索引相差 5
- 如何完成第一次的排序?
- 仔细想想,我们需要用一次循环将每组中的元素排序
- 总共有五组,我们又需要一次循环
- 所以完成每次排序,需要两层循环
- 程序代码如下,把 i ,j 都看作是辅助指针:
- i 与 j 配合使用,可以将指针从数组第一个元素,移动至最后一个元素,目的:把数组遍历一遍
- j 与 i 配合使用,每次都从数组索引 i 处往前遍历,每次向前移动 gap 个位置,然后进行交换(冒泡排序的意思):看看前面的元素有没有比我的值大,如果前面的元素比我的值大,我就要和他交换位置,跑到前面去
- 如何完成第一次的排序?
// 希尔排序的第1轮排序
// 因为第1轮排序,是将10个数据分成了 5组
for (int i = 5; i < arr.length; i++)
// 遍历各组中所有的元素(共5组,每组有2个元素), 步长5
for (int j = i - 5; j >= 0; j -= 5)
// 如果当前元素大于加上步长后的那个元素,说明交换
if (arr[j] > arr[j + 5])
temp = arr[j];
arr[j] = arr[j + 5];
arr[j + 5] = temp;
-
第二次:gap = gap /2 = 2; , 将数组分为两组,每个数组元素的索引相差 2
- 第一组:
- i = 2 时,数组从索引 2 处往前遍历,间隔为 2 :将 arr[0]、arr[2] 排序
- i = 4 时,数组从索引 4 处往前遍历,间隔为 2 :将 arr[0]、arr[2]、arr[4] 排序
- i = 6 时,数组从索引 6 处往前遍历,间隔为 2 :将 arr[0]、arr[2]、arr[4]、arr[6] 排序
- i = 8 时,数组从索引 8 处往前遍历,间隔为 2 :将 arr[0]、arr[2]、arr[4]、arr[6]、arr[8] 排序
- 第二组:
- i = 3 时,数组从索引 3 处往前遍历,间隔为 2 :将 arr[1]、arr[3] 排序
- i = 5 时,数组从索引 5 处往前遍历,间隔为 2 :将 arr[1]、arr[3]、arr[5] 排序
- i = 7 时,数组从索引 7 处往前遍历,间隔为 2 :将 arr[1]、arr[3]、arr[5]、arr[7] 排序
- i = 9 时,数组从索引 9 处往前遍历,间隔为 2 :将 arr[1]、arr[3]、arr[5]、arr[7]、arr[9] 排序
- 第一组:
// 希尔排序的第2轮排序
// 因为第2轮排序,是将10个数据分成了 5/2 = 2组
for (int i = 2; i < arr.length; i++)
// 遍历各组中所有的元素(共5组,每组有2个元素), 步长5
for (int j = i - 2; j >= 0; j -= 2)
// 如果当前元素大于加上步长后的那个元素,说明交换
if (arr[j] > arr[j + 2])
temp = arr[j];
arr[j] = arr[j + 2];
arr[j + 2] = temp;
System.out.println("希尔排序2轮后=" + Arrays.toString(arr));
- 第三次:gap = gap /2 = 1; , 将数组分为一组,每个数组元素的索引相差 1 ,对于交换法而言,这就是异常冒泡排序
- i = 1 时,数组从索引 1 处往前遍历,间隔为 1 :将 arr[0]、arr[1] 排序
- i = 2 时,数组从索引 2 处往前遍历,间隔为 1 :将 arr[0]、arr[1]、arr[2] 排序
- i = 3 时,数组从索引 3 处往前遍历,间隔为 1 :将 arr[0]、arr[1]、arr[2]、arr[3] 排序
- …
// 希尔排序的第3轮排序
// 因为第3轮排序,是将10个数据分成了 2/2 = 1组
for (int i = 1; i < arr.length; i++)
// 遍历各组中所有的元素(共5组,每组有2个元素), 步长5
for (int j = i - 1; j >= 0; j -= 1)
// 如果当前元素大于加上步长后的那个元素,说明交换
if (arr[j] > arr[j + 1])
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
System.out.println("希尔排序3轮后=" + Arrays.toString(arr));
-
总结:每次使用循环改变 gap 的值(初始值:数组大小/2 ,之后:gap = gap/2),然后在改变 gap 的循环中嵌套上面的双层 for 循环
-
改变 gap :for (int gap = arr.length / 2; gap > 0; gap /= 2)
-
内层循环:实现对每组数组的排序
for (int i = gap; i < arr.length; i++)
// 遍历各组中所有的元素(共gap组,每组有?个元素), 步长gap
for (int j = i - gap; j >= 0; j -= gap) -
希尔排序伪代码
for (int gap = arr.length / 2; gap > 0; gap /= 2) for (int i = gap; i < arr.length; i++) // 遍历各组中所有的元素(共gap组,每组有?个元素), 步长gap for (int j = i - gap; j >= 0; j -= gap) // 对每组进行冒泡排序
-
6.5、代码实现
6.5.1、理解希尔排序(交换法)
- 理解基于交换法的希尔排序
public class ShellSort
public static void main(String[] args)
int[] arr = 8, 9, 1, 7, 2, 3, 5, 4, 6, 0 ;
shellSort(arr);
// 使用逐步推导的方式来编写希尔排序
// 希尔排序时, 对有序序列在插入时采用交换法,
// 思路(算法) ===> 代码
public static void shellSort(int[] arr)
int temp = 0;
// 希尔排序的第1轮排序
// 因为第1轮排序,是将10个数据分成了 5组
for (int i = 5; i < arr.length; i++)
// 遍历各组中所有的元素(共5组,每组有2个元素), 步长5
for (int j = i - 5; j >= 0; j -= 5)
// 如果当前元素大于加上步长后的那个元素,说明交换
if (arr[j] > arr[j + 5])
temp = arr[j];
arr[j] = arr[j + 5];
arr[j + 5] = temp;
System.out.println("希尔排序1轮后=" + Arrays.toString(arr));
// 希尔排序的第2轮排序
// 因为第2轮排序,是将10个数据分成了 5/2 = 2组
for (int i = 2; i < arr.length; i++)
// 遍历各组中所有的元素(共5组,每组有2个元素), 步长5
for (int j = i - 2; j >= 0; j -= 2)
// 如果当前元素大于加上步长后的那个元素,说明交换
if (arr[j] > arr[j + 2])
temp = arr[j];
arr[j] = arr[j + 2];
arr[j + 2] = temp;
System.out.println("希尔排序2轮后=" + Arrays.toString(arr));
// 希尔排序的第3轮排序
// 因为第3轮排序,是将10个数据分成了 2/2 = 1组
for (int i = 1; i < arr.length; i++)
// 遍历各组中所有的元素(共5组,每组有2个元素), 步长5
for (int j = i - 1; j >= 0; j -= 1)
// 如果当前元素大于加上步长后的那个元素,说明交换
if (arr[j] > arr[j + 1])
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
System.out.println("希尔排序3轮后=" + Arrays.toString(arr));
- 程序运行结果
希尔排序1轮后=[3, 5, 1, 6, 0, 8, 9, 4, 7, 2]
希尔排序2轮后=[0, 2, 1, 4, 3, 5, 7, 6, 9, 8]
希尔排序3轮后=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
6.5.2、编写希尔排序(交换法)
- 编写基于交换法的希尔排序算法
public class ShellSort
public static void main(String[] args)
int[] arr = 8, 9, 1, 7, 2, 3, 5, 4, 6, 0 ;
shellSort(arr);
// 使用逐步推导的方式来编写希尔排序
// 希尔排序时, 对有序序列在插入时采用交换法,
// 思路(算法) ===> 代码
public static void shellSort(int[] arr)
int temp = 0;
int count = 0;
// 根据前面的逐步分析,使用循环处理
for (int gap = arr.length / 2; gap > 0; gap /= 2)
for (int i = gap; i < arr.length; i++)
// 遍历各组中所有的元素(共gap组,每组有?个元素), 步长gap
for (int j = i - gap; j >= 0; j -= gap)
// 如果当前元素大于加上步长后的那个元素,说明交换
if (arr[j] > arr[j + gap])
temp = arr[j];
arr[j] = arr[j + gap];
arr[j + gap] = temp;
System.out.println("希尔排序第" + (++count) + "轮 =" + Arrays.toString(arr));
- 程序运行结果
希尔排序第1轮 =[3, 5, 1, 6, 0, 8, 9, 4, 7, 2]
希尔排序第2轮 =[0, 2, 1, 4, 3, 5, 7, 6, 9, 8]
希尔排序第3轮 =[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
6.5.3、测试希尔排序(交换法)性能
- 测试基于交换法的希尔排序算法性能
public class ShellSort
public static void main(String[] args)
// 创建要给80000个的随机的数组
int[] arr = new int[80000];
for (int i = 0; i < 80000; i++)
arr[i] = (int) (Math.random() * 8000000); // 生成一个[0, 8000000) 数
System.out.println("排序前");
Date date1 = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date1Str = simpleDateFormat.format(date1);
System.out.println("排序前的时间是=" + date1Str);
shellSort(arr); // 交换式
Date data2 = new Date();
String date2Str = simpleDateFormat.format(data2);
System.out.println("排序前的时间是=" + date2Str);
// 使用逐步推导的方式来编写希尔排序
// 希尔排序时, 对有序序列在插入时采用交换法,
// 思路(算法) ===> 代码
public static void shellSort(int[] arr)
int temp = 0;
int count = 0;
// 根据前面的逐步分析,使用循环处理
for (int gap = arr.length / 2; gap > 0; gap /= 2)
for (int i = gap; i < arr.length; i++)
// 遍历各组中所有的元素(共gap组,每组有?个元素), 步长gap
for (int j = i - gap; j >= 0; j -= gap)
// 如果当前元素大于加上步长后的那个元素,说明交换
if (arr[j] > arr[j + gap])
temp = arr[j];
arr[j] = arr[j + gap];
arr[j + gap] = temp;
- 程序运行结果
排序前
排序前的时间是=2020-07-16 10:22:27
排序前的时间是=2020-07-16 10:22:33
- 分析:由于使用交换法实现希尔排序算法,所以基于交换法的希尔排序算法比简单选择排序算法更慢,所以我们一定要编写基于插入法的希尔排序算法
6.5.4、编写希尔排序(插入法)
- 编写基于插入法的希尔排序算法:
- 记录当前位置的元素值 int temp = arr[j]; ,从当前元素前一个位置开始,往前寻找,每次移动 gap 个距离
- 如果 temp < arr[j - gap] :
- 将数组元素后移,腾出插入空间:arr[j] = arr[j - gap];
- 然后继续往前找:j -= gap;
- 如果 temp > arr[j - gap] ,找到插入位置,执行插入 arr[j] = temp; ,因为在上一步已经腾出了插入空间,并且将指针 j 前移,所以可直接插入
- 如果 找到数组最前面还是没有找到插入位置:j - gap < 0 ,则证明 temp 需要插入在数组最前面
- 如果 temp < arr[j - gap] :
- 仅仅就是将之前交换法的冒泡操作替换成了插入操作
- 记录当前位置的元素值 int temp = arr[j]; ,从当前元素前一个位置开始,往前寻找,每次移动 gap 个距离
public class ShellSort
public static void main(String[] args)
int[] arr = 8, 9, 1, 7, 2, 3, 5, 4, 6, 0 ;
System.out.println("排序前");
System.out.println(Arrays.toString(arr));
shellSort(arr);
System.out.println("排序前");
System.out.println(Arrays.toString(arr));
// 对交换式的希尔排序进行优化->移位法
public static void shellSort(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;
int temp = arr[j];
if (arr[j] < arr[j - gap])
while (j - gap >= 0 && temp < arr[j - gap])
// 移动
arr[j] = arr[j - gap];
j -= gap;
// temp 比 arr[j - gap] 大,所以需要插入在 j 的位置
arr[j] = temp;
- 程序运行结果
排序前
[8, 9, 1, 7, 2, 3, 5, 4, 6, 0]
排序前
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9尚硅谷算法与数据结构学习笔记06 -- 排序算法