数据结构与算法:排序算法(一)

前言

本文主要讲解排序算法


数据结构与算法文章列表

数据结构与算法文章列表: 点击此处跳转查看


目录


(一)排序算法的介绍

排序也称排序算法(Sort Algorithm),排序是将一组数据,依指定的顺序进行排列的过程

常见的排序算法有:冒泡排序、选择排序、插入排序、希尔排序、快速排序、归并排序、基数排序等


(二)冒泡排序

冒泡排序(Bubble Sort)也是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端

(1)冒泡排序思路:
在这里插入图片描述

(2)动画演示:
在这里插入图片描述
动画解释:

将天平放在序列的右端,并比较天平左右的数字

在这种情况下我们比较 3 和 8

比较后如果右边的数字较小,则被交换

因为 8 大于 3 ,所以数字不用交换

比较完成后,将天平向左移动一个位置,比较数字

因为 3 大于 2 ,所以数字不用交换

比较完成后,逐一移动天平,比较数字

此时 2 小于 4 ,所以左右的数字互相交换

重复同样的操作,直到天平移动到左端

。。。。。。

天平到达左端

经过上述的操作,数列中最小的数字已经移动到左端

将天平返回右端

重复相同的操作,直到所有数字都被排序

。。。。。。

吐完泡泡了


(3)代码
代码:(推导冒泡排序过程)

public class BubbleSort {
    
    
     public static void main(String[] args) {
    
    
          int arr[] = {
    
     3, 9, -1, 10, 20 };
          int temp = 0;
          
          // 第一趟排序,就是将第一大的数排在倒数第一位
          for (int j = 0; j < arr.length - 1 - 0; j++) {
    
    
              // 如果前面的数比后面的数大,则交换
              if (arr[j] > arr[j + 1]) {
    
    
                   temp = arr[j];
                   arr[j] = arr[j + 1];
                   arr[j + 1] = temp;
              }
          }
          System.out.println("第一趟排序后的数组");
          System.out.println(Arrays.toString(arr));

          // 第二趟排序,就是将第二大的数排在倒数第二位
          for (int j = 0; j < arr.length - 1 - 1; j++) {
    
    
              // 如果前面的数比后面的数大,则交换
              if (arr[j] > arr[j + 1]) {
    
    
                   temp = arr[j];
                   arr[j] = arr[j + 1];
                   arr[j + 1] = temp;
              }
          }
          System.out.println("第二趟排序后的数组");
          System.out.println(Arrays.toString(arr));

          // 第三趟排序,就是将第三大的数排在倒数第三位
          for (int j = 0; j < arr.length - 1 - 2; j++) {
    
    
              // 如果前面的数比后面的数大,则交换
              if (arr[j] > arr[j + 1]) {
    
    
                   temp = arr[j];
                   arr[j] = arr[j + 1];
                   arr[j + 1] = temp;
              }
          }
          System.out.println("第三趟排序后的数组");
          System.out.println(Arrays.toString(arr));

          // 第四趟排序,就是将第4大的数排在倒数第4位
          for (int j = 0; j < arr.length - 1 - 3; j++) {
    
    
              // 如果前面的数比后面的数大,则交换
              if (arr[j] > arr[j + 1]) {
    
    
                   temp = arr[j];
                   arr[j] = arr[j + 1];
                   arr[j + 1] = temp;
              }
          }
          System.out.println("第四趟排序后的数组");
          System.out.println(Arrays.toString(arr));
     }
}

结果:

第一趟排序后的数组
[3, -1, 9, 10, 20]
第二趟排序后的数组
[-1, 3, 9, 10, 20]
第三趟排序后的数组
[-1, 3, 9, 10, 20]
第四趟排序后的数组
[-1, 3, 9, 10, 20]

代码:(简化后的)

public class BubbleSort {
    
    
     public static void main(String[] args) {
    
    
          int arr[] = {
    
    3, 9, -1, 10, 20};
          // 测试冒泡排序
          bubbleSort(arr);
     }

     // 将前面额冒泡排序算法,封装成一个方法
     public static void bubbleSort(int[] arr) {
    
    
          // 冒泡排序 的时间复杂度 O(n^2), 自己写出
          int temp = 0; // 临时变量
          boolean flag = false; // 标识变量,表示是否进行过交换
          for (int i = 0; i < arr.length - 1; i++) {
    
    
              for (int j = 0; j < arr.length - 1 - i;  j++) {
    
    
                   // 如果前面的数比后面的数大,则交换
                   if (arr[j] > arr[j + 1]) {
    
    
                        flag = true;
                        temp = arr[j];
                        arr[j] = arr[j + 1];
                        arr[j + 1] = temp;
                   }
              }
              System.out.println("第" + (i + 1) + "趟排序后的数组");
              System.out.println(Arrays.toString(arr));
              if (!flag) {
    
     // 在一趟排序中,一次交换都没有发生过
                   break;
              } else {
    
    
                   flag = false; // 重置flag!!!, 进行下次判断
              }
          }
     }
}

