【Java数据结构与算法】基础篇(4):常用排序算法:冒泡、选择、插入、希尔、快速、归并、基数

10 篇文章 0 订阅
9 篇文章 0 订阅

大家好,我是皮皮猫吖!

每文一言:请再悄悄加点油,无论如何都想听你说:“我终于成为不负众望的人了”。


本篇文章:

主要是关于java数据结构与算法的一些基本知识:常用排序算法:冒泡、选择、插入、希尔、快速、归并、基数、堆排序等【后续会补充堆排序】

正文如下:

一、排序算法

1)排序算法的介绍:

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

2)排序的分类:

① 内部排序:

指将需要处理的所有数据都加载到内部存储器中进行排序。

② 外部排序法:

数据量过大,无法全部加载到内存中,需要借助外部存储进行

排序。

3) 常见的排序算法分类(见下图):

在这里插入图片描述

4)算法的时间复杂度:

度量一个程序(算法)执行时间的两种方法:

① 事后统计的方法

这种方法可行, 但是有两个问题:一是要想对设计的算法的运行性能进行评测,需要实际运行该程序;二是所得时间的统计量依赖于计算机的硬件、软件等环境因素, 这种方式,要在同一台计算机的相同状态下运行,才能比较那个算法速度更快

② 事前估算的方法

通过分析某个算法的时间复杂度来判断哪个算法更优。

③ 时间频度

时间频度:一个算法花费的时间与算法中语句的执行次数成正比例,哪个算法中语句执行次数多,它花费时间就多。一个算法中的语句执行次数称为语句频度或时间频度。记为T(n)。[举例说明]

例子:计算1—100所有数字之和:

解法一:T(n) = n + 1

在这里插入图片描述

解法二:T(n) = 1

在这里插入图片描述

④ 忽略常数项:

在这里插入图片描述
在这里插入图片描述

结论:

1)2n+20 和 2n 随着n 变大,执行曲线无限接近, 20可以忽略

2)3n+10 和 3n 随着n 变大,执行曲线无限接近, 10可以忽略

⑤ 忽略低次项:

在这里插入图片描述
在这里插入图片描述

结论:

2)2n^2+3n+10 和 2n^2 随着n 变大, 执行曲线无限接近, 可以忽略 3n+10

1)n^2+5n+20 和 n^2 随着n 变大,执行曲线无限接近, 可以忽略 5n+20

⑥ 忽略系数

在这里插入图片描述
在这里插入图片描述

结论:

1)随着n值变大,5n^2+7n 和 3n^2 + 2n ,执行曲线重合, 说明 这种情况下, 5和3可以忽略。

2)而n^3+5n 和 6n^3+4n ,执行曲线分离,说明多少次方式关键

★★★⑦ 时间复杂度:

1)一般情况下,算法中的基本操作语句的重复执行次数是问题规模n的某个函数,用T(n)表示,若有某个辅助函数f(n),使得当n趋近于无穷大时,T(n) / f(n) 的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数。记作 T(n)=O( f(n) ),称O( f(n) ) 为算法的渐进时间复杂度,简称时间复杂度

2)T(n) 不同,但时间复杂度可能相同。 如:T(n)=n²+7n+6 与 T(n)=3n²+2n+2 它们的T(n) 不同,但时间复杂度相同,都为O(n²)。

3)计算时间复杂度的方法:

① 用常数1代替运行时间中的所有加法常数 T(n)=n²+7n+6 => T(n)=n²+7n+1

② 修改后的运行次数函数中,只保留最高阶项 T(n)=n²+7n+1 => T(n) = n²

③ 去除最高阶项的系数 T(n) = n² => T(n) = n² => O(n²)

★★★⑧ 常见的时间复杂度
1)常数阶O(1)

无论代码执行了多少行,只要是没有循环等复杂结构,那这个代码的时间复杂度就都是O(1)

在这里插入图片描述

上述代码在执行的时候,它消耗的时候并不随着某个变量的增长而增长,那么无论这类代码有多长,即使有几万几十万行,都可以用O(1)来表示它的时间复杂度。

2)对数阶O(log2n)

在这里插入图片描述

说明:

在while循环里面,每次都将 i 乘以 2,乘完之后,i 距离 n 就越来越近了。假设循环x次之后,i 就大于 2 了,此时这个循环就退出了,也就是说 2 的 x 次方等于 n,那么 x = log2n也就是说当循环 log2n 次以后,这个代码就结束了。因此这个代码的时间复杂度为:O(log2n) 。 O(log2n) 的这个2 时间上是根据代码变化的,i = i * 3 ,则是 O(log3n) 。

上述时间复杂度:受到n和底数的影响,所以,时间复杂度为O(logan)。

在这里插入图片描述

3)线性阶O(n)

在这里插入图片描述

说明

该段代码,for循环里面的代码会执行n遍,因此它消耗的时间是随着n的变化而变化的,因此这类代码都可以用O(n)来表示它的时间复杂度。

上述时间复杂度:只与n有关系,与其他都没有关系。所以,时间复杂度为O(n)。

4)线性对数阶O(nlog2n)

在这里插入图片描述

说明:线性对数阶O(nlogN) 其实非常容易理解,将时间复杂度为O(log2n)的代码循环N遍的话,那么它的时间复杂度就是 n * O(log2N),也就是了O(nlog2N)。

上述时间复杂度:外层与n有关系,内层与log2n有关系,所以,时间复杂度为O(nlog2n)。

5)平方阶O(n^2)

