排序算法

  • 排序算法的介绍
  • 算法的时间复杂度
  • **度量一个程序(算法)执行时间的两种方法**
  • **时间频度**
  • **时间复杂度**
  • **常见的时间复杂度**
  • 平均时间复杂度和最坏时间复杂度
  • 算法的空间复杂度
  • 基本介绍
  • 排序算法
  • 冒泡排序
  • 选择排序
  • 插入排序
  • 希尔排序
  • 快速排序
  • 归并排序
  • 基数排序
  • 相关术语解释


排序算法的介绍

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

  1. 内部排序:<重点>
    指将需要处理的所有数据都加载到内部存储器中进行排序。
  2. 外部排序法:数据量过大,无法全部加载到内存中,需要借助外部存储(文件等)进行排序。
  3. 常见的排序算法分类(见下图):

java数据结构和算法源码 java数据结构与算法_算法

算法的时间复杂度

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

  1. 事后统计的方法:
    这种方法可行, 但是有两个问题:一是要想对设计的算法的运行性能进行评测,需要实际运行该程序;二是所得时间的统计量依赖于计算机的硬件、软件等环境因素, 这种方式,要在同一台计算机的相同状态下运行,才能比较那个算法速度更快。
    问题:可能花费很长时间;电脑不一样结果可能不一样
  2. 事前估算的方法:
    通过分析某个算法的时间复杂度来判断哪个算法更优.

时间频度

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

例如:

java数据结构和算法源码 java数据结构与算法_算法_02


java数据结构和算法源码 java数据结构与算法_java_03

结论: 在统计一个时间复杂度时,常数项可以忽略

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

java数据结构和算法源码 java数据结构与算法_算法_04

java数据结构和算法源码 java数据结构与算法_java_05


结论: 在统计一个时间复杂度时,可以忽略低次项

  1. 2n^2+3n+10 和 2n^2 随着n 变大, 执行曲线无限接近, 可以忽略 3n+10
  2. 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 ,执行曲线分离,说明多少次方式关键

时间复杂度

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

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

计算时间复杂度的方法:

  1. 用常数1代替运行时间中的所有加法常数 T(n)=n²+7n+6 —> T(n)=n²+7n+1
  2. 修改后的运行次数函数中,只保留最高阶项 T(n)=n²+7n+1 —> T(n) = n²
  3. 去除最高阶项的系数 T(n) = n² —> T(n) = n² —> O(n²)

常见的时间复杂度

  1. 常数阶O(1)
  2. 对数阶O(log2n)
  3. 线性阶O(n)
  4. 线性对数阶O(nlog2n)
  5. 平方阶O(n^2)
  6. 立方阶O(n^3)
  7. k次方阶O(n^k)
  8. 指数阶O(2^n)

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

java数据结构和算法源码 java数据结构与算法_java数据结构和算法源码_06

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


常见的时间复杂度

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

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

  1. 对数阶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) .

  1. 线性阶O(n)

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

  1. 线性对数阶O(nlogN)

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

… … … …

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

java数据结构和算法源码 java数据结构与算法_算法_07

java数据结构和算法源码 java数据结构与算法_数据结构_08

算法的空间复杂度

基本介绍

  1. 类似于时间复杂度的讨论,一个算法的空间复杂度(Space Complexity)定义为该算法所耗费的存储空间,它也是问题规模n的函数。
  2. 空间复杂度(Space Complexity)是对一个算法在运行过程中临时占用存储空间大小的量度。有的算法需要占用的临时工作单元数与解决问题的规模n有关,它随着n的增大而增大,当n较大时,将占用较多的存储单元,例如快速排序和归并排序算法就属于这种情况
  3. 在做算法分析时,主要讨论的是时间复杂度。从用户使用体验上看,更看重的程序执行的速度。一些缓存产品(redis, memcache)和算法(基数排序)本质就是用空间换时间.

排序算法

冒泡排序

基本介绍

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

优化:

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

图解

java数据结构和算法源码 java数据结构与算法_数据结构_09

代码

package sort;
/*
 * @Author: Min
 * @Date:   2021/10/3
 * @description
 */

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

public class BubbleSort {
    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)的随机数组
        }
        Date start = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String s = sdf.format(start);
        bubbleSort(arr);
        Date end = new Date();
        String e = sdf.format(end);
        System.out.println("排序前:" + s);
        System.out.println("排序后:" + e);



