快速排序、归并排序(归并逆序对算法)

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. 分治思想
    • 归并排序是基于分治法的排序算法。它将数组递归地分成两部分,直到每个子数组的长度为1(或0)。显然,这样的子数组是有序的。
    • 然后,通过合并有序子数组来恢复整个数组的有序性。在合并的过程中,可以利用两个有序子数组之间的顺序关系来统计逆序对的数量。
  1. 合并过程中统计逆序对
    • 在合并两个子数组时,我们用两个指针分别指向左子数组和右子数组的开头。每次比较两个指针所指元素的大小。
    • 如果左子数组当前元素小于等于右子数组当前元素,则说明此时没有逆序对,可以直接将左子数组的元素添加到结果数组中。
    • 如果左子数组当前元素大于右子数组当前元素,则说明左子数组当前元素及其之后的所有元素(因为子数组本身是有序的)都与右子数组的这个元素构成了逆序对。这时我们就可以统计出逆序对的数量,即为左子数组剩余元素的个数。
  1. 递归地处理
    • 对于每个子问题,通过递归处理左右两个子数组,统计它们各自的逆序对数量。
    • 在合并两个有序子数组时,除了将其合并成一个有序数组外,还统计并累加跨两个子数组的逆序对数量。
  1. 整体逆序对数量
    • 递归处理后,整个数组的逆序对数量即为左子数组的逆序对数量、右子数组的逆序对数量和合并过程中跨子数组的逆序对数量之和。

归并排序求逆序对的时间复杂度:

归并排序的时间复杂度是 O(n log n),其中 n 是数组的大小。在求逆序对的过程中,我们只是在合并过程中多做了一些操作来统计逆序对的数量,因此整个算法的时间复杂度仍然是 O(n log n)。这比直接暴力计算所有逆序对数量的 O(n^2) 的方法要高效得多。

  • 16
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值