在这里插入图片描述

说明:平方阶O(n²) 就更容易理解了,如果把 O(n) 的代码再嵌套循环一遍,它的时间复杂度就是 O(n²),这段代码其实就是嵌套了2层n循环,它的时间复杂度就是 O(n * n),即 O(n²) 如果将其中一层循环的n改成m,那它的时间复杂度就变成了 O(m * n)

上述时间复杂度:外程与n有关系,内层与m有关系,所以,时间复杂度为O(n^2)

6)立方阶O(n^3)

与上面的O(n²) 很相似,O(n³)相当于三层n循环

7)k次方阶O(n^k)

与上面的O(n²) 很相似,O(n^k)相当于k层n循环

8)指数阶O(2^n)

在这里插入图片描述

结论:

1)常见的算法时间复杂度由小到大依次为:Ο(1)<Ο(log2n)<Ο(n)<Ο(nlog2n)<Ο(n2)<Ο(n3)< Ο(nk) <Ο(2n) ,随着问题规模n的不断增大,上述时间复杂度不断增大,算法的执行效率越低。

2)从图中可见,我们应该尽可能避免使用指数阶的算法。

⑨ 平均复杂度和最坏时间复杂度

1)平均时间复杂度是指所有可能的输入实例均以等概率出现的情况下,该算法的运行时间。

2)最坏情况下的时间复杂度称最坏时间复杂度。一般讨论的时间复杂度均是最坏情况下的时间复杂度。 这样做的原因是:最坏情况下的时间复杂度是算法在任何输入实例上运行时间的界限,这就保证了算法的运行时间不会比最坏情况更长。

3)平均时间复杂度和最坏时间复杂度是否一致,和算法有关(如下图):

在这里插入图片描述

⑩ 算法的空间复杂度

1)类似于时间复杂度的讨论,一个算法的空间复杂度(Space Complexity)定义为该算法所耗费的存储空间,它也是问题规模n的函数。

2)空间复杂度(Space Complexity)是对一个算法在运行过程中临时占用存储空间大小的量度。有的算法需要占用的临时工作单元数与解决问题的规模n有关,它随着n的增大而增大,当n较大时,将占用较多的存储单元,例如快速排序和归并排序算法就属于这种情况。

3)在做算法分析时,主要讨论的是时间复杂度。从用户使用体验上看,更看重的程序执行的速度。 一些缓存产品(redis, memcache)和算法(基数排序)本质就是用空间换时间。

5)冒泡排序:

① 冒泡排序的介绍:

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

② 冒泡排序的优化:

因为排序的过程中,各元素不断接近自己的位置,如果一趟比较下来没有进行过交换,就说明序列有序,因此要在排序过程中设置一个标志flag判断元素是否进行过交换。从而减少不必要的比较。(这里说的优化,在冒泡排序写好后,再进行优化)。

③ 冒泡排序的的过程分析:

举一个具体的案例来说明冒泡法。将五个无序的数:3, 9, -1, 10, -2 使用冒泡排序法将其排成一个从小到大的有序数列:

在这里插入图片描述

④ 冒泡排序的代码实现:
package com.data.structure.study6.sort;

import org.omg.CORBA.Current;

import java.util.Random;

/**
 * 冒泡排序的代码分析
 * @author imppm
 * @create 2021-03-20-9:28
 */
public class Sort1BubbleDemo1 {
    public static void main(String[] args) {

        //测试普通冒泡排序
        //需要遍历的数组
        //int[] array = {3,9,-1,10,-2};
        //冒泡排序数组
        //bubbleSort1(array);
        //遍历数组
        //list(array);


        //测试优化过后的冒泡排序
//        int[] array = {3,9,-1,10,20};
//        bubbleSort2(array);


        //测试一下冒泡排序的时间复杂度
        //创建一个80000数据的数组
        int[] array = new int[80000];
        Random random = new Random();
        for (int i = 0; i < 80000; i++){
            array[i] = random.nextInt();
        }

        //数组排序前的初始时间
        long begin = System.currentTimeMillis();
        bubbleSort(array);
        //数组排序后的结束时间
        long end = System.currentTimeMillis();
        System.out.println("冒泡排序,排序80000个数据的数组,大概耗时"+(end-begin)+"ms。");
    }


    //冒泡排序的时间复杂度:O(n^2)
    private static void bubbleSort1(int[] array){
        //用作交换的变量
        int temp;
        //大循环:比较array.length-1次
        for (int i = 0; i < array.length-1; i++){
            //小循环,每一次都是比较到未排序的最后一个数据处
            for (int j = 0; j < array.length-i-1; j++){
                //如果前面的元素大于后面的元素值,就进行交换
                //得到的应该是从小到大的排序数组
                if(array[j]>array[j+1]){
                    temp = array[j];
                    array[j] = array[j+1];
                    array[j+1] = temp;
                }
                System.out.printf("第%d趟的第%d次比较之后的数组:",i+1,j+1);
                list(array);
            }
            System.out.println("------------------------------");
        }
    }

