c++内置函数:
正序:sort(arr, arr+n) / sort(vec.begin(), vec.end())
逆序:sort(arr, arr + n, greater<int>()) / sort(vec.begin(), vec.end(), greater<int>())
快速排序--分治思想
1、确定分界点q[left] q[right] q[(left+right)/2] q[random]
对于分界点的选取,建议选择q[(left+right)/2]
if(l >= r) return;
//递归时使用j做边界,则不能取arr[r]作为初始边界值
//原理同下
int mid = arr[l];
int i = l - 1, j = r + 1;
while(i < j){
while(arr[++i] < mid);
while(arr[--j] > mid);
if(i < j)
swap(arr[i], arr[j]);
}
quicksort(arr, l, j);
quicksort(arr, j+1, r);
//递归时使用i做边界,则不能取arr[l]作为初始边界值
/*以传入边界为[0,1]为例,以arr[l]作为边界值,会导致左侧为空集
右侧仍为[0,1],则每次递归区间无变化,会无限递归*/
int mid = arr[r];
int i = l - 1, j = r + 1;
while(i < j){
while(arr[++i] < mid);
while(arr[--j] > mid);
if(i < j)
swap(arr[i], arr[j]);
}
quicksort(arr, l, i - 1);
quicksort(arr, i, r);
2、调整范围使得左侧数据都小于x 右侧数据都大于x
3、递归左右两端
//快速排序函数,排序数组arr在区间[l,r]内元素
void quicksort(int arr[], int l, int r){
//基准条件:如果l>=r,说明区间无效或只有一个元素,直接返回
if(l >= r) return;
//选择中间元素作为基准值
int mid = arr[(l + r) >> 1];
// i 初始化为 l - 1, j 初始化为 r + 1
int i = l - 1,j = r + 1;
//进行分区操作,将小于基准值的元素移动到左边,大于基准值的元素移动到右边
while(i < j){
// 从左到右找到第一个不小于 mid 的元素
while(arr[++i] < mid);
// 从右到左找到第一个不大于 mid 的元素
while(arr[--j] > mid);
// 如果 i 小于 j,则交换 arr[i] 和 arr[j]
if(i < j)
swap(arr[i], arr[j]);
}
//递归左半部分进行排序
quicksort(arr, l, j);
//递归右半部分进行排序
quicksort(arr, j+1, r);
}
归并排序--分治思想
归并排序相比于快速排序需要开辟额外的空间去暂存排好序的数据,最后再将排好序的数据复制到原数组
1、确定分界点 mid=(l+r)/2
2、递归排序左右两边
3、归并,将两个有序数组合并为一个有序数组
const int N = 1e5 + 10;
//定义临时数组,用于合并时存储排序结果
int temp[N], arr[N];
//归并排序函数,排序数组arr在区间[l,r]内的元素
void mergesort(int arr[], int l, int r){
//基准条件:如果l>=r,说明区间无效或只有一个元素,直接返回
if(l >= r) return;
//确定分界点mid
int mid = (l + r) >> 1;
mergesort(arr, l, mid);//递归排序左半部分
mergesort(arr, mid + 1, r);//递归排序右半部分
//初始化合并过程中的指针
int i = l, j = mid + 1,k = 0;
//合并有序数组
while(i <= mid && j <= r){
if(arr[i] <= arr[j])
temp[k++] = arr[i++];
else
temp[k++] = arr[j++];
}
//将剩余数据存入临时数组
while(i <= mid) temp[k++] = arr[i++];
while(j <= r) temp[k++] = arr[j++];
//将合并后的结果拷贝到原数组相应位置
for(i = l, j = 0; i <= r; i++, j++)
arr[i] = temp[j];
}
归并排序求逆序对
typedef long long ll;
ll ans; // 全局变量,用于存储逆序对的数量
const int N = 1e7 + 10;
int a[N], temp[N]; // 定义两个数组,一个用于存储待排序的数组,另一个用于临时存储合并结果
void merge_sort(int l, int r) {
if (l >= r) return; // 如果子数组的左右边界重合或交叉,说明子数组已经有序,直接返回
int mid = l + r >> 1; // 计算数组中点,将数组分为两部分
merge_sort(l, mid); // 递归排序左半部分
merge_sort(mid + 1, r); // 递归排序右半部分
int i = l, j = mid + 1, k = 1; // 初始化指针,i指向左半部分起点,j指向右半部分起点,k指向临时数组起点
while (i <= mid && j <= r) { // 合并两个有序数组
if (a[i] <= a[j])
temp[k++] = a[i++]; // 如果左半部分当前元素小于等于右半部分当前元素,将左半部分元素复制到临时数组
else {
ans += mid - i + 1; // 如果右半部分当前元素小于左半部分当前元素,说明右半部分元素与左半部分所有剩余元素都构成逆序对
temp[k++] = a[j++]; // 将右半部分当前元素复制到临时数组
}
}
while (i <= mid) temp[k++] = a[i++]; // 将左半部分剩余元素复制到临时数组
while (j <= r) temp[k++] = a[j++]; // 将右半部分剩余元素复制到临时数组
for (i = l, k = 1; i <= r; i++) a[i] = temp[k++]; // 将排序后的临时数组复制回原数组
}
代码的功能概述:
- 该代码实现了一个归并排序算法,并在排序过程中计算数组中的逆序对数量。
merge_sort
函数通过递归的方式将数组分为左右两部分,对两部分分别排序后再合并,同时在合并过程中统计逆序对的数量。- 逆序对的数量通过全局变量
ans
累积。
归并排序求逆序对思路:
利用归并排序求逆序对数量的思路基于归并排序的特性,即在排序的过程中,将两个已经排序的子数组进行合并。在这个合并的过程中,可以很方便地统计逆序对的数量。
逆序对的定义:
在一个数组中,如果存在两个元素 a[i]
和 a[j]
,且 i < j
并且 a[i] > a[j]
,则称 (i, j)
是一个逆序对。
思路解析:
- 分治思想:
-
- 归并排序是基于分治法的排序算法。它将数组递归地分成两部分,直到每个子数组的长度为1(或0)。显然,这样的子数组是有序的。
- 然后,通过合并有序子数组来恢复整个数组的有序性。在合并的过程中,可以利用两个有序子数组之间的顺序关系来统计逆序对的数量。
- 合并过程中统计逆序对:
-
- 在合并两个子数组时,我们用两个指针分别指向左子数组和右子数组的开头。每次比较两个指针所指元素的大小。
- 如果左子数组当前元素小于等于右子数组当前元素,则说明此时没有逆序对,可以直接将左子数组的元素添加到结果数组中。
- 如果左子数组当前元素大于右子数组当前元素,则说明左子数组当前元素及其之后的所有元素(因为子数组本身是有序的)都与右子数组的这个元素构成了逆序对。这时我们就可以统计出逆序对的数量,即为左子数组剩余元素的个数。
- 递归地处理:
-
- 对于每个子问题,通过递归处理左右两个子数组,统计它们各自的逆序对数量。
- 在合并两个有序子数组时,除了将其合并成一个有序数组外,还统计并累加跨两个子数组的逆序对数量。
- 整体逆序对数量:
-
- 递归处理后,整个数组的逆序对数量即为左子数组的逆序对数量、右子数组的逆序对数量和合并过程中跨子数组的逆序对数量之和。
归并排序求逆序对的时间复杂度:
归并排序的时间复杂度是 O(n log n)
,其中 n
是数组的大小。在求逆序对的过程中,我们只是在合并过程中多做了一些操作来统计逆序对的数量,因此整个算法的时间复杂度仍然是 O(n log n)
。这比直接暴力计算所有逆序对数量的 O(n^2)
的方法要高效得多。