//        int arr[] = {3,9,-1,10,-2};
//        //为了容易理解,如下是冒泡排序的演变过程
//        //第一趟排序.最大的数排在最后
//        int temp = 0;//临时变量
//        for (int i = 0; i < arr.length-1; i++) {
//            //如果前面的数比后面的数大,则交换
//            if (arr[i] > arr[i+1]) {
//                temp = arr[i];
//                arr[i] = arr[i+1];
//                arr[i+1] = temp;
//            }
//        }
//        System.out.println("第一趟排序后的数组:");
//        System.out.println(Arrays.toString(arr));
//
//        //第二趟排序
//        for (int i = 0; i < arr.length-1-1; i++) {
//            //如果前面的数比后面的数大,则交换
//            if (arr[i] > arr[i+1]) {
//                temp = arr[i];
//                arr[i] = arr[i+1];
//                arr[i+1] = temp;
//            }
//        }
//        System.out.println("第二趟排序后的数组:");
//        System.out.println(Arrays.toString(arr));
//
//        //第三趟排序
//        for (int i = 0; i < arr.length-1-2; i++) {
//            //如果前面的数比后面的数大,则交换
//            if (arr[i] > arr[i+1]) {
//                temp = arr[i];
//                arr[i] = arr[i+1];
//                arr[i+1] = temp;
//            }
//        }
//        System.out.println("第三趟排序后的数组:");
//        System.out.println(Arrays.toString(arr));
//
//        //第四趟排序
//        for (int i = 0; i < arr.length-1-3; i++) {
//            //如果前面的数比后面的数大,则交换
//            if (arr[i] > arr[i+1]) {
//                temp = arr[i];
//                arr[i] = arr[i+1];
//                arr[i+1] = temp;
//            }
//        }
//        System.out.println("第四趟排序后的数组:");
//        System.out.println(Arrays.toString(arr));

//        //代码优化
//        int temp = 0;
//        for (int j = 0; j < arr.length-1; j++) {
//
//            for (int i = 0; i < arr.length-1-j; i++) {
//                //如果前面的数比后面的数大,则交换
//                if (arr[i] > arr[i+1]) {
//                    temp = arr[i];
//                    arr[i] = arr[i+1];
//                    arr[i+1] = temp;
//                }
//            }
//            System.out.println("第"+(j+1)+"趟排序后的数组:");
//            System.out.println(Arrays.toString(arr));
//        }
//
//        int arr2[] = {3,9,-1,10,20};
//        //算法优化
//        boolean flag = false;//标识符---表示是否进行过交换
//        for (int j = 0; j < arr2.length-1; j++) {
//
//            for (int i = 0; i < arr2.length-1-j; i++) {
//                //如果前面的数比后面的数大,则交换
//                if (arr2[i] > arr2[i+1]) {
//                    flag = true;
//                    temp = arr2[i];
//                    arr2[i] = arr2[i+1];
//                    arr2[i+1] = temp;
//                }
//            }
//            System.out.println("第"+(j+1)+"趟排序后的数组:");
//            System.out.println(Arrays.toString(arr2));
//            if (!flag) {
//                //某一趟排序中一次都没有交换过
//                break;
//            } else {
//                flag = false;//很重要
//                //重置,进行下一次的判断
//            }
//        }
//        /*
//        解析:最后有三趟
//        int arr2[] = {3,9,-1,10,20};
//        第1趟排序后的数组:[3, -1, 9, 10, 20]——有交换
//        第2趟排序后的数组:[-1, 3, 9, 10, 20]——有交换
//        第3趟排序后的数组:[-1, 3, 9, 10, 20]——无交换,代码停止
//         */


//        //测试封装后的冒泡排序
//        int arr3[] = {3,9,-1,10,20};
//        bubbleSort(arr3);
    }

    //将冒泡排序优化算法封装
    public static void bubbleSort(int[] arr) {
        int temp = 0;
        boolean flag = false;//标识符---表示是否进行过交换
        for (int j = 0; j < arr.length-1; j++) {

            for (int i = 0; i < arr.length-1-j; i++) {
                //如果前面的数比后面的数大,则交换
                if (arr[i] > arr[i+1]) {
                    flag = true;
                    temp = arr[i];
                    arr[i] = arr[i+1];
                    arr[i+1] = temp;
                }
            }
//            System.out.println("第"+(j+1)+"趟排序后的数组:");
//            System.out.println(Arrays.toString(arr));
            if (!flag) {
                //某一趟排序中一次都没有交换过
                break;
            } else {
                flag = false;//很重要
                //重置,进行下一次的判断
            }
        }
        System.out.println(Arrays.toString(arr));
    }
}