    //冒泡排序的优化:在新的一轮中,如果没有发生一次交换的话,
    // 在以后的交换判断中,是不会发生交换的
    public static void bubbleSort2(int[] array){
        //交换变量
        int temp;
        //标志位,判断在当前这一趟,是否发生交换
        boolean flag = false;

        //冒泡排序:需要比较array.length-1趟
        for (int i = 0; i < array.length-1; i++){
            //标志位置为false
            flag = false;
            for (int j = 0; j < array.length-1-i; j++){
                //如果当前数据的值大于后面邻近位的值,就发生交换
                //最后得到从大到小排序的数组
                if(array[j]>array[j+1]){
                    //发生过交换,标志位就置为true
                    flag = true;
                    temp = array[j];
                    array[j] = array[j+1];
                    array[j+1] = temp;
                }
                System.out.printf("第%d趟的第%d次比较之后的数组:",i+1,j+1);
                list(array);
            }

            System.out.println("---------------------------------");
            //如果flag = false,一次都没发生过交换,提前结束排序
            if(!flag){
                System.out.println("冒泡排序提前结束");
                System.out.print("排序后的数组是:");
                list(array);
                return;
            }
        }
    }

    //冒泡排序的模板代码
    //冒泡排序的时间复杂度:O(n^2)
    private static void bubbleSort(int[] array){
        //交换变量
        int temp;
        //标志位,判断在当前这一趟,是否发生交换
        boolean flag = false;

        //冒泡排序:需要比较array.length-1趟
        for (int i = 0; i < array.length-1; i++){
            //标志位置为false
            flag = false;
            for (int j = 0; j < array.length-1-i; j++){
                //如果当前数据的值大于后面邻近位的值,就发生交换
                //最后得到从大到小排序的数组
                if(array[j]>array[j+1]){
                    //发生过交换,标志位就置为true
                    flag = true;
                    temp = array[j];
                    array[j] = array[j+1];
                    array[j+1] = temp;
                }
            }
            //如果flag = false,一次都没发生过交换,提前结束排序
            if(!flag){
                return;
            }
        }
    }


    //遍历排序后的数组
    private static void list(int[] array){
        for (int i:array){
            System.out.print(i+" ");
        }
        System.out.println();
    }
}

6)选择排序:

① 选择排序的介绍

选择式排序也属于内部排序法,是从欲排序的数据中,按指定的规则选出某一元素,再依规定交换位置后达到排序的目的。

② 选择排序的思想

选择排序(selectsorting)也是一种简单的排序方法。它的基本思想是:第一次从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次,得到一个按排序码从小到大排列的有序序列。

③ 选择排序的的过程分析:

有一群牛, 颜值分别是101,34, 119, 1,使用选择排序从低到高进行排序[101, 34, 119, 1]

在这里插入图片描述

④ 选择排序的代码实现:
package com.data.structure.study6.sort;

import com.data.leetcode.day01.Array;

import java.util.Random;

/**
 * 选择排序代码实现:
 *  复杂的算法简单化,先简单实现,然后再综合实现
 * @author imppm
 * @create 2021-03-20-13:46
 */
public class Sort2SelectDemo1 {
    public static void main(String[] args) {
        //int[] array1 = {3,9,-1,10,-2};

//        int[] array1 = {8,3,2,1,7,4,6,3};

//        int[] array1 = {101,34,119,1};

//        selectSort1(array1);
//        list(array1);


        //创建一个长度为80000的数组
        int[] array1 = new int[80000];

        //测试选择排序排序80000个数据的速度
        Random random = new Random();
        for (int i = 0; i < 80000; i++){
            array1[i] = random.nextInt(80000);
        }
        long begin = System.currentTimeMillis();
        selectSort1(array1);
        long end = System.currentTimeMillis();
        long result = end - begin;
        System.out.println("选择排序,排序80000个数据的数组,耗时"+result+"ms!");
    }

    //选择排序算法模板
    //选择排序的时间复杂度:O(n^2)
    private static void selectSort1(int[] array){
        //中间变量
        int temp;

        //记录最小值下标
        int index;

        //需要比较的次数:array.length-1
        for (int i = 0; i < array.length-1; i++){

            //每次都将下标初始化为已排序数据的下一位索引
            index = i;
            //从未排序的索引开始遍历:在剩下的数据中查找是否需要更换最小值索引
            for (int j = i+1; j < array.length; j++){
                //如果当前数组index索引下的值小于j索引下的值
                //更换temp存储的值:更换为最小数据下标
                //temp保存的永远都是最小值的索引
                if(array[index] > array[j]){
                    index = j;
                }
            }
            //如果没有发生交换,就不进行数据的设置
            if(index!=i) {
                temp = array[i];
                array[i] = array[index];
                array[index] = temp;
            }
        }
    }

    //遍历数组
    private static void list(int[] array){
        for (int i:array){
            System.out.print(i+" ");
        }
    }
}

7)插入排序

① 插入排序的介绍

插入式排序属于内部排序法,是对于欲排序的元素以插入的方式找寻该元素的适当位置,以达到排序的目的。

② 插入排序的思想:

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

③ 插入排序的过程分析:

在这里插入图片描述

④ 插入排序的代码实现:

有一群小牛, 考试成绩分别是101,34, 119, 1 请从小到大排序

package com.data.structure.study6.sort;

import java.util.Random;

/**
 * 插入排序算法:
 * @author imppm
 * @create 2021-03-20-14:38
 */
public class Sort3InsertDemo1 {
    public static void main(String[] args) {
//        int[] array = {101, 34, 119, 1, -1, 3};
//
//        insertSort1(array);

        //创建一个长度为80000的数组
        int[] array1 = new int[80000];

        //测试插入排序排序80000个数据的速度
        Random random = new Random();
        for (int i = 0; i < 80000; i++){
            array1[i] = random.nextInt(100000);
//            array1[i] = (int) (Math.random()*80000);
        }
        long begin = System.currentTimeMillis();
        insertSort(array1);
        long end = System.currentTimeMillis();
        long result = end - begin;
        System.out.println("插入排序,排序80000个数据的数组,耗时"+result+"ms!");

        list(array1);
    }