结果:

第一趟排序后的数组
[3, -1, 9, 10, 20]
第二趟排序后的数组
[-1, 3, 9, 10, 20]
第三趟排序后的数组
[-1, 3, 9, 10, 20]
第四趟排序后的数组
[-1, 3, 9, 10, 20]

代码:(冒泡排序速度测试),在本人机器上面需要花费17-18s

import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;

public class BubbleSort {
    
    
     public static void main(String[] args) {
    
    
          // 测试一下冒泡排序的速度O(n^2), 给80000个数据,测试
          // 创建要给80000个的随机的数组
          int[] arr = new int[80000];
          for (int i = 0; i < 80000; i++) {
    
    
              arr[i] = (int) (Math.random() * 8000000);  // 生成一个[0, 8000000)数
          }

          Date data1 = new Date();
          SimpleDateFormat simpleDateFormat = new  SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
          String date1Str =  simpleDateFormat.format(data1);
          System.out.println("排序前的时间是=" +  date1Str);

          // 测试冒泡排序
          bubbleSort(arr);

          Date data2 = new Date();
          String date2Str =  simpleDateFormat.format(data2);
          System.out.println("排序后的时间是=" +  date2Str);
     }

     // 将前面额冒泡排序算法,封装成一个方法
     public static void bubbleSort(int[] arr) {
    
    
          // 冒泡排序 的时间复杂度 O(n^2), 自己写出
          int temp = 0; // 临时变量
          boolean flag = false; // 标识变量,表示是否进行过交换
          for (int i = 0; i < arr.length - 1; i++) {
    
    
              for (int j = 0; j < arr.length - 1 - i;  j++) {
    
    
                   // 如果前面的数比后面的数大,则交换
                   if (arr[j] > arr[j + 1]) {
    
    
                        flag = true;
                        temp = arr[j];
                        arr[j] = arr[j + 1];
                        arr[j + 1] = temp;
                   }
              }
              if (!flag) {
    
     // 在一趟排序中,一次交换都没有发生过
                   break;
              } else {
    
    
                   flag = false; // 重置flag!!!, 进行下次判断
              }
          }
     }
}

结果:

排序前的时间是=2020-12-06 11:14:15
排序后的时间是=2020-12-06 11:14:32

(三)选择排序

(1)基本介绍
选择排序是一种简单直观的排序算法,无论什么数据进去都是 O(n²) 的时间复杂度。所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了

(2)选择排序思想
选择排序(select sorting)也是一种简单的排序方法。它的基本思想是:
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
重复第二步,直到所有元素均排序完毕。

例如:
第一次从 arr[0]~arr[n-1]中选取最小值,与 arr[0]交换
第二次从 arr[1]~arr[n-1]中选取最小值,与 arr[1]交换
第三次从 arr[2]~arr[n-1]中选取最小值,与 arr[2]交换,…
第 i 次从 arr[i-1]~arr[n-1]中选取最小值,与 arr[i-1]交换,…
第 n-1 次从 arr[n-2]~arr[n-1]中选取最小值,与 arr[n-2]交换
总共通过 n-1 次,得到一个按排序码从小到大排列的有序序列
在这里插入图片描述
(3)选择排序动画演示
在这里插入图片描述
动画解释:

线性搜索数列并找到最小值,此时找到了为 2

将最小值替换为数列中左端的数字,即将 2 与 4 进行交换

此时 2 已经排序好

继续线性搜索剩余数列找到最小值,此时找到了 3

将最小值替换为数列中左端的数字,即将 3 与 4 进行交换

此时 2 与 3 已经排序好

继续线性搜索剩余数列找到最小值,此时找到了 4

如果最小值已经在左端,那么不执行任何操作,所以此时不做任何处理

此时 2 、 3 、 4 已经排序好

重复相同操作,直到所有数字都被排序


(4)代码
代码:(推导选择排序过程)

import java.util.Arrays;

// 选择排序
public class SelectSort {
    
    