选择排序

基本介绍

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

基本思想:

java数据结构和算法源码 java数据结构与算法_java数据结构和算法源码_10

图解

java数据结构和算法源码 java数据结构与算法_算法_11


java数据结构和算法源码 java数据结构与算法_数据结构_12


代码

package sort;
/*
 * @Author: Min
 * @Date:   2021/10/4
 * @description
 */

import java.text.SimpleDateFormat;
import java.util.Arrays;
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)的随机数组
        }
        Date start = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String s = sdf.format(start);
        selectSort(arr);
        System.out.println(Arrays.toString(arr));
        Date end = new Date();
        String e = sdf.format(end);
        System.out.println("排序前:" + s);
        System.out.println("排序后:" + e);



//        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 (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));
        }

//        //使用逐步推导是方式来
//        //第一轮:
//        //原始的数组: 101,34,119,1
//        //第一轮排序: 1,34,119,101
//        //算法:简单->复杂 把一个复杂的算法拆分成简单的问题,逐步解决
//
//        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("第一轮:");
//        System.out.println(Arrays.toString(arr));
//
//        //第二轮
//        minIndex = 1;
//        min = arr[1];
//        for (int j = 0 + 1; j < arr.length; j++) {
//            if (min > arr[j]) {
//                //如果满足,说明假定的值不是最小的
//                min = arr[j];//重置min
//                minIndex = j;//重置minIndex
//            }
//        }
//        if (minIndex != 1) {
//            arr[minIndex] = arr[1];
//            arr[1] = min;
//        }
//        System.out.println("第二轮:");
//        System.out.println(Arrays.toString(arr));
//
//        //第三轮
//        minIndex = 2;
//        min = arr[2];
//        for (int j = 0 + 2; j < arr.length; j++) {
//            if (min > arr[j]) {
//                //如果满足,说明假定的值不是最小的
//                min = arr[j];//重置min
//                minIndex = j;//重置minIndex
//            }
//        }
//        if (minIndex != 2) {
//            arr[minIndex] = arr[2];
//            arr[2] = min;
//        }
//        System.out.println("第三轮:");
//        System.out.println(Arrays.toString(arr));
    }
}

插入排序

基本介绍:

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

插入排序法思想:

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

图解

java数据结构和算法源码 java数据结构与算法_java数据结构和算法源码_13


代码

package sort;
/*
 * @Author: Min
 * @Date:   2021/10/4
 * @description
 */

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);
        //测试插入排序的速度
        //给定80000个数据测试
        int[] arr = new int[80000];
        for (int i = 0; i < 80000; i++) {
            arr[i] = (int) (Math.random() * 8000000);//生成[0,8000000)的随机数组
        }
        Date start = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String s = sdf.format(start);
        insertSort(arr);
        System.out.println(Arrays.toString(arr));
        Date end = new Date();
        String e = sdf.format(end);
        System.out.println("排序前:" + s);
        System.out.println("排序后:" + e);
    }

    //插入排序
    public static void insertSort(int[] arr) {
//        //使用逐步推导的方式
//        //第一轮
//        //定义待插入的数
//        int insertVal = arr[1];
//        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];
//            insertIndex--;
//        }
//        //当退出while循环时,说明插入的位置找到,insertIndex +1
//        arr[insertIndex + 1] = insertVal;
//        System.out.println("第一轮插入:");
//        System.out.println(Arrays.toString(arr));
//
//        //第二轮
//        insertVal = arr[2];
//        insertIndex = 2 - 1;
//        while (insertIndex >= 0 && insertVal < arr[insertIndex]) {
//            arr[insertIndex + 1] = arr[insertIndex];
//            insertIndex--;
//        }
//        arr[insertIndex + 1] = insertVal;
//        System.out.println("第二轮插入:");
//        System.out.println(Arrays.toString(arr));
//
//        //第san轮
//        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("第3轮插入:");
//        System.out.println(Arrays.toString(arr));

        //简化代码
        for (int i = 1; i < arr.length; i++) {

        int insertVal = arr[i];
        int insertIndex = i-1;
        while (insertIndex >= 0 && insertVal < arr[insertIndex]) {
            arr[insertIndex + 1] = arr[insertIndex];
            insertIndex--;
        }
        if (insertIndex+1 == i){//优化
        arr[insertIndex + 1] = insertVal;
        }
//        System.out.println("第"+(i)+"轮插入:");
//        System.out.println(Arrays.toString(arr));
        }
    }
}

