三个基本排序就是这么简单~

129 阅读3分钟

直接插入排序

基本思路:

  1. 定义两个指针 i 和 j , 还得有一个临时变量 temp
  2. i 从 1 下标开始遍历数组, j 每次从 i - 1 的下标开始往前遍历, temp 每次一开始存储的是 i 下标的元素
  3. j 每次往前走都判断 temp 的值和 j 下标的值, 如果 j 下标的值比 temp 的值大就将 j 下标的值放入 j+1 下标
  4. j 遍历结束或者 j 下标的值比 temp 的值小 , 当前的插入排序就结束

图解

插入第一个元素

插入第二个元素

插入第三个元素

插入第四个元素

代码

public void insertSort(int[] arr) {
    if (arr == null || arr.length < 1) {
        return;
    }
    for (int i = 1; i < arr.length; i++) {
        int temp = arr[i];
        int j = i - 1;
        for (; j >= 0; j--) {
            if (arr[j] > temp) {
                arr[j + 1] = arr[j];
            } else {
                // arr[j + 1] = temp;
                // 只要j回退的时候,遇到了 比temp小的元素就可以结束这次的比较
                // 因为经过前面的比较, 前面的元素已经保持有序的状态, 不需要在进行比较了
                break;
            }
        }
        // j回退到了 < 0 的地方
        arr[j + 1] = temp;
    }
}

分析

空间复杂度: O(1)

时间复杂度: O(N ^ 2)

  • 对于直接插入排序来说最好的情况就是数据有序的时候, 数组越有序就越快, 这是直接插入排序的一大特点
  • 因此插入排序的最好的情况下的时间复杂度为: O(N)

稳定性: 稳定的

  • 一个稳定的排序,可以实现为不稳定的排序
  • 但是一个本身就不稳定的排序,是不可以变成稳定的排序的
  • 因此直接插入排序经常使用在 数据量不多 且 整体数据 趋于有序了的场景下

选择排序

基本思路:

​ 每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完

  1. 定义两个指针 i 和 j
  2. i 遍历数组, j 就遍历 i + 1 后面的所有元素, 找到最小值
  3. 然后让这个最小值和 i 下标的元素交换 , i++ 继续在剩余元素找到最小值, 再交换, 直到 i 遍历完数组~

总结:

​ 在元素集合array[i]--array[n-1]中选择关键码最大(小)的数据元素若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换在剩余的array[i]--array[n-2](array[i+1]--array[n-1])集合中,重复上述步骤,直到集合剩余1个元素

代码

public void selectSort(int[] arr) {
    if (arr == null || arr.length < 1) {
        return;
    }
    for (int end = 0; end < arr.length; end++) {
        // 定义一个遍历来维护最小值下标
        int minIndex = end;
        for (int second = end + 1; second < arr.length; second++) {
            // 每次从当前的 end 下标的第二给元素开始往后遍历, 寻找最小值并更新 minIndex 下标
            // 这样一来每次的 0 ~ end 区间的元素就确定是有序的了
            minIndex = arr[second] < arr[minIndex] ? second : minIndex;
        }
        // 让当前的 end	 下标和 最小值下标交换
        swap(arr, end, minIndex);
    }
}

private void swap(int[] arr, int j, int i) {
    int temp = arr[j];
    arr[j] = arr[i];
    arr[i] = temp;
}

分析

空间复杂度: O(N^2)

时间复杂度: O(1)

稳定性: 不稳定的排序

冒泡排序

思路: 让 j 这个指针遍历从前往后遍历, 相邻的两个元素两两比较, 满足条件就交换~

代码1

// 未优化版本
public void bubbleSort(int[] arr) {
    if (arr == null || arr.length < 1) {
        return;
    }
    for (int i = 0; i < arr.length; i++) {
        // 冒泡的趟数
        for (int j = 0; j < arr.length - 1; j++) {
            // 每一趟所要比较的次数
            // 每次的比较都让 j 和 j+1 比较, 满足条件就交换, 然后 j 继续往后走
            if (arr[j] > arr[j + 1]) {
                swap(arr, j, j+1);
            }
        }
    }
}

private void swap(int[] arr, int j, int i) {
        int temp = arr[j];
        arr[j] = arr[i];
        arr[i] = temp;
}

代码2

使用 标志位 优化~

// 优化版本
public void bubbleSort2(int[] arr) {
    if (arr == null || arr.length < 1) {
        return;
    }
    for (int i = 0; i < arr.length; i++) {
        boolean flag = false;
        // 冒泡的趟数 i
        for (int j = 0; j < arr.length - 1 - i; j++) {
            // 每一趟所要比较的次数 j
            // 每次的比较都让 j 和 j+1 比较, 满足条件就交换, 然后 j 继续往后走
            // 每一趟都确定了一个最大值, 因此 j 就可以不用遍历数组的全部元素
            // 只需在上一趟的基础上减少一次比较
            if (arr[j] > arr[j + 1]) {
                swap(arr, j, j+1);
                flag = true;
            }
        }
        if (flag == false) {
            // 如果 flag 没变说明没有进入 if 语句, 整体有序不需要排序
            break;
        }
    }
}

private void swap(int[] arr, int j, int i) {
    int temp = arr[j];
    arr[j] = arr[i];
    arr[i] = temp;
}

分析

空间复杂度: O(1)

时间复杂度: O(N ^ 2)

  • 对于 代码1 来说, 无论是最好情况下还是最坏情况下, 它的时间复杂度都是 O(N ^ 2)
  • 对于 代码2 来说, 最好情况下是 O(N) , 最坏情况下 O(N ^ 2)

稳定性: 稳定的排序

堆排序算法有兴趣的童鞋还可以看看下面这篇文章~

四个进阶排序,入门看这个就够了