尚硅谷算法与数据结构学习笔记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 需要插入在数组最前面
    • 仅仅就是将之前交换法的冒泡操作替换成了插入操作
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]

6.5.5、测试希尔排序(插入法)性能

  • 测试基于插入法的希尔排序算法性能
以上是关于尚硅谷算法与数据结构学习笔记07 -- 排序算法2的主要内容,如果未能解决你的问题,请参考以下文章

尚硅谷算法与数据结构学习笔记06 -- 排序算法

尚硅谷算法与数据结构学习笔记04 -- 栈

尚硅谷算法与数据结构学习笔记02 -- 单链表

尚硅谷算法与数据结构学习笔记05 -- 递归

尚硅谷算法与数据结构学习笔记01 -- 稀疏数组和队列

尚硅谷算法与数据结构学习笔记03 -- 双向链表