希尔排序

基本介绍

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

基本思想

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

图解

java数据结构和算法源码 java数据结构与算法_算法_14

代码

java数据结构和算法源码 java数据结构与算法_java_15

  • 交换法:
package sort;
/*
 * @Author: Min
 * @Date:   2021/10/5
 * @description 希尔排序
 */

import javax.imageio.metadata.IIOMetadataFormatImpl;
import java.text.SimpleDateFormat;
import java.time.temporal.Temporal;
import java.util.Arrays;
import java.util.Date;

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);

        //测试希尔排序的速度
        //给定80000个数据测试
        int[] arr = new int[80000];
        for (int i = 0; i < 80000; i++) {
            arr[i] = (int) (Math.random() * 8000000);//生成[0,8000000)的随机数组
        }
        Date start = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String s = sdf.format(start);
        shellSort(arr);
        System.out.println(Arrays.toString(arr));
        Date end = new Date();
        String e = sdf.format(end);
        System.out.println("排序前:" + s);
        System.out.println("排序后:" + e);

    }

    //希尔排序
    public static void shellSort(int[] arr) {


//        int temp = 0;
//        //逐步推导
//        //思路:第一轮:先将10个数据分成了5组 10//2=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("第一轮:");
//        System.out.println(Arrays.toString(arr));
//        //第二轮:之前有5组,5//2=2组
//        for (int i = 2;i < arr.length;i ++) {
//            //遍历各组中的所有元素,(共4组,每组2个元素),步长为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("第二轮:");
//        System.out.println(Arrays.toString(arr));
//        //第三轮:之前有2组,2//2=1
//        for (int i = 1;i < arr.length;i ++) {
//            //遍历各组中的所有元素,(共4组,每组2个元素),步长为2
//            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("第三轮:");
//        System.out.println(Arrays.toString(arr));

        //代码简化
        int temp = 0;
        int x = 0;
        for (int k = arr.length/2; k > 0; k /= 2) {
            for (int i = k;i < arr.length;i ++) {
                //遍历各组中的所有元素,(共5组,每组2个元素),步长为5
                for (int j = i - k; j >= 0; j -= k) {
                    //如果当前元素大于加上步长后的那个元素,则交换
                    if (arr[j] > arr[j + k]) {
                        temp = arr[j];
                        arr[j] = arr[j + k];
                        arr[j + k] = temp;
                    }
                }
            }
//            System.out.println("第"+ (++x) + "轮:");
//            System.out.println(Arrays.toString(arr));
        }
    }

}
  • 移动法
//希尔排序——移动法
    public static void shellSort2(int[] arr) {
        //增量k,并逐步缩小增量
        for (int k = arr.length/2; k > 0; k /= 2) {
            //从第k个元素,诸葛对其所在的组进行直接插入排序
            for (int i = k; i < arr.length; i++) {
                int j = i;
                int temp = arr[j];
                if (arr[j] > arr[j-k]) {
                    while (j-k >= 0 && temp < arr[j-k]) {
                        //移动
                        arr[j] = arr[j-k];
                        j -= k;
                    }
                    //当退出while后,就给temp找到插入的位置
                    arr[j] = temp;
                }
            }
        }
    }

快速排序

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

示意图:

java数据结构和算法源码 java数据结构与算法_时间复杂度_16


代码

package sort;
/*
 * @Author: Min
 * @Date:   2021/10/7
 * @description
 */

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

