力扣算法 Java 刷题笔记【十大排序算法】代码实现

文章目录

  • 稳定排序&不稳定排序
  • 1. 快速排序
    • 复杂度分析:
    • 例题:
  • 2. 归并排序
  • 3. 插入排序
  • 4. 冒泡排序

Arrays.sort()的底层实现

练习:912. 排序数组

稳定排序&不稳定排序

对于序列中的相同元素,如果排序之后它们的相对位置没有发生改变,则称该排序算法为「稳定排序」,反之则为「不稳定排序」
应用:
如果单单排序 int 数组,那么稳定性没有什么意义。但如果排序一些结构比较复杂的数据,那么稳定性排序就有更大的优势了。

比如说你有若干订单数据,已经按照订单号排好序了,现在你想对订单的交易日期再进行排序:

如果用稳定排序算法(比如归并排序),那么这些订单不仅按照交易日期排好了序,而且相同交易日期的订单的订单号依然是有序的。

但如果你用不稳定排序算法(比如快速排序),那么虽然排序结果会按照交易日期排好序,但相同交易日期的订单的订单号会丧失有序性。

在实际工程中我们经常会将一个复杂对象的某一个字段作为排序的 key,所以应该关注编程语言提供的 API 底层使用的到底是什么排序算法,是稳定的还是不稳定的,这很可能影响到代码执行的效率甚至正确性。

1. 快速排序

2022/2 - 7/27

算法思想:以最左边的数字为基准数,两个哨兵分别位于数组的左右两端,右端的先出发,遇到比基准数小或相等的就停下,此时左边哨兵出发,遇到比基准数大的的就停下,然后交换两个数字的,重复这个过程,直到两个哨兵相遇,此时将相遇时的数字与基准数交换,这样基准数左边都是比基准数小或相等的,右边都是比基准数大的,分别在左右两部分做递归操作即可;

void sort(int[] nums, int lo, int hi) {
    if (lo >= hi) {
        return;
    }

    int p = partition(nums, lo, hi);
    sort(nums, lo, p - 1);
    sort(nums, p + 1, hi);
}

int partition(int[] nums, int lo, int hi) {
    int pivot = nums[lo];
    int i = lo + 1, j = hi;

    while (i <= j) {                              // 注意边界
        while (j > lo && nums[j] > pivot) {    
            j--;
        }
        while (i < hi && nums[i] <= pivot) {
            i++;
        }
        if (i >= j) {
            break;
        }
        swap(nums, i, j);
    }
    swap(nums, lo, j);
    return j;
}

void shuffle(int[] nums) {
    Random rand = new Random();
    int n = nums.length;
    for (int i = 0; i < n; i++) {
        swap(nums, i, i + rand.nextInt(n - i));
    }
}

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

复杂度分析:

partition 执行的次数是二叉树节点的个数,每次执行的复杂度就是每个节点代表的子数组 nums[lo…hi] 的长度,所以总的时间复杂度就是整棵树中「数组元素」的个数。

假设数组元素个数为 N,那么二叉树每一层的元素个数之和就是 O(N);分界点分布均匀的理想情况下,树的层数为 O(logN),所以理想的总时间复杂度为 O(NlogN)。

由于快速排序没有使用任何辅助数组,所以空间复杂度就是递归堆栈的深度,也就是树高 O(logN)。

快速排序的效率存在一定随机性,如果每次 partition 切分的结果都极不均匀:
快速排序就退化成选择排序了,树高为 O(N),每层节点的元素个数从 N 开始递减,总的时间复杂度为:

N + (N - 1) + (N - 2) + … + 1 = O(N^2)
所以我们说,快速排序理想情况的时间复杂度是 O(NlogN),空间复杂度 O(logN),极端情况下的最坏时间复杂度是 O(N^2),空间复杂度是 O(N)。

例题:

力扣算法 Java 刷题笔记【快速排序】hot100(一)快速排序 快速选择算法 1

2. 归并排序

归并排序是建立在归并操作的一种高效的排序方法,该方法采用了分治的思想,比较适用于处理较大规模的数据,但比较耗内存

2022/2/22 - 8/5

class Solution {
    public int[] sortArray(int[] nums) {
        int[] temp = new int[nums.length];
        mergeSort(nums, temp, 0, nums.length - 1);
        return nums;
    }

    void mergeSort(int[] nums, int[] temp, int lo, int hi) {
        if(lo == hi) {
            return;
        }
        if (lo < hi) {
            int middle = lo + (hi - lo) / 2;
            mergeSort(nums, temp, lo, middle);
            mergeSort(nums, temp, middle + 1, hi);
            merge(nums, temp, middle, lo, hi);
        }
    }

    void merge(int[] nums, int[] temp, int middle, int lo, int hi) {
        int i = lo, j = middle + 1;
        for (int k = lo; k <= hi; k++) {
            if (i > middle) {
                temp[k] = nums[j];
                j++;
            } else if (j > hi) {
                temp[k] = nums[i];
                i++;
            } else if (nums[i] <= nums[j]) {
                temp[k] = nums[i];
                i++;
            } else {
                temp[k] = nums[j];
                j++;
            }
        }

        for (int p = lo; p <= hi; p++) {
            nums[p] = temp[p];
        }
    }
}

3. 插入排序

适用处理数据量比较少或者部分有序的数据

  • 用一个数组存储要排序的数据(无序)
  • 用for循环从前到后遍历整个数组,将无序元素一个一个地插入到正确的位置(排好序的位置),第一个元素我认为它是排好序的,所以我从第二个元素开始遍历
  • 用一个临时变量把待插元素(将要插入到有序集合的元素)存起来,然后逐个和有序集合里的元素比较,如果集合里的元素大于待插元素,就将它向后移动一个单元,这样当遇到有序集合中小于等于待插元素的元素时就有地方放待插元素了

2022/7 - 8/5

class Solution {
    public int[] sortArray(int[] nums) {
        for (int i = 1; i < nums.length; i++) {
            insertSort(nums, i);
        }
        return nums;
    }

    void insertSort(int[] nums, int i) {
        int temp = nums[i];
        int j = i - 1;
        for (; j >= 0 && nums[j] > temp; j--) {
            nums[j + 1] = nums[j];
        }
        nums[j + 1] = temp;
    }
}

4. 冒泡排序

从第一个石子开始,让它和右边相邻的石子进行比较,如果左边的石子大于右边的石子,那么就交换两个石子的位置,(也可以左小于右交换,这里采用大于交换),这样每比较一次,大的就跑到右边,直到跑到最右边

  • 第一层循环是控制趟数
  • 第二层就是控制你第 i+1趟(因为i从0开始)所比较的次数,第 i +1 趟比较了 N – 1 - i 次

2022/8/22

class Solution {
    public int[] sortArray(int[] nums) {
        bubbleSort(nums);
        return nums;
    }

    void bubbleSort(int[] nums) {
        if (nums == null || nums.length < 2) {
            return;
        }

        int temp = 0;
        for (int i = 0; i < nums.length - 1; i++) {
            boolean flag = true;
            for (int j = 0; j < nums.length - 1 - i; j++) {
                if (nums[j] > nums[j + 1]) {
                    temp = nums[j];
                    nums[j] = nums[j + 1];
                    nums[j + 1] = temp;
                    flag = false;
                }
            }

            if (flag) {
                break;
            }
        }
    }
}

在这里插入图片描述

你可能感兴趣的:(数据结构与算法,算法,排序算法,java)