    public static void main(String[] args) {
    
    
        int[] arr = {
    
     101, 34, 119, 1 };

        System.out.println("排序前");
        System.out.println(Arrays.toString(arr));

        selectSort(arr);

        System.out.println("排序后");
        System.out.println(Arrays.toString(arr));
    }


    // 选择排序
    public static void selectSort(int[] arr) {
    
    
        // 使用逐步推导的方式来,讲解选择排序
        // 第1轮
        // 原始的数组 : 101, 34, 119, 1
        // 第一轮排序 : 1, 34, 119, 101
        // 算法 先简单--》 做复杂, 就是可以把一个复杂的算法,拆分成简单的问题-》逐步解决

        // 第1轮
        int minIndex = 0;
        int min = arr[0];
        for (int j = 0 + 1; j < arr.length; j++) {
    
    
            if (min > arr[j]) {
    
     // 说明假定的最小值,并不是最小
                min = arr[j]; // 重置min
                minIndex = j; // 重置minIndex
            }
        }

        // 将最小值,放在arr[0], 即交换
        if (minIndex != 0) {
    
    
            arr[minIndex] = arr[0];
            arr[0] = min;
        }

        System.out.println("第1轮后~~");
        System.out.println(Arrays.toString(arr));// 1, 34, 119, 101

        // 第2轮
        minIndex = 1;
        min = arr[1];
        for (int j = 1 + 1; j < arr.length; j++) {
    
    
            if (min > arr[j]) {
    
     // 说明假定的最小值,并不是最小
                min = arr[j]; // 重置min
                minIndex = j; // 重置minIndex
            }
        }

        // 将最小值,放在arr[0], 即交换
        if (minIndex != 1) {
    
    
            arr[minIndex] = arr[1];
            arr[1] = min;
        }

        System.out.println("第2轮后~~");
        System.out.println(Arrays.toString(arr));// 1, 34, 119, 101

        // 第3轮
        minIndex = 2;
        min = arr[2];
        for (int j = 2 + 1; j < arr.length; j++) {
    
    
            if (min > arr[j]) {
    
     // 说明假定的最小值,并不是最小
                min = arr[j]; // 重置min
                minIndex = j; // 重置minIndex
            }
        }

        // 将最小值,放在arr[0], 即交换
        if (minIndex != 2) {
    
    
            arr[minIndex] = arr[2];
            arr[2] = min;
        }

        System.out.println("第3轮后~~");
        System.out.println(Arrays.toString(arr));// 1, 34, 101, 119
    }
}

结果:

排序前
[101, 34, 119, 1]1轮后~~
[1, 34, 119, 101]2轮后~~
[1, 34, 119, 101]3轮后~~
[1, 34, 101, 119]
排序后
[1, 34, 101, 119]

代码:(简化)

import java.util.Arrays;

// 选择排序
public class SelectSort {
    
    

    public static void main(String[] args) {
    
    
        int[] arr = {
    
    101, 34, 119, 1 };

        System.out.println("排序前");
        System.out.println(Arrays.toString(arr));

        selectSort(arr);

        System.out.println("排序后");
        System.out.println(Arrays.toString(arr));
    }

    // 选择排序
    public static void selectSort(int[] arr) {
    
    
        // 在推导的过程,我们发现了规律,因此,可以使用for来解决
        // 选择排序时间复杂度是 O(n^2)
        for (int i = 0; i < arr.length - 1; i++) {
    
    
            int minIndex = i;
            int min = arr[i];
            for (int j = i + 1; j < arr.length; j++) {
    
    
                if (min > arr[j]) {
    
     // 说明假定的最小值,并不是最小
                    min = arr[j]; // 重置min
                    minIndex = j; // 重置minIndex
                }
            }

            // 将最小值,放在arr[0], 即交换
            if (minIndex != i) {
    
    
                arr[minIndex] = arr[i];
                arr[i] = min;
            }

            System.out.println("第"+(i+1)+"轮后~~");
            System.out.println(Arrays.toString(arr));// 1, 34, 119, 101
        }
    }
}

结果:

排序前
[101, 34, 119, 1]1轮后~~
[1, 34, 119, 101]2轮后~~
[1, 34, 119, 101]3轮后~~
[1, 34, 101, 119]
排序后
[1, 34, 101, 119]

代码:(选择排序速度测试),在本人机器上面需要花费1-2s

package com.lzacking.sort;
import java.text.SimpleDateFormat;
import java.util.Date;
// 选择排序
public class SelectSort {
    
    
     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 data1 = new Date();
          SimpleDateFormat simpleDateFormat = new  SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
          String date1Str =  simpleDateFormat.format(data1);
          System.out.println("排序前的时间是=" +  date1Str);