public class QuickSort {
    public static void main(String[] args) {
//        int[] arr = {-9,78,0,23,-56,70,-1,90,5,8,8};
//        quickSort(arr,0,arr.length -1);

        //快速排序速度测试
        //给定80000个数据测试
        int[] arr = new int[80000];
        for (int i = 0; i < 80000; i++) {
            arr[i] = (int) (Math.random() * 8000000);//生成[0,8000000)的随机数组
        }
        Date start = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String s = sdf.format(start);
        quickSort(arr, 0, arr.length - 1);
        System.out.println(Arrays.toString(arr));
        Date end = new Date();
        String e = sdf.format(end);
        System.out.println("排序前:" + s);
        System.out.println("排序后:" + e);
    }

    public static void quickSort(int[] arr,int left,int right) {
        int l = left;//左下标
        int r = right;//右下标
        //pivot 中轴
        int pivot = arr[(left + right) / 2];
        int temp = 0;//临时变量
        //while 循环的目的是让比pivot值小的放到左边,比它大的放在右边
        while (l < r) {
            //找比pivot小的值放在左边,遇到大或等于的退出
            while (arr[l] < pivot) {
                l += 1;
            }
            //找比pivot 大的值放在右边,遇到小或等于的退出
            while (arr[r] > pivot) {
                r -= 1;
            }
            if (l >= r) {
                //说明pivot的左右两边的值,已经按照左边全是小于pivot的值
                //右边全部是大于pivot的值
                break;
            }
            //否则交换
            temp = arr[l];
            arr[l] = arr[r];
            arr[r] = temp;
            //如果交换完后发现pivot=arr[l],则前移
            if (arr[l] == pivot) {
                r -= 1;
            }
            //如果交换后,发现pivot=arr[r],则后移
            if (arr[r] == pivot) {
                l += 1;
            }
//            System.out.println(Arrays.toString(arr));
        }
        //如果l==r,必须l++,r--,否则出现栈溢出
        if (l == r) {
            l += 1;
            r -= 1;
        }
        //向左递归
        if (left < r) {
            quickSort(arr,left,r);
        }
        //向右递归
        if (right > l) {
            quickSort(arr,l,right);
        }
    }
}

归并排序

归并排序介绍:

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

思想示意图

java数据结构和算法源码 java数据结构与算法_算法_17

说明:

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

java数据结构和算法源码 java数据结构与算法_算法_18

代码

package sort;
/*
 * @Author: Min
 * @Date:   2021/10/7
 * @description
 */

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

public class MergetSort {
    public static void main(String[] args) {
//        int arr[] ={8,4,5,7,1,3,6,2};
//        int temp[] = new int[arr.length];//归并排序需要一个额外的空间
//        mergeSort(arr,0,arr.length-1,temp);
//        System.out.println(Arrays.toString(arr));

        //给定80000个数据测试
        int[] arr = new int[80000];
        int temp[] = new int[arr.length];//归并排序需要一个额外的空间
        for (int i = 0; i < 80000; i++) {
            arr[i] = (int) (Math.random() * 8000000);//生成[0,8000000)的随机数组
        }
        Date start = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String s = sdf.format(start);
        mergeSort(arr,0,arr.length-1,temp );
        System.out.println(Arrays.toString(arr));
        Date end = new Date();
        String e = sdf.format(end);
        System.out.println("排序前:" + s);
        System.out.println("排序后:" + e);
    }

    //分+合的方法
    public static void mergeSort(int[] arr,int left,int right,int[] temp) {
        if (left < right) {
            int mid = (left + right) / 2;//中间索引
            //向左递归进行分解
            mergeSort(arr,left,mid,temp);
            //向右递归进行分解
            mergeSort(arr,mid+1,right,temp);
            //每分解一次就合并一次
            merge(arr,left,mid,right,temp);
        }
    }