    //插入排序:
    private static void insertSort1(int[] array){
        //待插入的值
        int insertVal;

        //插入的索引
        int insertIndex;

        //从小到大进行排序
        //总共需要比较array.length-1次
        for (int i = 1; i < array.length; i++){
            //获取到要插入的数据
            insertVal = array[i];

            //获取到插入数据索引的前一个
            insertIndex = i - 1;

            //要插入的数据是否在该位置
            while (insertIndex>=0 && insertVal < array[insertIndex]){
                //要插入的数据小于当前值
                //已排序的数组向后移动一位
                array[insertIndex+1] = array[insertIndex];
                //插入的位置--
                insertIndex--;
            }

            if(insertIndex+1!=i) {
                //当前位置为要插入元素合适的位置
                array[insertIndex + 1] = insertVal;
            }

            System.out.printf("第%d次排序后的数组:",i);
            list(array);
        }
    }


    //插入排序:模板代码
    private static void insertSort(int[] array){
        //待插入的值
        int insertVal;

        //插入的索引
        int insertIndex;

        //从小到大进行排序
        //总共需要比较array.length-1次
        for (int i = 1; i < array.length; i++){
            //获取到要插入的数据
            insertVal = array[i];

            //获取到插入数据索引的前一个
            insertIndex = i - 1;

            //要插入的数据是否在该位置
            while (insertIndex>=0 && insertVal < array[insertIndex]){
                //要插入的数据小于当前值
                //已排序的数组向后移动一位
                array[insertIndex+1] = array[insertIndex];
                //插入的位置--
                insertIndex--;
            }

            //当前位置为要插入元素合适的位置
            array[insertIndex+1] = insertVal;
        }
    }

    private static void list(int[] array){
        for (int i:array){
            System.out.print(i+" ");
        }
        System.out.println();
    }
}

8)希尔排序【移位法重点掌握】

① 简单插入排序存在的问题

数组 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}

结论:当需要插入的数是较小的数的时候,后移的次数明显增多,对效率有影响

② 希尔排序的介绍:

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

③ 希尔排序法基本思想

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

在这里插入图片描述
在这里插入图片描述

④ 希尔排序的应用实例:

有一群小牛, 考试成绩分别是 {8,9,1,7,2,3,5,4,6,0} 请从小到大排序. 请分别使用

  1. 希尔排序时, 对有序序列在插入时采用交换法, 并测试排序速度.

  2. 希尔排序时, 对有序序列在插入时采用移动法, 并测试排序速度

package com.immort.dataStructure.sort;

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

/**
 * 希尔排序
 * @author ppmy
 * @create 2021-03-26 20:47
 */
public class Study2_ShellSort1 {
    public static void main(String[] args) {
        //创建要排序的数组
        int[] array = {8,9,1,7,2,3,5,4,6,0};
        //希尔排序
        //exchangeShellSort(array);
        //排序后的数组输出®
        //list(array);
        //test();
        //shiftShellSort1(array);
        //list(array);
        test();
    }

    //测试希尔排序,排序80000个数据的时间
    private static void test(){
        //随机产生80000个随机数
        int[] array = new int[800000];
        Random random = new Random();
        for (int i = 0; i < array.length; i++){
            array[i] = random.nextInt(10000000);
        }


        //创建date对象
        Date date = new Date();
        //格式化对象
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        //开始排序的毫秒数
        long begin = System.currentTimeMillis();
        //开始排序的时间
        String beginStr = simpleDateFormat.format(date);

        System.out.println("排序之前的时间为:"+beginStr);


        //交换法进行希尔排序
        //exchangeShellSort(array);
        //移位法进行希尔排序
        shiftShellSort1(array);

        //排序完成之后的date
        Date date1 = new Date();
        //排序完成后的时间
        String endStr = simpleDateFormat.format(date1);
        //排序完成之后的毫秒数
        long end = System.currentTimeMillis();
        //排序消耗的毫秒数
        long result = end - begin;
        System.out.println("希尔排序随机排序80000数,时间为:"+result+"ms");
        System.out.println("排序之后的时间为:"+endStr);
    }

    //希尔排序
    private static void shellSort2(int[] array){
        //中间交换变量
        int temp;
        //希尔排序分的组数
        int groupCount;

        //第一轮希尔排序的组数
        groupCount = array.length / 2;
        //第一轮排序:需要排序比较的次数
        for (int i = groupCount; i < array.length; i++){
            //第一轮排序的比较:
            for (int j = i - groupCount; j >= 0; j-=groupCount){
                //如果当前值大于同组中后面的其他值,就进行交换
                if(array[j] > array[j+groupCount]){
                    temp = array[j];
                    array[j] = array[j+groupCount];
                    array[groupCount+j] = temp;
                }
            }
        }

        System.out.print("第一轮排序之后的数组:");
        list(array);

        //第二轮排序的组数
        groupCount /= 2;

        //第二轮排序
        for (int i = groupCount; i < array.length; i++){
            for (int j = i - groupCount; j >= 0;j -= groupCount){
                if(array[j]>array[j+groupCount]){
                    temp = array[j];
                    array[j] = array[j+groupCount];
                    array[j+groupCount] = temp;
                }
            }
        }
        System.out.print("第二轮排序之后的数组:");
        list(array);

        //第三次排序的组数
        groupCount /= 2;
        //第三次排序
        for (int i = groupCount; i < array.length; i++){

            for (int j = i - groupCount; j >= 0; j -= groupCount){
                if(array[j]>array[j+groupCount]){
                    temp = array[j];
                    array[j] = array[j+groupCount];
                    array[j+groupCount] = temp;
                }
            }
        }

        System.out.print("第三次排序之后的数组:");
        list(array);

    }

