diff --git a/notes/src/SUMMARY.md b/notes/src/SUMMARY.md index abfc0f3..9667a76 100644 --- a/notes/src/SUMMARY.md +++ b/notes/src/SUMMARY.md @@ -3,14 +3,14 @@ # 数组 - [总结](./array.md) -- [二分查找](./bin_search.md) +- [二分查找 ⭐](./bin_search.md) - [移除元素](./remove_elements.md) - [长度最小的子数组](./minimum_size_subarray_sum.md) - [三数相加](./three_sum.md) # 链表 -- [总结](./linked_list.md) +- [总结 ⭐](./linked_list.md) - [环形链表](./linked_list_cycle.md) # 哈希表 @@ -70,4 +70,5 @@ # 经典代码 +- [排序算法](./sorting.md) - [合并两个有序链表](./merge_two_sorted_linked_lists.md) diff --git a/notes/src/sorting.md b/notes/src/sorting.md new file mode 100644 index 0000000..8f538d9 --- /dev/null +++ b/notes/src/sorting.md @@ -0,0 +1,190 @@ +# 排序算法 + +在这里规定: + +- 数组是 `arr[0...k]` +- 排序是指从左到右从小到大排列 +- 有序区是指数组中的一个子数组(子数组要求连续,子序列不要求连续),该子数组已排好序 +- 无序区是指数组中的一个子数组,该子数组未排好序 + +## 插入排序 + +`arr[0...i]` 为有序区,`arr[i+1...k]` 为无序区,有序区长度一开始为 1。 + +每次排序将 `arr[i+1]` 插入到有序区中的合适位置。 + +插入到合适位置是指,插入进去后有序区后面部分的元素后移。 + +```cpp +void insertSort(std::vector &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。 + +每一次遍历无序区中的元素,将最大值放到无序区末尾。 + +具体是怎么放到末尾的呢?从后往前遍历无序区,比较相邻的两个元素,将大的那个元素往后挪。 + +```cpp +void bubbleSort(std::vector &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。 + +每一次遍历无序区中的元素,将最大值放到无序区末尾。 + +具体是怎么放到末尾的呢?从后往前遍历无序区,找到最大值的下标,然后将这个值和无序区末尾元素交换。 + +```cpp +void selectionSort(std::vector &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]` 排序。 + +```cpp +void quickSort(std::vector &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)** + +稳定性:**不稳定** + +## 桶排序 + +[一看就懂的原理](https://www.cs.usfca.edu/~galles/visualization/BucketSort.html) + +时间复杂度:取决于桶数量和数据分散程度,最好情况下可以达到**O(N)**,最坏情况下为**O(N^2)**,平均**O(N+K)** + +空间复杂度:最坏**O(N·K)** + +稳定性:**稳定**