          selectSort(arr);

          Date data2 = new Date();
          String date2Str =  simpleDateFormat.format(data2);
          System.out.println("排序前的时间是=" +  date2Str);
     }

     // 选择排序
     public static void selectSort(int[] arr) {
    
    
          // 在推导的过程,我们发现了规律,因此,可以使用for来解决
          // 选择排序时间复杂度是 O(n^2)
          for (int i = 0; i < arr.length - 1; i++) {
    
    
              int minIndex = i;
              int min = arr[i];
              for (int j = i + 1; j < arr.length; j++) {
    
    
                   if (min > arr[j]) {
    
     // 说明假定的最小值,并不是最小
                        min = arr[j]; // 重置min
                        minIndex = j; // 重置minIndex
                   }
              }
              // 将最小值,放在arr[0], 即交换
              if (minIndex != i) {
    
    
                   arr[minIndex] = arr[i];
                   arr[i] = min;
              }
          }
     }
}

结果:

排序前
排序前的时间是=2020-12-06 11:09:23
排序前的时间是=2020-12-06 11:09:25

(四)插入排序

(1)插入排序法介绍
插入排序的代码实现虽然没有冒泡排序和选择排序那么简单粗暴,但它的原理应该是最容易理解的了,因为只要打过扑克牌的人都应该能够秒懂。插入排序是一种最简单直观的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入

(2)插入排序法思想
将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列
从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面)

(3)插入排序的动画
在这里插入图片描述
动画解释:

一开始左端数字已经排序,数字 5 不动

然后,取出剩余未操作的左端数字 3

将其与已经操作的左侧数字相比较

如果左边的数字较大,则交换两个数字

这种情况下,由于 5 大于 3 ,所以交换两个数字

重复此操作,直到出现一个较小的数字或者数字到达左端

数字 3 已经完成排序

接下来,和之前一样取出剩余未操作的左端数字 4

与其相邻的左边数字进行比较

这种情况下,由于 5 大于 4 ,所以交换两个数字

继续操作,由于 3 小于 4 ,即出现了更小的数字,所以 4 停止移动

数字 4 已经完成排序

重复相同的操作,直到所有的数字完成排序


(4)代码
代码:(插入排序推导过程)

import java.util.Arrays;
public class InsertSort {
    
    

     public static void main(String[] args) {
    
    
          int[] arr = {
    
     101, 34, 119, 1};
          insertSort(arr); // 调用插入排序算法
     }

     // 插入排序
     public static void insertSort(int[] arr) {
    
    
          // 使用逐步推导的方式来讲解,便利理解
          // 第1轮 {101, 34, 119, 1}; => {34, 101, 119, 1}
          // 第一轮是为34找位置
          // {101, 34, 119, 1}; => {101, 101, 119, 1}

          // 定义待插入的数
          int insertVal = arr[1]; // insertVal = 34
          int insertIndex = 1 - 1; // 即arr[1]的前面这个数的下标

          // 给insertVal 找到插入的位置
          // 说明
          // 1. insertIndex >= 0 保证在给insertVal 找插入位置,不越界
          // 2. insertVal < arr[insertIndex] 待插入的数,还没有找到插入位置
          // 3. 就需要将 arr[insertIndex] 后移
          while (insertIndex >= 0 && insertVal <  arr[insertIndex]) {
    
    
              arr[insertIndex + 1] = arr[insertIndex];
              // {101, 34, 119, 1}; => {101, 101, 119, 1}
              insertIndex--; // 此时insertIndex为-1
          }

          // 当退出while循环时,说明插入的位置找到,  insertIndex + 1
          arr[insertIndex + 1] = insertVal; // arr[-1 + 1]  = 34; 即arr[0] = 34
          // {34, 101, 119, 1}
          System.out.println("第1轮插入");
          System.out.println(Arrays.toString(arr));

          // 第2轮
          // 第2轮 {34, 101, 119, 1}; => {34, 101, 119, 1}
          // 第二轮是为119找位置
          insertVal = arr[2]; // insertVal = 119
          insertIndex = 2 - 1; // insertIndex = 1;

          // 不满足while条件,直接跳出while循环
          while (insertIndex >= 0 && insertVal <  arr[insertIndex]) {
    
    
              arr[insertIndex + 1] = arr[insertIndex];
              insertIndex--;
          }

          arr[insertIndex + 1] = insertVal; // arr[1 + 1]  = 119
          // {34, 101, 119, 1}
          System.out.println("第2轮插入");
          System.out.println(Arrays.toString(arr));

          // 第3轮
          // 第3轮 {34, 101, 119, 1}; => {1, 34, 101, 119}
          // 第三轮是为1找位置
          insertVal = arr[3]; // insertVal = 1
          insertIndex = 3 - 1; // insertIndex = 2

          while (insertIndex >= 0 && insertVal <  arr[insertIndex]) {
    
    
              arr[insertIndex + 1] = arr[insertIndex];
              // {34, 101, 119, 119}
              // {34, 101, 101, 119}
              // {34, 34, 101, 119}
              insertIndex--;
              // insertIndex = 1
              // insertIndex = 0
              // insertIndex = -1
              
              // 当insertIndex = -1时,结束while循环
          }

          arr[insertIndex + 1] = insertVal; // arr[-1 + 1]  = 1;即arr[0] = 1
          // {1, 34, 101, 119}
          System.out.println("第3轮插入");
          System.out.println(Arrays.toString(arr));
     }
}