    //希尔排序交换法模版代码【复杂】
    //从小到大的希尔排序
    private static void exchangeShellSort(int[] array){
        //记录当前数组的组数
        int groupCount = 0;
        //作为中间交换的变量
        int temp;
        //设置组数的初值
        groupCount = array.length;
        while (true){
            //如果当前的组数为1的时候,跳出该循环
            if(groupCount == 1){
                break;
            }
            //排序一次之后组数/2
            groupCount /= 2;

            //以该题目中的数组为例:8,9,1,7,2,3,5,4,6,0
            //分为groupCount个组
            //第一次分组之后进行排序:
            //分为5个组:【8、3】、【9、5】、【1、4】、【7、6】、【2、0】只需要比较两位数就行
            //第一次排序之后的数组为:3,5,1,6,0,8,9,4,7,2

            //第二次分组之后进行排序:
            //分为2个组:【3、1、0、9、7】、【5、6、8、4、2】需要比较5个数
            //从每一组的最后一个数字开始向前比较,依次把最小的数字送到最前面
            //第一组进行排序:
            //  比较前两位:第一次内层for循环【1、3、0、9、7】
            //  比较前三位:第二次内层for循环【0、1、3、9、7】
            //  比较前四位:第三次内层for循环【0、1、3、9、7】
            //  比较前五位:第四次内层for循环【0、1、3、7、9】

            //第二组进行排序:
            //  比较前两位:第一次内层for循环【5、6、8、4、2】
            //  比较前三位:第二次内层for循环【5、6、8、4、2】
            //  比较前四位:第三次内层for循环【4、5、6、8、2】
            //  比较前五位:第四次内层for循环【2、4、5、6、8】


            //第三次分组之后进行排序:与第二次分组类似
            //分为1个组:需要比较10位数
            //从当前组的最后一个数字开始向前比较,依次把最小的数字送到前面去

            for (int i = groupCount; i < array.length; i++){
                for (int j = i - groupCount; j >= 0; j -= groupCount){
                    if(array[j]>array[j+groupCount]){
                        temp = array[j];
                        array[j] = array[j+groupCount];
                        array[j+groupCount] = temp;
                    }
                }
            }
        }
        System.out.print("希尔排序之后的数组:");
    }


    //推荐使用该种排序算法
    //移位法实现希尔排序【简单】
    private static void shiftShellSort1(int[] array){
        //记录组中的最小值
        int group_min;
        //记录最小值的索引值
        int j;

        //分组的次数
        for (int groupCount = array.length/2;groupCount!=0;groupCount/=2){
            //内层为插入排序
            for (int i = groupCount; i < array.length; i++){
                //假设当前位置为该组的最小值
                group_min = array[i];

                //取已排序数组的最后一个数据的索引
                j = i - groupCount;
                //比较当前位置与已排序数组的数据,找到要插入的位置
                while (j >= 0 && array[j] > group_min){
                    //大的数据放到后面
                    array[j + groupCount] = array[j];
                    //最小值索引向前移动
                    j -= groupCount;
                }
                //最小值插入到适当的位置
                array[j+groupCount] = group_min;
            }
        }
    }

    private static void list(int[] array){
        for(int i :array){
            System.out.print(i+" ");
        }
        System.out.println();
    }

}

9)快速排序【经典算法】

① 快速排序的介绍:

快速排序(Quicksort)是对冒泡排序的一种改进。基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

② 快速排序法的示意图:

在这里插入图片描述

③ 快速排序的应用实例:

要求: 对 [-9,78,0,23,-567,70] 进行从小到大的排序,要求使用快速排序法。【测试8w和800w】

package com.immort.dataStructure.sort;

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

/**
 * 快速排序的实现
 * @author ppmy
 * @create 2021-03-27 14:46
 */
public class Study3_QuickSortDemo1 {
    public static void main(String[] args) {
        int[] array = {-9,78,0,23,-567,70,-1,900,423};

        quickSort(array, 0, array.length-1);

        list(array);

        //测试快速排序排序8 0000数据的时间
        test();
    }

    //测试快速排序,排序8 0000个数据的时间
    private static void test(){
        //随机产生8 0000个随机数
        int[] array = new int[80000];
        Random random = new Random();
        for (int i = 0; i < array.length; i++){
            array[i] = random.nextInt(1000000);
        }


        //创建date对象
        Date date = new Date();
        //格式化对象
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        //开始排序的毫秒数
        long begin = System.currentTimeMillis();
        //开始排序的时间
        String beginStr = simpleDateFormat.format(date);

        System.out.println("排序之前的时间为:"+beginStr);

        //快速排序
        quickSort(array,0,array.length-1);

        //排序完成之后的date
        Date date1 = new Date();
        //排序完成后的时间
        String endStr = simpleDateFormat.format(date1);
        //排序完成之后的毫秒数
        long end = System.currentTimeMillis();
        //排序消耗的毫秒数
        long result = end - begin;
        System.out.println("快速排序随机排序80000数,时间为:"+result+"ms");
        System.out.println("排序之后的时间为:"+endStr);
    }

