AcWing学习记录
钱都花了不学也太亏了
两种排序算法
一、快排
快排的思路:
- 确定分界值(一般选择q[(l + r) / 2])
- 双指针扫描
- 递归处理左右两段
注:
1.一次快排的结果,就是比q[l + r >> 1]小的数都在左边,比q[l + r >> 1]大的数都在右边。
根据这样的结果可以实现一个“分类“,记得大二数据结构的期末考题就是用O(nlog(n))的算法实现对给出的字母序列进行大小写字母划分。这道题的思想就是快排。
2.双指针扫描的过程就是让 i 和 j 分别指向数组首尾,分别向中间扫。i 遇到比分界值大的就停止,j 遇到比分界值小的就停止,然后 i 和 j 指向的数值交换,并继续往后扫,直到i >= j
为什么这样的过程就可以实现分类呢?因为在这样的过程下 i 左边的数一定比分界值小,j 右边的数一定比分界值大。i 和 j 相遇后,自然就实现了一次划分。这就是优雅的原理。
3.给一个例子:
4 3 2 1
假设我们取分界值为3,那么 i 、j 一开始分别指向4和1。4比3大,i 停下。1比3小,j也停下。此时 i 小于 j ,所以 i 和 j 互换,结果是1 3 2 4
因为 i 仍小于 j,所以继续:i 指向1,比3小,i++;i指向3,不比3小,i停下。j指向4,比3大,j–; j指向2,比3小,j停下。此时 i 小于 j ,所以 i 和 j 互换,结果是1 2 3 4
因为 i 仍小于 j,所以继续:i指向2,比3小,i++; i指向3,不比3小,i停下。j指向3,不比3大,j停下。此时 i 已经大于 j,不互换。
因为 i 大于 j,所以可以停了, 一次快排的结果是1 2 3 4(一次就排好实属巧合)。
(板子中是do while,是先++或–再判断。和上面表述略有一点点不同。)
快排板子如下:
void quick_sort(int q[], int l, int r){
if(l >= r) return;
//确定分界值
int x = q[l + r >> 1];
//双指针
int i = l - 1, j = r + 1;
while(i < j){
do i++; while(q[i] < x); //先+1再判断
do j--; while(q[j] > x);
if(i < j) swap(q[i], q[j]);
}
quick_sort(q, l, j);
quick_sort(q, j+1, r);
}
AcWing 785 代码如下:
#include<iostream>
using namespace std;
const int N = 100010;
int n;
int q[N];
void quick_sort(int q[], int l, int r){
if(l >= r) return;
int i = l - 1, j = r + 1, x = q[l + r >> 1];
while(i < j){
do i++; while(q[i] < x); //先+1再判断
do j--; while(q[j] > x);
if(i < j) swap(q[i], q[j]);
}
quick_sort(q, l, j);
quick_sort(q, j+1, r);
}
int main(){
scanf("%d", &n);
for(int i = 0; i < n; i++) scanf("%d", &q[i]);
quick_sort(q, 0, n-1);
for(int i = 0; i < n; i++) printf("%d ", q[i]);
return 0;
}
当然,上面那道题在main函数里直接用下面的代码也是没问题的。
sort(q, q+n)
二、归并排序
归并排序的思路:
- 确定分界点(一般选用(l + r) / 2)(快排是确定分界值)
- 递归排序左边右边
- 归并(合二为一)
注:
1.归并的原理是这样的:(也是优雅的双指针)
两个有序序列,两个指针分别指向序列首部。
每次比谁小,谁小就取谁出来,到一个新的数组中。这样两个指针各扫一遍,出来的新数组就是想要的新有序序列。
2.快排是整体先排,然后小的再排。而归并排序是每一次归并的两个序列必须是有序的,因此归并排序需要从小的序列开始。这在代码段中会有所体现。
3.给一个例子:
4 3 2 1:处理4 3、2 1。
4 3:处理4、3,顿号前后已经是一个数,不用再递归了,根据注1的原理排序,结果是3 4
2 1:处理2、1,顿号前后已经是一个数,不用再递归了,根据注1的原理排序,结果是1 2
处理4 3、2 1的结果是3 4、1 2,注意观察顿号前后分别是两个有序序列(递归处理好的结果),根据注1的原理排序,结果是1 2 3 4
感觉说的很乱,建议画图,递归这里不太好说清楚。
归并排序板子如下:
void merge_sort(int q[], int l, int r){
if(l >= r) return;
int mid = l + r >> 1; //分界点
merge_sort(q, l, mid);
merge_sort(q, mid + 1, r);
int k = 0;
int i = l, j = mid + 1; //双指针
while(i <= mid && j <= r){
if(q[i] <= q[j]) t[k ++] = q[i ++];
else t[k ++] = q[j ++];
}
while(i <= mid) t[k ++] = q[i ++];
while(j <= r) t[k ++] = q[j ++];
for(i = l, j = 0; i <= r; i++, j++) q[i] = t[j];
}
AcWing 788 逆序对的数量 代码如下:
这道题我的理解:
归并排序的思路就是两个有序数组归并的过程,谁小谁先进。
如果前面的先进就不存在逆序;
如果后面的先进,前面有多少个没进就有多少个逆序对。
#include<iostream>
using namespace std;
const int N = 100010;
int n;
//int res = 0;
long long res = 0;
int q[N], t[N];
void merge_sort(int q[], int l, int r){
if(l >= r) return;
int mid = l + r >> 1;
merge_sort(q, l, mid);
merge_sort(q, mid + 1, r);
int k = 0, i = l, j = mid + 1;
while(i <= mid && j <= r){
if(q[i] <= q[j]) t[k ++] = q[i ++];
else{
t[k ++] = q[j ++];
res += mid - i + 1;
}
}
while(i <= mid) t[k ++] = q[i ++];
while(j <= r) t[k ++] = q[j ++];
for(i = l, j = 0; i <= r; i ++, j ++) q[i] = t[j];
}
int main(){
scanf("%d", &n);
for(int i = 0; i < n; i++) scanf("%d", &q[i]);
merge_sort(q, 0, n-1);
//printf("%d", res);
printf("%lld", res);
return 0;
}
本质上就只需要在else语句(q[ i ] > q[ j ])后面加上这样一句就可以:
res += mid - i + 1;
下一个会整理二分。