一、八种重要的内部排序

image.png

二、常见的时间复杂度

image.png
说明
Ο(1): 一句话就搞定;
Ο(log2__n):对数运算;
Ο(n):单层for循环;
Ο(nlog2__n):线性和对数相结合的运算;
Ο(_n_2):双重嵌套循环;
Ο(_n_3):三层嵌套循环;
Ο(_n_k):K层嵌套循环;
Ο(_2_n) :
•常见的算法时间复杂度由小到大依次为:Ο(1)<Ο(log2__n)<Ο(n)<Ο(nlog2__n)<Ο(_n_2)<Ο(_n_3)< Ο(_n_k) <Ο(_2_n) ,随着问题规模n的不断增大,上述时间复杂度不断增大,算法的执行效率越低
•从图中可见,我们应该尽可能避免使用指数阶的算法

1. 时间复杂度示例

image.png
image.png
image.png
image.png
image.png

三、冒泡排序(Bubble Sorting)

1. 图解冒泡过程

image.png

一句话总结:每次产生最大的一个数调整到最后,所以一共执行的次数是:(n-1)(n-2)(n-3)1 次, 时间复杂度是: O(n2)

  1. package com.atguigu.sort;
  2. public class BubbleSortDemo {
  3. public static void main(String[] args) {
  4. int[] arr = {3, 9, -1, 10, -2};
  5. // 设置临时变量做交换的时候用
  6. int temp = 0;
  7. for (int j = 0; j < arr.length - 1; j++) {
  8. for (int i = 0; i < arr.length - 1 - j; i++) {
  9. if (arr[i] > arr[i + 1]) {
  10. temp = arr[i];
  11. arr[i] = arr[i + 1];
  12. arr[i + 1] = temp;
  13. }
  14. }
  15. }
  16. }
  17. }

2. 代码的优化:

做一个标识,如果发现某次顺序没有发生变化,则说明顺序正确,可以提前停止
// 将前面的冒泡排序封装成一个方法
    public static void bubbleSortFun(int[] arr){
        // 做一个标识,如果发现某次顺序没有发生变化,则说明顺序正确,可以提前停止
        boolean flag=false;
        // 设置临时变量做交换的时候用
        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]) {
                    flag=true;
                    temp = arr[i];
                    arr[i] = arr[i + 1];
                    arr[i + 1] = temp;
                }
            }
            if(!flag){ // 说明在一次循环过程中,代码没有交换过
                break;
            }else {
                flag=false;  // 重置flag进行下次判断
            }
        }
        System.out.println(Arrays.toString(arr));
    }

3. 创建时间格式:

Date date1 = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date1Str = simpleDateFormat.format(date1);
System.out.printf("排序之前的时间:%s",date1Str);

四、选择排序

1. 逻辑

image.png
一共有N-1次大循环;
每次循环有N-i 次对比

2. 代码实现

//选择排序的方法(每次选最小值)
    public static void selectSortFun(int[] arr){

        for (int i = 0; i < arr.length-1; i++) {
            // 设置变量,标记最小值和对应的下标
            int minIndex = i; // 每次假设第i个位置的数据是最小的,不过不是,则需要调换,如果是,则保持不变,进行下一轮
            int minValue = arr[i];
            for (int j = i+1; j < arr.length; j++) {
                if(minValue > arr[j]){
                    minValue=arr[j];
                    minIndex=j;
                }
            }
            // 如果当前的最小值和最小下标就是此次循环的初始设置值,则不做任何改变
            if(minIndex!=i){
                arr[minIndex] = arr[i];  // 第i轮,是和第i个位置的数值进行交换
                arr[i]=minValue;
            }
        }
    }

五、冒泡和选择排序时间复杂度

选择排序比冒泡快了很多;

六、插入排序

1. 核心思想

把原始数组分为2部分,前面为有序的,后面是无序的。所以一般选择第一个元素作为子数组,即有序数组。
在当前数组中,从第二个数开始,和前面的数进行比较,使之放在正确的位置上;
image.png

2. 代码实现

public static void insertSort(int[] arr) {
        int tempValue = 0;
        for (int i = 1; i < arr.length; i++) {
            for (int j = i; j >= 1; j--) {
                if (arr[j] < arr[j - 1]) {
                    tempValue = arr[j];
                    arr[j] = arr[j - 1];
                    arr[j - 1] = tempValue;
                } else {
                    break;
                }
            }
        }
    }