    //合并的方法
    /**@param arr 排序的原始数组
     * @param left 左边的有序序列的初始索引
     * @param mid 中间索引
     * @param right 右边索引
     * @param temp 做中转的数组
     */
    public static void merge(int []arr,int left,int mid,int right,int[] temp) {
        int i = left;//初始化i,左边有序序列的初始索引
        int j = mid + 1;//初始化j,右边有序序列的初始索引
        int t = 0;//指向temp数组的当前索引
        //先把左右两边(有序)的数据按照规则填充到temp数组
        //直到左右两边的有序数组序列右一边全部处理完毕为止
        while (i <= mid && j <= right) {
            //如果左边的有序序列的当前元素,小于等于右边有序序列的当前元素
            //即将左边的当前元素,
            //然后t++,i++
            if (arr[i] <= arr[j]) {
                temp[t] = arr[i];
                t ++;
                i ++;
            } else {
                temp[t] = arr[j];
                t ++;
                j ++;
            }
        }
        //把剩余数据的一边的数据依次全部填充到temp
        while (i <= mid) {
            //左边的有序序列还有剩余元素,就全部填充到temp
            temp[t] = arr[i];
            t ++;
            i ++;
        }
        while (j <= right) {
            //右边的有序序列还有剩余的元素,就全部填充到temp
            temp[t] = arr[j];
            t ++;
            j ++;
        }
        //将temp数组的一边数据一次全部填充到temp
        //注意:并不是每次都拷贝所有
        t = 0;
        int tempLeft = left;
        while (tempLeft <= right) {
            //
            arr[tempLeft] = temp[t];
            t ++;
            tempLeft ++;
        }
    }

}

基数排序

基数排序(桶排序)介绍:

  1. 基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是通过键值的各个位的值,将要排序的元素分配至某些“桶”中,达到排序的作用
  2. 基数排序法是属于稳定性的排序,基数排序法的是效率高的稳定性排序法
  3. 基数排序(Radix Sort)是桶排序的扩展
  4. 基数排序是1887年赫尔曼·何乐礼发明的。它是这样实现的:将整数按位数切割成不同的数字,然后按每个位数分别比较。

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

示意图

第一轮:

java数据结构和算法源码 java数据结构与算法_时间复杂度_19

第二轮:

java数据结构和算法源码 java数据结构与算法_算法_20


第三轮:

java数据结构和算法源码 java数据结构与算法_java数据结构和算法源码_21


一共要多少轮?取决于数据里面最大数的位数

代码

package sort;
/*
 * @Author: Min
 * @Date:   2021/10/9
 * @description
 */

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

import static java.lang.Math.pow;

public class RadixSort {
    public static void main(String[] args) {
//        int arr[] = {53,3,542,748,14,214,8452,2366,961212545,45461321};
//        radixSort(arr);
//        System.out.println(Arrays.toString(arr));

        //给定80000个数据测试
        int[] arr = new int[80000];
        int temp[] = new int[arr.length];
        for (int i = 0; i < 80000; i++) {
            arr[i] = (int) (Math.random() * 8000000);//生成[0,8000000)的随机数组
        }
        Date start = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String s = sdf.format(start);
        radixSort(arr);
        System.out.println(Arrays.toString(arr));
        Date end = new Date();
        String e = sdf.format(end);
        System.out.println("排序前:" + s);
        System.out.println("排序后:" + e);
    }

