算法05 排序算法(冒泡+选择+插入+希尔)

267 阅读11分钟

排序是将一组数据,依指定的顺序进行排列的过程

分类

  • 1)内部排序:指将需要处理的所有数据都加载到内部存储器中进行排序
    • 插入排序
      • 1 直接插入排序(常用)
      • 2 希尔排序
    • 选择排序
      • 3 简单选择排序(常用)
      • 4 堆排序
    • 交换排序
      • 5 冒泡排序(常用)
      • 6 快速排序
    • 7 归并排序
    • 8 基数排序
  • 2)外部排序法:数据量过大,无法全部加载到内存中,需要借助外部存储进行排序

1. 冒泡排序

冒泡排序(Bubble Sorting)的基本思想是:通过对待排序序列从前向后(从下标较小的元素开始)==,依次比较相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部==,就像水底的气泡一样逐渐向上冒。

推演过程

代码实现

main{
    int[] arr = {3,9,-1,10,-2};
    bubbleSort(arr);
    System.out.println("排序后的数组");
    System.out.println(Arrays.toString(arr));
}

public static void bubbleSort(int[] arr){
     //冒泡排序 时间复杂度O(n²)
    int temp = 0;//临时变量
    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]){
                temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            }
            System.out.println("内层排序后的数组为:j=="+j+"="+Arrays.toString(arr));
        }
        System.out.println("第"+(i+1)+"次排序后的数组");
        System.out.println(Arrays.toString(arr));
    }
}

控制台输出,感受数组的变化过程

内层排序后的数组为:j==0=[3, 9, -1, 10, -2]
内层排序后的数组为:j==1=[3, -1, 9, 10, -2]
内层排序后的数组为:j==2=[3, -1, 9, 10, -2]
内层排序后的数组为:j==3=[3, -1, 9, -2, 10]
----------------第1次排序后的数组----------------
[3, -1, 9, -2, 10]
内层排序后的数组为:j==0=[-1, 3, 9, -2, 10]
内层排序后的数组为:j==1=[-1, 3, 9, -2, 10]
内层排序后的数组为:j==2=[-1, 3, -2, 9, 10]
----------------第2次排序后的数组----------------
[-1, 3, -2, 9, 10]
内层排序后的数组为:j==0=[-1, 3, -2, 9, 10]
内层排序后的数组为:j==1=[-1, -2, 3, 9, 10]
----------------第3次排序后的数组----------------
[-1, -2, 3, 9, 10]
内层排序后的数组为:j==0=[-2, -1, 3, 9, 10]
----------------第4次排序后的数组----------------
[-2, -1, 3, 9, 10]

冒泡排序的优化

由于排序的过程中,各元素不断接近自己的位置,如果一趟比较下来没有进行过交换,就说明序列有序,因此要在排序过程中设置一个标志flag判断元素是否进行过交换。从而减少不必要的比较。

    public static void bubbleSort(int[] arr){
        //优化
        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;//进行下次判断
            }

        }
    }

测试80000个数排序所需的时间

需要11-12s就可以完成

    public static void main(String[] args) {
        //测试80000个数据的排序时间
        int[] array = new int[80000];
        for (int i = 0; i < 80000; i++) {
            array[i] = (int) (Math.random()*800000);//生成80000个0-800000的数
        }
        Date date1 = new Date();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String date1Str = simpleDateFormat.format(date1);
        System.out.println("排序前的时间是=="+date1Str);
        bubbleSort(array);
        Date date2 = new Date();
        String date2Str = simpleDateFormat.format(date2);
        System.out.println("排序后的时间是=="+date2Str);
    }

2. 选择排序

选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。选择排序是不稳定的排序方法。
时间复杂度T(n) = O(n²)

推演过程

代码实现

/**
 * @author DSH
 * @date 2020/4/30
 * @description 选择排序
 */
public class SelectionSort {

    public static void main(String[] args) {
        int[] arr = {3,9,-1,10,-2};
        selectionSort(arr);
        System.out.println("排序后=="+Arrays.toString(arr));
    }

