leetcode/notes/src/sorting.md

5.5 KiB

排序算法

在这里规定:

  • 数组是 arr[0...k]
  • 排序是指从左到右从小到大排列
  • 有序区是指数组中的一个子数组(子数组要求连续,子序列不要求连续),该子数组已排好序
  • 无序区是指数组中的一个子数组,该子数组未排好序

插入排序

arr[0...i] 为有序区,arr[i+1...k] 为无序区,有序区长度一开始为 1。

每次排序将 arr[i+1] 插入到有序区中的合适位置。

插入到合适位置是指,插入进去后有序区后面部分的元素后移。

void insertSort(std::vector<int> &v) {
  int len = v.size();
  if (len == 1) return;

  // 有序区 [0...i],无序区 [i+1...len-1]
  for (int i = 0; i < len - 1; ++i) {
    // 找到 v[i+1] 在有序区中的位置
    // 从后往前遍历有序区
    int j = i;
    while (v[i + 1] < v[j]) {
      --j;
    }
    // v[i+1] 应该插入到 v[j+1] 的位置
    // 先把原本的 v[j+1...i] 往后挪一位
    int tmp = v[i + 1];
    for (int x = i; j + 1 <= x; --x) {
      v[x + 1] = v[x];
    }
    // 插入 v[i+1]
    v[j + 1] = tmp;
  }
}

时间复杂度:双重循环,所以为 O(N^2)

空间复杂度:占用内存随着排序总数 n 的增大而等比增大,所以为 O(1)

稳定性:不改变其它元素间的相对位置,所以稳定

冒泡排序

arr[0...i] 为无序区,arr[i+1...k] 为有序区,有序区长度一开始为 0。

每一次遍历无序区中的元素,将最大值放到无序区末尾。

具体是怎么放到末尾的呢?从后往前遍历无序区,比较相邻的两个元素,将大的那个元素往后挪。

void bubbleSort(std::vector<int> &v) {
  int len = v.size();
  if (len == 1) return;

  // 无序区 [0...i],有序区 [i+1...len-1]
  for (int i = len - 1; i >= 0; --i) {
    // 从前往后遍历无序区
    for (int j = 0; j < i; ++j) {
      // 对比相邻的两个元素,把大的那个往后移
      if (v[j] > v[j + 1]) {
        v[j] = v[j] ^ v[j + 1];
        v[j + 1] = v[j] ^ v[j + 1];
        v[j] = v[j] ^ v[j + 1];
      }
    }
  }
}

时间复杂度:双重循环,所以为 O(N^2)

空间复杂度:占用内存随着排序总数 n 的增大而等比增大,所以为 O(1)

稳定性:不改变其它元素间的相对位置,所以稳定

选择排序

arr[0...i] 为无序区,arr[i+1...k] 为有序区,有序区长度一开始为 0。

每一次遍历无序区中的元素,将最大值放到无序区末尾。

具体是怎么放到末尾的呢?从后往前遍历无序区,找到最大值的下标,然后将这个值和无序区末尾元素交换。

void selectionSort(std::vector<int> &v) {
  int len = v.size();
  if (len == 1) return;

  int maxIndex = 0;
  int tmp = v[0];

  // 无序区 [0...i],有序区 [i+1...len-1]
  for (int i = len - 1; i >= 0; --i) {
    maxIndex = 0;
    // 从前往后遍历无序区
    for (int j = 0; j <= i; ++j) {
      // 找到最大值的索引
      if (v[j] > v[maxIndex]) maxIndex = j;
    }
    // 交换最大值和无序区末尾元素
    tmp = v[i];
    v[i] = v[maxIndex];
    v[maxIndex] = tmp;
  }
}

时间复杂度:双重循环,所以为 O(N^2)

空间复杂度:占用内存随着排序总数 n 的增大而等比增大,所以为 O(1)

稳定性:因为会与有序区末尾元素进行交换,改变了元素间的相对位置,所以不稳定

快速排序

选择一个“基准” (pivot),一般选择最左侧元素,比如 pivot = arr[0]

现在我们要做的是,将比 pivot 小的元素放到它的左侧,将比它大的元素放在右侧,排序完后就成了这样:

arr[0...x], pivot, arr[y...k]

其中 arr[0...x] 中的每一个元素都比 pivot 小,arr[y...k] 中的每个元素都比 pivot 大。

但是 arr[0...x]arr[y...k] 是无序的,怎么办呢?

递归调用快排,对 arr[0...x]arr[y...k] 排序。

void quickSort(std::vector<int> &v, int left, int right) {
  if (left >= right) return;
  int pivot = v[left];
  int tmp = 0;
  // 双指针
  // i = left 从左到右
  // j = right 从右到左
  int i = left;
  int j = right;
  while (i < j) {
    // 移动 j
    // 从右往左找到第一个比 pivot 小的 v[j]
    // 此时 v[j+1...k] 都比 pivot 大
    while (i < j && v[j] >= pivot) --j;
    // 把这个数覆盖到左侧 v[i]
    if (i < j) {
      v[i] = v[j];
      ++i;
    }
    // 移动 i
    // 从左往右找到第一个比 pivot 大的 v[i]
    // 此时 v[0...i-1] 都比 pivot 小
    while (i < j && v[i] <= pivot) ++i;
    // 把这个数覆盖到右侧 v[j]
    if (i < j) {
      v[j] = v[i];
      --j;
    }
  }
  // 出循环后 i == j
  // 此时左侧和右侧都已经处理完毕
  // 接下来把 pivot 放到 v[i] 即可
  v[i] = pivot;
  // 递归处理左侧
  quickSort(v, left, i - 1);
  // 递归处理右侧
  quickSort(v, i + 1, right);
}

时间复杂度:最坏情况下为O(N^2),平均时间复杂度为O(N·logN)

空间复杂度: 和时间复杂度相关,每次递归需要的空间是固定的,总体空间复杂度即为递归层数,因此平均/最好空间复杂度为 O(logN),最坏空间复杂度为 O(N)

稳定性:不稳定

桶排序

一看就懂的原理

时间复杂度:取决于桶数量和数据分散程度,最好情况下可以达到O(N),最坏情况下为O(N^2),平均O(N+K)

空间复杂度:最坏O(N·K)

稳定性:稳定