    //快速排序:模板代码
    private static void quickSort(int[] array, int left, int right){
        //左指针:负责遍历中轴左边的数据
        int l  = left;
        //右指针:负责遍历中轴右边的数据
        int r = right;
        //中轴索引上存储的数据
        int middle = array[(l+r)/2];

        //中间变量:交换时候使用
        int temp = 0;

        //左指针没有遇到右指针
        //完成交换的操作:选定中轴,左边的数据小于中轴数据,右边的数据大于中轴数据
        while (l < r){
            //当遇到左边的值大于中轴数据的时候,左指针停止遍历
            //左指针指向数据小于中轴值,循环遍历
            while (array[l] < middle){
                //左指针+1
                l++;
            }
            //当遇到右指针的值小于中轴数据的时候,右指针停止遍历
            //右指针指向数据大于中轴值,循环遍历
            while (array[r] > middle){
                //右指针-1
                r--;
            }

            //如果l>=r,说明middle的左右两边的值,已经按照中轴左边小,右边大的数据进行排序
            if(l >= r){
                break;
            }

            //如果没有满足l<=r左右指针就停止了,就需要进行交换
            temp = array[l];
            array[l] = array[r];
            array[r] = temp;

            //交换完成,发现l==middle,说明右指针到达了中轴数据位置
            //右指针需要向左移动一位
            if(array[l] == middle){
                r--;
            }

            //交换完毕,发现这个arr[r]==middle,表示左指针到达了中轴数据位置
            //左指针需要向右移动一位
            if(array[r] == middle){
                l++;
            }
        }

        //左指针和右指针都到达了中轴位置
        if(l == r){
            l++;
            r--;
        }

        //右指针还没有到达左端:
        // 传入数组:左边为left,右边为r,向左递归
        if(left < r){
            quickSort(array, left, r);
        }

        //左指针还没有到达右端
        //  传入数据:左边为l,右边为fright,向有递归
        if(right > l){
            quickSort(array, l, right);
        }
    }

    private static void list(int[] array){
        for (int i:array){
            System.out.print(i+" ");
        }
        System.out.println();
    }
}

10)归并排序

① 归并排序的介绍:

归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略( 分治法将问题 分(divide) 成一些小的问题然后递归求解,而治(conquer) 的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。

② 归并排序的基本思想:

在这里插入图片描述

可以看到这种结构很像一棵完全二叉树,归并排序采用递归去实现(也可采用迭代的方式去实现)。阶段可以理解为就是递归拆分子序列的过程。

③ 归并排序思想—合并相邻有序子序列

再来看看阶段,我们需要将两个已经有序的子序列合并成一个有序序列,比如上图中的最后一次合并,要将[4,5,7,8]和[1,2,3,6]两个已经有序的子序列,合并为最终序列[1,2,3,4,5,6,7,8],来看下实现步骤:

在这里插入图片描述
在这里插入图片描述

④ 归并排序的应用实例:

给定一个数组, val arr = Array(9,8,7,6,5,4,3,2,1), 请使用归并排序完成排序。

package com.immort.dataStructure.sort;

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

/**
 * @author ppmy
 * @create 2021-03-28 10:24
 */
public class Study4_MergeSortDemo1 {

    public static void main(String[] args) {
        int[] array = {1,3,1,5,6,7,8,3,2,4,1,2,3,213};

        int[] temp = new int[array.length];

        mergeSort(array, 0, array.length-1, temp);
        list(temp);

        //测试归并排序:排序8 0000万个数据花费的时间
        test();
    }

    //测试归并排序,排序8 0000个数据的时间
    private static void test(){
        //随机产生8 0000个随机数
        int[] array = new int[80000];
        Random random = new Random();
        for (int i = 0; i < array.length; i++){
            array[i] = random.nextInt(10000000);
        }


        //创建date对象
        Date date = new Date();
        //格式化对象
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        //开始排序的毫秒数
        long begin = System.currentTimeMillis();
        //开始排序的时间
        String beginStr = simpleDateFormat.format(date);

        System.out.println("排序之前的时间为:"+beginStr);

        int[] temp = new int[array.length];
        //归并排序
        mergeSort(array,0,array.length-1, temp);

        //排序完成之后的date
        Date date1 = new Date();
        //排序完成后的时间
        String endStr = simpleDateFormat.format(date1);
        //排序完成之后的毫秒数
        long end = System.currentTimeMillis();
        //排序消耗的毫秒数
        long result = end - begin;
        System.out.println("归并排序随机排序80000数,时间为:"+result+"ms");
        System.out.println("排序之后的时间为:"+endStr);
    }

    //分+和
    private static void mergeSort(int[] array, int left, int right, int[] temp){
        //将数组进行分解+合
        //分解的结束条件是,分到只有一个数据
        //分解到还有两个数据的时候,开始进行合并
        if(left < right){
            //获取到中间值
            int mid = (left + right) / 2;
            mergeSort(array, left, mid, temp);
            mergeSort(array, mid+1, right, temp);
            //进行合并
            mergeArray(array,left,mid,right,temp);
        }
    }

    /**
     *  合并的方法
     * @param array 要排序的数组
     * @param left 左边有序序列的初始索引值
     * @param middle 数组中间数据的索引【偶数:array.length/2-1;奇数:array.length/2】
     * @param right 数组的长度
     * @param temp 作为中间数组变量,归并好的数组
     */
    private static void mergeArray(int[] array,int left, int middle, int right, int[] temp){
        //左边有序序列的头指针
        int array1_l = left;
        //右边有序序列的头指针
        int array2_l = middle+1;
        //中间数组的初始索引值
        int temp_index = 0;

        //同时遍历两个有序序列
        //左边有序序列的头指针不能超过middle的值
        //右边有序序列的头指针不能超过right的值
        while (array1_l <= middle && array2_l <= right){
            //如果左边有序序列的数据大于右边有序序列的数据,将右边有序序列的数据填充到temp数组中,并将右边有序序列的头指针+1,temp+1
            if(array[array1_l] > array[array2_l]){
                temp[temp_index++] = array[array2_l++];
            }
            //如果左边有序序列的数据小于右边有序序列的值,将左边的有序序列的值填充到temp中,并将左边有序序列的头指针+1.temp的指针+1
            else{
                temp[temp_index++] = array[array1_l++];
            }
        }

        //左边有序序列有剩余,将左边序列的值,都填充到temp数组中
        while (array1_l <= middle){
            temp[temp_index++] = array[array1_l++];
        }

        //右边有序序列有剩余,将右边序列的值,都填充到temp数组中
        while (array2_l <= right){
            temp[temp_index++] = array[array2_l++];
        }

        //将temp中的数据拷贝到array中
        temp_index = 0;
        //需要将数据拷贝到array中的初始位置
        int tempLeft = left;

        //将temp中的数据,放到array的tempLeft到right
        while (tempLeft <= right){
            array[tempLeft++] = temp[temp_index++];
        }


    }

    private static void list(int[] array){
        for (int i:array){
            System.out.print(i+" ");
        }
        System.out.println();
    }
}

11)基数排序【桶排序】