    public static void selectionSort(int[] arr){
//        for (int i = 0; i < arr.length-1; i++) {
//            int min = arr[i];
//            int minIndex = i;
//            for (int j = i+1; j < arr.length; j++) {
//                if (arr[j]<min){
//                    min = arr[j];
//                    minIndex = j;
//                }
//            }
//            if (minIndex!=i) {
//                arr[minIndex] = arr[i];
//                arr[i] = min;
//            }
//        }

        for (int i = 0; i < arr.length-1; i++) {
            System.out.println("-----------第"+(i+1)+"次排序------------");
            int minIndex = i;
            for (int j = i+1; j < arr.length; j++) {
                if (arr[j]<arr[minIndex]){
                    minIndex = j;
                }
                System.out.println("内层循环 , 当前最小值为arr["+minIndex+"]="+arr[minIndex]);
            }
            if (minIndex!=i) {
                int temp = arr[i];
                arr[i] = arr[minIndex];
                arr[minIndex] = temp;
            }
            System.out.println("第"+(i+1)+"次排序后的数组");
            System.out.println(Arrays.toString(arr));
        }

    }

}

控制台输出,感受内层循环最小值的变化

-----------第1次排序------------
内层循环 , 当前最小值为arr[0]=3
内层循环 , 当前最小值为arr[2]=-1
内层循环 , 当前最小值为arr[2]=-1
内层循环 , 当前最小值为arr[4]=-2
第1次排序后的数组
[-2, 9, -1, 10, 3]
-----------第2次排序------------
内层循环 , 当前最小值为arr[2]=-1
内层循环 , 当前最小值为arr[2]=-1
内层循环 , 当前最小值为arr[2]=-1
第2次排序后的数组
[-2, -1, 9, 10, 3]
-----------第3次排序------------
内层循环 , 当前最小值为arr[2]=9
内层循环 , 当前最小值为arr[4]=3
第3次排序后的数组
[-2, -1, 3, 10, 9]
-----------第4次排序------------
内层循环 , 当前最小值为arr[4]=9
第4次排序后的数组
[-2, -1, 3, 9, 10]

测试80000个数排序所需的时间

需要3s左右(mbp15)

3. 插入排序

插入排序(Insertion Sorting):把n个待排序的元素看成为一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含有n-1个元素,排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表。

推导过程

    //代码推导过程
    public static void derivation(){
        int arr[] = {17,3,25,14};
        //第一轮 =>{3,17,25,14}
        //定义待插入的数
        int insertVal = arr[1];
        int insertIndex = 1-1;//即arr[1]前面的数的坐标
        //给insertValue找到插入的位置
        //1 insertIndex>=0保证不越界
        //2 insertVal<arr[insertIndex 待插入的数还没有找到插入位置
        //3 需要将arr[insertIndex]后移
        while(insertIndex>=0&&insertVal<arr[insertIndex]){//保证不越界
            arr[insertIndex+1] = arr[insertIndex];
            insertIndex--;
        }
        //当退出while循环时,说明插入的位置找到了,insertIndex+1;
        arr[insertIndex+1] = insertVal;
        System.out.println("第一轮插入后"+Arrays.toString(arr));//[3, 17, 25, 14]

        //第二轮
        insertVal = arr[2];
        insertIndex = 2-1;//即arr[1]前面的数的坐标
        while(insertIndex>=0&&insertVal<arr[insertIndex]){//保证不越界
            arr[insertIndex+1] = arr[insertIndex];
            insertIndex--;
        }
        arr[insertIndex+1] = insertVal;
        System.out.println("第二轮插入后"+Arrays.toString(arr));//[3, 17, 25, 14]

        //第三轮
        insertVal = arr[3];
        insertIndex = 3-1;
        while(insertIndex>=0&&insertVal<arr[insertIndex]){//保证不越界
            arr[insertIndex+1] = arr[insertIndex];
            insertIndex--;
        }
        arr[insertIndex+1] = insertVal;
        System.out.println("第三轮插入后"+Arrays.toString(arr));//[3, 14, 17, 25]

    }

代码实现

public class InsertionSort {
    public static void main(String[] args) {
        int arr[] = {17,3,25,14,20,9};
        insertionSort(arr);
    }

    public static void insertionSort(int[] arr) {
        int insertVal = 0;
        int insertIndex = 0;
        for (int i = 1; i < arr.length; i++) {
            insertVal = arr[i];
            insertIndex = i-1;//即arr[]前面的数的坐标
            //给insertValue找到插入的位置
            //1 insertIndex>=0保证不越界
            //2 insertVal<arr[insertIndex 待插入的数还没有找到插入位置
            //3 需要将arr[insertIndex]后移
            System.out.println("待插入的数据为arr["+i+"]="+insertVal);
            while(insertIndex>=0&&insertVal<arr[insertIndex]){//保证不越界
                arr[insertIndex+1] = arr[insertIndex];
                insertIndex--;
                System.out.println("挪动后的数组为"+Arrays.toString(arr));
            }
            //当退出while循环时,说明插入的位置找到了,insertIndex+1;
            //判断是否需要赋值
            if (insertIndex+1 != i) {
                arr[insertIndex + 1] = insertVal;
            }
            System.out.println("第"+i+"轮插入后,数组为");
            System.out.println(Arrays.toString(arr));
            System.out.println("-----------------------------------------");
        }
    }
}