    //基数排序
    public static void radixSort(int[] arr) {

        //代码简化
        int[][] bucket = new int[10][arr.length];
        //为了记录每个桶中实际存放了多少数据,定义一个一维数组来记录各个桶每次放入的数据是个数
        //*原来桶中的数据不会删除,新数据覆盖.....
        int[] bucketElementCounts = new int[10];
        //得到数组中的最大位数的值
        int max = arr[0];
        for (int i = 1; i < arr.length; i++) {
            if (arr[i] > max) {
                max = arr[i];
            }
        }
        //得到最大位数
        int length = (max + "").length();

        for (int j = 0; j < length; j++) {
            for (int i = 0;i < arr.length;i ++) {
                int digitOfElement = arr[i] / (int)(pow(10,j)) % 10 ;
                //放入到对应的桶中
                bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[i];
                bucketElementCounts[digitOfElement] ++ ;//bucketElementCounts[digitOfElement]初始化为0
            }
//            for (int i = 0,n = 1; i < arr.length; i++,n *= 10) {
//                int digitOfElement = arr[i] / (int)(pow(10,j)) % 10 ;
//                //放入到对应的桶中
//                bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[i];
//                bucketElementCounts[digitOfElement] ++ ;//bucketElementCounts[digitOfElement]初始化为0
//            }
            //按照桶的顺序,将数据放回数组
            int index = 0;
            //遍历每一个桶,并将桶中的数据,放入到原数组
            for (int k = 0;k < bucketElementCounts.length;k ++) {
                //如果桶中有数据才放入到原数组
                if (bucketElementCounts[k] != 0) {//说明有数据
                    //此时循环放入数据
                    for (int l = 0;l < bucketElementCounts[k];l ++) {
                        //取出元素放到arr
                        arr[index] = bucket[k][l];//第k个桶的l个元素
                        index++;
                    }
                }
                bucketElementCounts[k] = 0;//置0
            }
//            System.out.println("第"+j+"轮:" +  Arrays.toString(arr));
        }



//        //定义一个二维数组,表示10个桶,每个桶就是一个一维数组
//        //*为了防止溢出,每个一维数组的大小为 arr.length
//        int[][] bucket = new int[10][arr.length];
//        //为了记录每个桶中实际存放了多少数据,定义一个一维数组来记录各个桶每次放入的数据是个数
//        //*原来桶中的数据不会删除,新数据覆盖.....
//        int[] bucketElementCounts = new int[10];
//
//        //第一轮 ——对个位进行排序
//        for (int i = 0;i < arr.length;i ++) {
//            //取出个位
//            int digitOfElement = arr[i] % 10;//得到元素个位的值
//            //放入到对应的桶中
//            bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[i];
//            bucketElementCounts[digitOfElement] ++ ;//bucketElementCounts[digitOfElement]初始化为0
//        }
//        //按照桶的顺序,将数据放回数组
//        int index = 0;
//        //遍历每一个桶,并将桶中的数据,放入到原数组
//        for (int k = 0;k < bucketElementCounts.length;k ++) {
//            //如果桶中有数据才放入到原数组
//            if (bucketElementCounts[k] != 0) {//说明有数据
//                //此时循环放入数据
//                for (int l = 0;l < bucketElementCounts[k];l ++) {
//                    //取出元素放到arr
//                    arr[index] = bucket[k][l];//第k个桶的l个元素
//                    index++;
//                }
//            }
//            bucketElementCounts[k] = 0;//置0
//        }
//        System.out.println("第一轮:" +  Arrays.toString(arr));
//        //第二轮 ——对个位进行排序
//        for (int i = 0;i < arr.length;i ++) {
//            //取出十位
//            int digitOfElement = arr[i] /10 % 10;//得到元素十位的值
//            //放入到对应的桶中
//            bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[i];
//            bucketElementCounts[digitOfElement] ++ ;//bucketElementCounts[digitOfElement]初始化为0
//        }
//        //按照桶的顺序,将数据放回数组
//        index = 0;
//        //遍历每一个桶,并将桶中的数据,放入到原数组
//        for (int k = 0;k < bucketElementCounts.length;k ++) {
//            //如果桶中有数据才放入到原数组
//            if (bucketElementCounts[k] != 0) {//说明有数据
//                //此时循环放入数据
//                for (int l = 0;l < bucketElementCounts[k];l ++) {
//                    //取出元素放到arr
//                    arr[index] = bucket[k][l];//第k个桶的l个元素
//                    index++;
//                }
//            }
//            bucketElementCounts[k] = 0;//置0
//        }
//        System.out.println("第二轮:" +  Arrays.toString(arr));
//        //第三轮 ——对个位进行排序
//        for (int i = 0;i < arr.length;i ++) {
//            //取出百位
//            int digitOfElement = arr[i] /100 % 10;//得到元素百位的值
//            //放入到对应的桶中
//            bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[i];
//            bucketElementCounts[digitOfElement] ++ ;//bucketElementCounts[digitOfElement]初始化为0
//        }
//        //按照桶的顺序,将数据放回数组
//        index = 0;
//        //遍历每一个桶,并将桶中的数据,放入到原数组
//        for (int k = 0;k < bucketElementCounts.length;k ++) {
//            //如果桶中有数据才放入到原数组
//            if (bucketElementCounts[k] != 0) {//说明有数据
//                //此时循环放入数据
//                for (int l = 0;l < bucketElementCounts[k];l ++) {
//                    //取出元素放到arr
//                    arr[index] = bucket[k][l];//第k个桶的l个元素
//                    index++;
//                }
//            }
//
//        }
//        System.out.println("第三轮:" +  Arrays.toString(arr));
    }
}

相关术语解释

java数据结构和算法源码 java数据结构与算法_数据结构_22