① 基数排序的介绍:

(1) 基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是通过键值的各个位的值,将要排序的元素分配至某些“桶”中,达到排序的作用。

(2) 基数排序法是属于稳定性的排序,基数排序法的是效率高的稳定性排序法。

(3) 基数排序(Radix Sort)是桶排序的扩展

(4) 基数排序是1887年赫尔曼·何乐礼发明的。它是这样实现的:将整数按位数切割成不同的数字,然后按每个位数分别比较。

② 基数排序的基本思想:

(1) 将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。

(2) 图文解释,理解基数排序的步骤:

在这里插入图片描述

③ 基数排序的说明

(1) 基数排序是对传统桶排序的扩展,速度很快。

(2) 基数排序是经典的空间换时间的方式,占用内存很大, 当对海量数据排序时,容易造成 OutOfMemoryError 。

(3) 有负数的数组,一般不用基数排序进行排序。

④ 基数排序的应用实例:

将数组 {53, 3, 542, 748, 14, 214 } 使用基数排序, 进行升序排序:

package com.immort.dataStructure.sort;

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

/**
 * 基数排序:桶排序
 *  获取数字的位数可以用一个巧妙的方法:
 *      (数字+"").length
 * @author ppmy
 * @create 2021-03-28 14:17
 */
public class Study5_RadixSortDemo1 {
    public static void main(String[] args) {
        int[] array = {53, 3, 542, 748, 14, 214};

        radixSort(array);

        list(array);

        //测试桶排序:排序800 0000个数据
        test();
    }

    //测试桶排序,排序800 0000个数据的时间
    private static void test(){
        //随机产生800 0000个随机数
        int[] array = new int[8000000];
        Random random = new Random();
        for (int i = 0; i < array.length; i++){
            array[i] = random.nextInt(1000000);
        }


        //创建date对象
        Date date = new Date();
        //格式化对象
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        //开始排序的毫秒数
        long begin = System.currentTimeMillis();
        //开始排序的时间
        String beginStr = simpleDateFormat.format(date);

        System.out.println("排序之前的时间为:"+beginStr);

        //桶排序
        radixSort2(array);

        //排序完成之后的date
        Date date1 = new Date();
        //排序完成后的时间
        String endStr = simpleDateFormat.format(date1);
        //排序完成之后的毫秒数
        long end = System.currentTimeMillis();
        //排序消耗的毫秒数
        long result = end - begin;
        System.out.println("桶排序随机排序800 0000个数据,时间为:"+result+"ms");
        System.out.println("排序之后的时间为:"+endStr);
    }

    //基数排序的方法
    private static void radixSort1(int[] array){
        //定义一个二维数组,每一个二维数组就是一个桶
        //1.二位数组包含10个一维数组
        //2.为了防止桶中存储数据出现溢出的情况,将桶的大小都设置为array.length
        //3,基数排序是典型的空间换时间的算法

        //创建一个桶
        int[][] buckets = new int[10][array.length];

        //创建一个一维数组,用来存储每个桶中的数据个数
        int[] bucketCount = new int[10];

        int numberOfDigit;
        //遍历数组中的所有的数据,向桶中添加数据
        for (int i = 0; i < array.length; i++){
            //获取到个数上的数字
            numberOfDigit = array[i] % 10;
            //把该数字加入到对应桶中
            buckets[numberOfDigit][bucketCount[numberOfDigit]] = array[i];
            bucketCount[numberOfDigit]++;
        }

        //记录当前遍历桶中数据的位置
        int index;
        //记录当前数组中的数据的位置
        int begin = 0;
        //遍历所有的桶,查看桶中存储的数据
        for (int i = 0; i < buckets.length; i++){
            index = 0;
            //如果存储桶个数的数组对应的值不为0,表示有数据
            if(bucketCount[i]>0){
               while (index < bucketCount[i]){
                   array[begin++] = buckets[i][index];
                   index++;
               }
               bucketCount[i] = 0;
            }
        }

        list(array);
        
    }