结果:

1轮插入
[34, 101, 119, 1]2轮插入
[34, 101, 119, 1]3轮插入
[1, 34, 101, 119]

代码:(简化版)

import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
public class InsertSort {
    
    

     public static void main(String[] args) {
    
    
          int[] arr = {
    
     101, 34, 119, 1 };
          insertSort(arr); // 调用插入排序算法
     }

     // 插入排序
     public static void insertSort(int[] arr) {
    
    
          int insertVal = 0;
          int insertIndex = 0;

          // 使用for循环来把代码简化
          for (int i = 1; i < arr.length; i++) {
    
    
              // 定义待插入的数
              insertVal = arr[i];
              insertIndex = i - 1; // 即arr[i]的前面这个数的下标

              // 给insertVal 找到插入的位置
              // 说明
              // 1. insertIndex >= 0 保证在给insertVal 找插入位置,不越界
              // 2. insertVal < arr[insertIndex] 待插入的数,还没有找到插入位置
              // 3. 就需要将 arr[insertIndex] 后移
              while (insertIndex >= 0 && insertVal <  arr[insertIndex]) {
    
    
                   arr[insertIndex + 1] =  arr[insertIndex];// arr[insertIndex]
                   insertIndex--;
              }

              // 当退出while循环时,说明插入的位置找到,  insertIndex + 1
              // 这里我们判断是否需要赋值
              if (insertIndex + 1 != i) {
    
    
                   arr[insertIndex + 1] = insertVal;
              }

              System.out.println("第" + i + "轮插入");
              System.out.println(Arrays.toString(arr));
          }
     }
}

结果:

1轮插入
[34, 101, 119, 1]2轮插入
[34, 101, 119, 1]3轮插入
[1, 34, 101, 119]

代码:(插入排序速度测试),本人机器大约为4-5s

import java.text.SimpleDateFormat;
import java.util.Date;
public class InsertSort {
    
    

     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 data1 = new Date();
          SimpleDateFormat simpleDateFormat = new  SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
          String date1Str =  simpleDateFormat.format(data1);
          System.out.println("排序前的时间是=" +  date1Str);

          insertSort(arr); // 调用插入排序算法

          Date data2 = new Date();
          String date2Str =  simpleDateFormat.format(data2);
          System.out.println("排序前的时间是=" +  date2Str);
     }

     // 插入排序
     public static void insertSort(int[] arr) {
    
    
          int insertVal = 0;
          int insertIndex = 0;
          // 使用for循环来把代码简化
          for (int i = 1; i < arr.length; i++) {
    
    
              // 定义待插入的数
              insertVal = arr[i];
              insertIndex = i - 1; // 即arr[1]的前面这个数的下标
              // 给insertVal 找到插入的位置
              // 说明
              // 1. insertIndex >= 0 保证在给insertVal 找插入位置,不越界
              // 2. insertVal < arr[insertIndex] 待插入的数,还没有找到插入位置
              // 3. 就需要将 arr[insertIndex] 后移
              while (insertIndex >= 0 && insertVal <  arr[insertIndex]) {
    
    
                   arr[insertIndex + 1] =  arr[insertIndex];// arr[insertIndex]
                   insertIndex--;
              }
              // 当退出while循环时,说明插入的位置找到,  insertIndex + 1
              // 这里我们判断是否需要赋值
              if (insertIndex + 1 != i) {
    
    
                   arr[insertIndex + 1] = insertVal;
              }
          }
     }
}