控制台输出,感受数组的变化

待插入的数据为arr[1]=3
挪动后的数组为[17, 17, 25, 14, 20, 9]
第1轮插入后,数组为
[3, 17, 25, 14, 20, 9]
-----------------------------------------
待插入的数据为arr[2]=25
第2轮插入后,数组为
[3, 17, 25, 14, 20, 9]
-----------------------------------------
待插入的数据为arr[3]=14
挪动后的数组为[3, 17, 25, 25, 20, 9]
挪动后的数组为[3, 17, 17, 25, 20, 9]
第3轮插入后,数组为
[3, 14, 17, 25, 20, 9]
-----------------------------------------
待插入的数据为arr[4]=20
挪动后的数组为[3, 14, 17, 25, 25, 9]
第4轮插入后,数组为
[3, 14, 17, 20, 25, 9]
-----------------------------------------
待插入的数据为arr[5]=9
挪动后的数组为[3, 14, 17, 20, 25, 25]
挪动后的数组为[3, 14, 17, 20, 20, 25]
挪动后的数组为[3, 14, 17, 17, 20, 25]
挪动后的数组为[3, 14, 14, 17, 20, 25]
第5轮插入后,数组为
[3, 9, 14, 17, 20, 25]
-----------------------------------------

自己憋的双层for循环渣排序

    //自己憋了一个小时写的插入排序,双层for循环,效率并不高
    public static void myInsertionSort(int[] arr) {
        int[] newArr = new int[arr.length];
        //先给第一个元素赋值,然后从第二个开始比较
        newArr[0] = arr[0];
        for (int i = 1; i < arr.length; i++) {
            newArr[i] = arr[i];
//            System.out.println("-------第"+i+"次排序");
            for (int j = 0; j < i; j++) {
                if (newArr[j]>newArr[i]){
                    int temp = newArr[i];
                    newArr[i] = newArr[j];
                    newArr[j] = temp;
                }
//                System.out.println(Arrays.toString(newArr));
            }
        }
        
    }

测试80000个数排序所需的时间

0.7-0.75s之间
InsertionSort.insertionSort(array);
双层for循环,3.5-3.8s之间
InsertionSort.myInsertionSort(array);

4. 希尔排序(缩小增量排序)

希尔排序是希尔(Donald Shell)于1959年提出的一种排序算法。希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序.

基本思想

希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序; 随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止.

推导过程

public static void derivation(int[] arr){
        //第一轮排序,将十个数分成了5组
        int temp = 0;
        for (int i = 5; i < arr.length; i++) {
            //遍历各组中所有的元素(共5组,每组两个元素,步长是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("第一轮之后的数组为=="+ Arrays.toString(arr));

        //第二轮排序,将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("第二轮之后的数组为=="+ Arrays.toString(arr));

        //第三轮排序,将10个数分成2/2 一组
        for (int i = 1; i < arr.length; i++) {
            //遍历各组中所有的元素(共2组,每组5个元素,步长是2)
            for (int j = i-1; j >=0; j --) {
                //如果当前元素大于加上步长后的那个元素,说明交换
                if (arr[j]>arr[j+1]){
                    temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                }
            }
        }
        System.out.println("第三轮之后的数组为=="+ Arrays.toString(arr));

    }

代码实现

交换式

public class ShellSort {
    public static void main(String[] args) {
        int arr[] = {8,9,1,7,2,3,5,4,6,0};
        shellSort(arr);
//        derivation(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组,每组arr/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));
        }
    }
    
}

移位式

//对交换式的希尔排序进行优化->移位法
    public static void shellSort2(int[] arr) {
        //增量gap,并逐步缩小增量
        int count = 0;
        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));
        }
    }

测试80000个数排序所需的时间

希尔排序(交换式)6-8s,7.7s左右
ShellSort.shellSort(array);
希尔排序(移位式)0.02s
ShellSort.shellSort2(array);