    //基数排序的模板代码1
    private static void radixSort(int[] array){
        //定义一个二维数组,每一个二维数组就是一个桶
        //1.二位数组包含10个一维数组
        //2.为了防止桶中存储数据出现溢出的情况,将桶的大小都设置为array.length
        //3,基数排序是典型的空间换时间的算法

        //创建一个桶:二维数组
        int[][] buckets = new int[10][array.length];

        //创建一个一维数组,用来存储每个桶中的当前数据个数
        int[] bucketCount = new int[10];

        //数字位数上的值即桶号
        int numberOfDigit;

        //记录桶排序是否已经排好序
        //false:已排好序
        //true:还需要排序
        boolean flag = false;

        //记录数据的位数
        int count = 0;

        while(true){
            //获取当前的位数
            count++;

            //初始化桶标志位
            flag = false;

            //遍历数组中的所有数据,向桶中添加数据
            for (int i = 0; i < array.length; i++){
                //获取到数组中数据各个位数上的数字
                numberOfDigit = getNumberOfDigits(count,array[i]);
                //把该数字放到 该位数字 对应的桶中
                //bucketCount[numberOfDigit]:获取到记录的numberOfDigit桶中的当前数据个数
                //buckets[numberOfDigit][bucketCount[numberOfDigit]]:给numberOfDigit桶中继续向后添加数据
                buckets[numberOfDigit][bucketCount[numberOfDigit]] = array[i];

                //bucketCount[numberOfDigit]++:numberOfDigit桶中的数据个数+1
                bucketCount[numberOfDigit]++;

                //如果存在numberOfDigit不等于0,表示有非0桶放入数据,表示还未到达最大数的位数
                if(numberOfDigit!=0){
                    flag = true;
                }
            }

            //将array中的数据放入到桶中,全部放入到0桶中,表示排序已完成
            if(!flag){
                return;
            }

            //记录当前遍历桶中数据的位置
            int index;
            //记录当前数组中的数据的位置
            int begin = 0;

            //遍历所有的桶,将桶中存储的数据赋值到原数组中
            for (int i = 0; i < buckets.length; i++){
                index = 0;
                //如果存储桶个数的数组对应的值不为0,表示该桶中有数据
                //循环遍历,取出该桶中的所有数据
                if(bucketCount[i]>0){
                    while (index < bucketCount[i]){
                        array[begin++] = buckets[i][index];
                        index++;
                    }
                    //当前桶中的数据已全部拿出,记录该桶的个数的变量需要置为0
                    bucketCount[i] = 0;
                }
            }
        }
    }

    /**
     *
     * @param number 获取当前digit的第几位
     * @param digit 数据
     * @return 获取到的数据
     */
    private static int getNumberOfDigits(int number, int digit){
        int num = 0;
        //获取到digit的第number位上的数字
        for (int i = 0; i < number; i++){
            num = digit%10;
            digit/=10;
        }
        return num;
    }

    //基数排序模板代码2
    public static void radixSort2(int[] arr){
        //先找到原数组中的最大值
        int max = arr[0];
        for(int i = 1; i < arr.length; i++) {
            if (arr[i] > max) {
                max = arr[i];
            }
        }

        //获取到最大值数据的位数
        int maxLength = (max + "").length();

        //创建一个桶:二维数组
        int[][] bucket = new int[10][arr.length];
        //创建一个一维数组用来记录各个桶当前的数据个数
        int[] bucketElementCounts = new int[10];

        //遍历的次数:最大值数据的位数
        for(int i = 0 , n = 1; i < maxLength; i++, n *= 10) {
            //遍历原数组,将原数组中的数据放入到桶中
            for(int j = 0; j < arr.length; j++) {
                //获取到对应位数上的值:从个位开始获取
                int digitOfElement = arr[j] / n % 10;
                //把该数据放入到对应的桶号中
                bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
                //记录桶中数据个数的数组,相应桶号下桶中数据个数+1
                bucketElementCounts[digitOfElement]++;
            }
            //记录原数组的个数
            int index = 0;
            //遍历桶
            for(int k = 0; k < bucketElementCounts.length; k++) {
                //判断记录桶个数数组对应的桶号中的个数是否为0
                //等于0:该桶中没有数据
                //不等于0:该桶中存放了数据
                if(bucketElementCounts[k] != 0) {
                    //遍历该桶:依次将桶中的数据放入到数组中
                    //放入的顺序是:桶中存放的顺序,从头开始放
                    for(int l = 0; l < bucketElementCounts[k]; l++) {
                        arr[index++] = bucket[k][l];
                    }
                    //每把桶中的数据放入到数组中,记录桶的数据个数的数组就需要清0一次
                    bucketElementCounts[k] = 0;
                }

            }
        }
    }

    //遍历数组
    private static void list(int[] array){
        for (int i:array){
            System.out.print(i+" ");
        }
        System.out.println();
    }
}

12)排序的稳定性:

① 稳定性是什么?

假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的

13)基本排序算法的比较:

在这里插入图片描述
在这里插入图片描述


希望本篇文章对大家有所帮助,后续会继续分享java数据结构与算法相关学习知识…

如果文章内容有错误的地方,请在留言处留下你的见解,方便大家共同学习。谢谢!

如有侵权或其他任何问题请联系:QQ1370922071,本文主要用于学习交流,转载请声明!

作者:皮皮猫吖


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值