Add sorting.md
This commit is contained in:
parent
4d93e5a150
commit
5c03c9e3f2
@ -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)
|
||||
|
190
notes/src/sorting.md
Normal file
190
notes/src/sorting.md
Normal file
@ -0,0 +1,190 @@
|
||||
# 排序算法
|
||||
|
||||
在这里规定:
|
||||
|
||||
- 数组是 `arr[0...k]`
|
||||
- 排序是指从左到右从小到大排列
|
||||
- 有序区是指数组中的一个子数组(子数组要求连续,子序列不要求连续),该子数组已排好序
|
||||
- 无序区是指数组中的一个子数组,该子数组未排好序
|
||||
|
||||
## 插入排序
|
||||
|
||||
`arr[0...i]` 为有序区,`arr[i+1...k]` 为无序区,有序区长度一开始为 1。
|
||||
|
||||
每次排序将 `arr[i+1]` 插入到有序区中的合适位置。
|
||||
|
||||
插入到合适位置是指,插入进去后有序区后面部分的元素后移。
|
||||
|
||||
```cpp
|
||||
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。
|
||||
|
||||
每一次遍历无序区中的元素,将最大值放到无序区末尾。
|
||||
|
||||
具体是怎么放到末尾的呢?从后往前遍历无序区,比较相邻的两个元素,将大的那个元素往后挪。
|
||||
|
||||
```cpp
|
||||
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。
|
||||
|
||||
每一次遍历无序区中的元素,将最大值放到无序区末尾。
|
||||
|
||||
具体是怎么放到末尾的呢?从后往前遍历无序区,找到最大值的下标,然后将这个值和无序区末尾元素交换。
|
||||
|
||||
```cpp
|
||||
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]` 排序。
|
||||
|
||||
```cpp
|
||||
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)**
|
||||
|
||||
稳定性:**不稳定**
|
||||
|
||||
## 桶排序
|
||||
|
||||
[一看就懂的原理](https://www.cs.usfca.edu/~galles/visualization/BucketSort.html)
|
||||
|
||||
时间复杂度:取决于桶数量和数据分散程度,最好情况下可以达到**O(N)**,最坏情况下为**O(N^2)**,平均**O(N+K)**
|
||||
|
||||
空间复杂度:最坏**O(N·K)**
|
||||
|
||||
稳定性:**稳定**
|
Loading…
Reference in New Issue
Block a user