// 方法二:时间复杂度更低
// 中心思想:当前值与之前所有的值都逆序比较,当之前的值大于当前值时,用之前为止的值替换此位置的值(意味着每个位置值只交换一次)
    //插入排序
    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
            // 举例:理解不了,我们一会 debug
            //这里我们判断是否需要赋值
            if(insertIndex + 1 != i) {
                arr[insertIndex + 1] = insertVal;
            }

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

3. 时间复杂度

Ο(_n_2)
问题:当出现较小的值时,该值前面所有的数都得重新调换位置(第二个for循环),影响效率。《——希尔排序

七、希尔排序(缩小增量排序)

1. 核心思想

每次先分组后插入排序
image.png

2. 代码实现(交换法)

public static void shellSortFun(int[] arr){
        int tempValue = 0;
        for(int stepSize = arr.length/2;stepSize>0;stepSize /=2){    // 直到stepSize = 1为止
            for(int i=stepSize;i<arr.length;i++){
                // 每组元素中的下标步长
                for(int j=i-stepSize;j>=0;j-=stepSize){
                    // 如果当前元素大于加上步长后的那个元素,则交换位置
                    if(arr[j]>arr[j+stepSize]){
                        tempValue = arr[j];
                        arr[j]=arr[j+stepSize];
                        arr[j+stepSize]=tempValue;
                    }
                }
            }
        }
    }

3. 更高效的代码实现(移位方法)

//对交换式的希尔排序进行优化->移位法
    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;
                }

            }
        }
    }

八、快速排序

1. 核心思想

是对冒泡排序的改进
第六章 排序算法 - 图12
逻辑思维参考链接:https://www.jb51.net/article/211611.htm
把左边和右边的位置中不合要求的元素对调位置。
然后把变量i\j对应的位置和基准值进行对比,对调位置;

2. 代码实现

public class QuickSort {
    public static void quickSort(int[] arr,int low,int high){
        int i,j,temp,t;
        if(low>high){
            return;
        }
        i=low;
        j=high;
        //temp就是基准位
        temp = arr[low];

        while (i<j) {
            //先看右边,依次往左递减
            while (temp<=arr[j]&&i<j) {
                j--;
            }
            //再看左边,依次往右递增
            while (temp>=arr[i]&&i<j) {
                i++;
            }
            //如果满足条件则交换
            if (i<j) {
                t = arr[j];
                arr[j] = arr[i];
                arr[i] = t;
            }
        }
        //最后将基准为与i和j相等位置的数字交换
         arr[low] = arr[i];
         arr[i] = temp;
        //递归调用左半数组
        quickSort(arr, low, j-1);
        //递归调用右半数组
        quickSort(arr, j+1, high);
    }


    public static void main(String[] args){
        int[] arr = {10,7,2,4,7,62,3,4,2,1,8,9,19};
        quickSort(arr, 0, arr.length-1);
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
    }
}

3. 时间复杂度

速度很快

九、归并排序

1. 核心思想

image.png
image.png
image.pngimage.png

2. 代码实现

package com.atguigu.sort;

import java.util.Arrays;

public class MergeSortDemo {
    public static void main(String[] args) {
        int[] arr =  {9,8,7,6,5,4,3,2,1};
        int[] temp = new int[arr.length];
        mergeSortFun(arr, 0, arr.length-1, temp);
        System.out.println(Arrays.toString(arr));

    }

    // 分+合的方法
    public static void mergeSortFun(int[] arr,int left, int right,int[] temp){
        // step1: 先把原数组拆分成子数组,分别是left和right
        // step2: 子数组形成有序数组
        // step3: 子数组合并成大的完整有序数组
        if(left<right){
            int mid = (left+right)/2;
            // 左递归分解
            mergeSortFun(arr, left, mid, temp);
            // 向右递归进行分解
            mergeSortFun(arr, mid+1, right, temp);
            // 每分解一次就合并一次
            merge(arr, left, mid,right,temp);
        }
    }

    // 合并的方法