结果:

插入排序前
排序前的时间是=2020-12-07 23:22:31
排序前的时间是=2020-12-07 23:22:35

(五)希尔排序

(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}
结论: 当需要插入的数是较小的数时,后移的次数明显增多,对效率有影响。

(2)希尔排序法介绍
希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法

希尔排序是基于插入排序的以下两点性质而提出改进方法的:

  1. 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率;
  2. 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位;

希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序

(3)希尔排序法基本思想
选择一个增量序列 t1,t2,……,tk,其中 ti > tj, tk = 1

按增量序列个数 k,对序列进行 k 趟排序

每趟排序,根据对应的增量 ti,将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。仅增量因子为 1 时,整个序列作为一个表来处理,表长度即为整个序列的长度

(4)希尔排序动画
在这里插入图片描述
动画解释:

首先,选择增量 gap = 10/2 ,缩小增量继续以 gap = gap/2 的方式

初始增量为 gap = 10/2 = 5,整个数组分成了 5 组

按颜色划分为【 8 , 3 】,【 9 , 5 】,【 1 , 4 】,【 7 , 6 】,【 2 , 0 】

对这分开的 5 组分别使用上节所讲的插入排序

结果可以发现,这五组中的相对小元素都被调到前面了

缩小增量 gap = 5/2 = 2,整个数组分成了 2 组

【 3 , 1 , 0 , 9 , 7 】,【 5 , 6 , 8 , 4 , 2 】

对这分开的 2 组分别使用上节所讲的插入排序

此时整个数组的有序性是很明显的

再缩小增量 gap = 2/2 = 1,整个数组分成了 1 组

【 0, 2 , 1 , 4 , 3 , 5 , 7 , 6 , 9 , 0 】

此时,只需要对以上数列进行简单的微调,不需要大量的移动操作即可完成整个数组的排序


(5)代码
代码:(对有序序列在插入时采用交换法,推导过程)

import java.util.Arrays;
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) {
    
     // j = 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++) {
    
    
              // 遍历各组中所有的元素(共2组,每组有5个元素), 步长2
              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++) {
    
    
              // 遍历各组中所有的元素(共1组,每组有10个元素), 步长1
              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]

代码:(对有序序列在插入时采用交换法,简化版)

import java.util.Arrays;
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]

代码:(对有序序列在插入时采用交换法,测试速度,本人机器测试时间为9s)

import java.text.SimpleDateFormat;
import java.util.Date;
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() * 80000); // 生成一个[0, 80000) 数
          }
          System.out.println("排序前");

          Date data1 = new Date();
          SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
          String date1Str = simpleDateFormat.format(data1);
          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;
          // 根据前面的逐步分析,使用循环处理
          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));
          }
     }
}

结果:

排序前的时间是=2020-12-10 23:03:11
排序前的时间是=2020-12-10 23:03:20

代码:(对有序序列在插入时采用移动法,简化版)

import java.util.Arrays;
public class ShellSort {
    
    
     public static void main(String[] args) {
    
    
          int[] arr = {
    
     8, 9, 1, 7, 2, 3, 5, 4, 6, 0};
          shellSort2(arr);// 移位方式
     }
     // 对交换式的希尔排序进行优化->移位法
     public static void shellSort2(int[] arr) {
    
    
          // 增量gap, 并逐步的缩小增量
          int count = 1;
          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;
                        }
                        // 当退出while后,就给temp找到插入的位置
                        arr[j] = 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]

代码:(对有序序列在插入时采用移动法,测试速度,本人机器为1s左右)

import java.text.SimpleDateFormat;
import java.util.Date;
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() * 80000); // 生成一个[0, 8000000) 数
          }

          System.out.println("排序前");
          Date data1 = new Date();
          SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
          String date1Str = simpleDateFormat.format(data1);
          System.out.println("排序前的时间是=" + date1Str);

          shellSort2(arr);// 移位方式

          Date data2 = new Date();
          String date2Str = simpleDateFormat.format(data2);
          System.out.println("排序前的时间是=" + date2Str);
     }

     // 对交换式的希尔排序进行优化->移位法
     public static void shellSort2(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;
                        }
                        // 当退出while后,就给temp找到插入的位置
                        arr[j] = temp;
                   }
              }
          }
     }
     
}

结果:

排序前
排序前的时间是=2020-12-10 23:12:55
排序前的时间是=2020-12-10 23:12:55

猜你喜欢

转载自blog.csdn.net/a13027629517/article/details/115283194