    /**
     * @param arr   初始的数组
     * @param left  左边有序子数组初始索引
     * @param mid   右边子数组的前一位的下标值, 也就是left的最后一个元素的下标值
     * @param right 右边有序子数组初始索引
     * @param temp  用于存储的临时有序数组
     */
    public static  void merge(int[] arr, int left, int mid, int right, int[] temp) {
        int i = left;  // 左边有序序列的初始索引
        int j = mid + 1; // 右边有序序列的初始索引
        int t = 0; // 指向temp数组的当前索引

        //step1: 先把左右两边的有序数据逐一取出来进行对比,较小的值进入temp数组,直到左右两边有一边处理完毕
        while (i <= mid && j <= right) {
            // 说明工作还没有结束
            if (arr[i] <= arr[j]) {
                temp[t] = arr[i];
                i++;
            } else {
                temp[t] = arr[j];
                j++;
            }
            t++;
        }

        // step2: 把有剩余的数组的元素全部拷贝到temp中去

        //说明右边的遍历完了,把左边剩余部分直接加入到temp中
        while (i <= mid) {
            temp[t] = arr[i];
            t += 1;
            i += 1;    
        }

        while (j <= right) {
            temp[t] = arr[j];
            t += 1;
            j += 1;    
        }


        // step3: 把temp中的元素全部拷贝到arr中去,替换原来的数据
        // 注意: 是每次把单独的小数组合并到原始的大数组中去
        t = 0;  // temp数组的下标
        int tempLeft = left;   // left是最初传入的,其代表原始数组中元素的下标
        while(tempLeft<=right){
            arr[tempLeft]=temp[t];
            t++;
            tempLeft++;
        }
    }
}

十、基数排序

1. 逻辑思维

image.png

2. 代码实现

step1: 获取个位上的数: element%10
step2: 获取十位数上的数: element/10%10
step3: 获取百位上的数: element/100%10

// 获取最大数的位数
        int max = arr[0];  // 初始化,假设位置为0的数字最大
        // 获取最大值
        for (int value : arr) {
            max = Math.max(max, value);
        }
        // 获取最大值的位数
        int maxLength = (max+"").length();
package com.atguigu.sort;

public class RadixSortDemo {
    public static void main(String[] args) {

    }

    public static void radixSort(int[] arr){
        // 获取最大数的位数
        int max = arr[0];  // 初始化,假设位置为0的数字最大
        // 获取最大值
        for (int value : arr) {
            max = Math.max(max, value);
        }
        // 获取最大值的位数
        int maxLength = (max+"").length();
        // 第一轮(针对个位数上)
        // 定义一个二维数组,表示10个桶,每个桶就是一个一维数组
        /**
         * 1. 二维数组包含10个一维数组
         * 2.为了防止放数的时候数据溢出,每个一维数组的大小设定为arr.length个
         * 3. 所以基数排序是用空间换时间的经典案例
         */
        // 每个桶都需要有个指针,表明当前桶里放了几个有效数据
        int[][] bucket = new int[10][arr.length];
        // 定义一个一维数组,记录实际每个桶放了多少数据
        int[] bucketElementCount = new int[10];

        for (int j = 0,n =1; j < maxLength; j++,n *=10) {
            //j代表第几轮
            for (int i : arr) {
                // 取出每个元素的个位
                int digitOfElement = i/n%10; // 取余
                // 放入到对应的桶中
                bucket[digitOfElement][bucketElementCount[digitOfElement]] = i;
                bucketElementCount[digitOfElement]++;
            }
            // 按照桶的顺序,依次取值,放入到原来的数组中(覆盖掉原数组中的数据)
            int index = 0;
            for (int k = 0; k < bucketElementCount.length; k++) {
                // 如如果桶中有数据,我们才放入到原数组中
                if(bucketElementCount[k]!=0){
                    for (int l = 0; l < bucketElementCount[k]; l++) {
                        // 取出元素放入到arr中
                        arr[index]=bucket[k][l];
                        index++;
                    }
                }
                // 第一轮处理后,需要将每个bucketElementCounts[k]=0,即将桶清空
                bucketElementCount[k]=0;
            }
        }
    }

}

3. 时间复杂度

运算速度非常快,但是消耗内存

十一、排序时间复杂度的比较

image.png