C语言算法学习:求逆序对数量

👋 欢迎来到“C语言算法学习:求逆序对数量”篇!

在排序算法和数据结构领域,逆序对是一个非常重要的概念。简单来说,逆序对指的是数组中一对元素,它们满足数组中的前一个元素比后一个元素大。逆序对的数量在排序、数据处理和某些算法问题中有着广泛的应用。本篇博客将介绍如何通过C语言高效地求解逆序对的数量,并探讨优化方法。

💡 逆序对简介

逆序对是数组中一对不符合升序排列的元素对。举个例子,假设数组为 [3, 1, 2, 4],那么 (3, 1)(3, 2) 就是逆序对,因为它们的位置满足条件:前面的元素比后面的元素大。总的来说,求逆序对的数量就是要找出数组中所有这样的“不和谐”数对。

逆序对不仅在理论上有很多应用,还经常出现在排序算法中。例如,在归并排序过程中,如果元素 a[i]a[j] 大,那么就产生了 mid - i + 1 个逆序对,其中 mid 是当前分割点。


💡 思路分析

在计算逆序对时,最直观的方法是使用暴力法,这将导致 O(n^2) 的时间复杂度,但我们希望能够通过更高效的方法解决这个问题。

归并排序 是一种自然的解决方案,通过在排序的过程中记录逆序对的数量,我们能够将时间复杂度降低到 O(n log n)

在归并排序的过程中,当我们将两个子数组合并时,如果左边的元素大于右边的元素,就意味着这些元素之间形成了逆序对。具体来说,对于数组 A[l...m]A[m+1...r],当元素 A[i](来自左子数组)大于元素 A[j](来自右子数组)时,所有从 im 的元素都与 A[j] 构成逆序对。


💻 代码实现

一、暴力求解

暴力法的基本思路:

暴力法直接遍历数组中的每一对元素,判断是否构成逆序对。具体来说,对于每一对 (i, j),我们检查是否满足条件 i < jA[i] > A[j]。如果满足条件,就算作一个逆序对。

暴力求解算法:
  1. 使用两层循环遍历数组。
  2. 对每一对元素 (i, j),如果 A[i] > A[j],就将逆序对数量加1。
  3. 最后输出逆序对的总数。
C语言代码实现(暴力法):
#include <stdio.h>
#define MAXN 100000

int arr[MAXN]; // 原始数组
int n; // 数组长度

int count_inversions() {
    int inversions = 0;
    for (int i = 0; i < n; i++) {
        for (int j = i + 1; j < n; j++) {
            if (arr[i] > arr[j]) {
                inversions++; // 如果 arr[i] > arr[j],就算作逆序对
            }
        }
    }
    return inversions;
}

int main() {
    scanf("%d", &n); // 输入数组长度
    for (int i = 0; i < n; i++) {
        scanf("%d", &arr[i]); // 输入数组元素
    }

    int result = count_inversions(); // 计算逆序对数量
    printf("%d\n", result); // 输出结果
    return 0;
}
方法二:归并排序中统计逆序对
基本思路:
  • 归并排序在合并两个有序子数组时,如果左边的元素大于右边的元素,那么它们之间的所有元素都会构成逆序对。
  • 具体来说,当我们在归并过程中发现 A[i] > A[j],我们可以推断出左侧数组中 A[i] 及其后面的所有元素都会和 A[j] 形成逆序对,因为左边的数组是有序的。
#include <stdio.h>
#define maxn 100000

int a[maxn], b[maxn]; // a数组存储原始数据,b数组存储排序后的数据
int c[maxn], d[maxn]; // c数组存储count值,d数组存储right值
int ans; // 记录逆序对数量

void merge(int l, int m, int r) {
    int i = l, j = m + 1, k = l;
    while (i <= m && j <= r) {
        if (a[i] <= a[j]) {
            b[k++] = a[i++];
        } else {
            ans += m - i + 1; // 计算以a[j]为分界线的逆序对数量
            b[k++] = a[j++];
        }
    }
    while (i <= m) {
        b[k++] = a[i++];
    }
    while (j <= r) {
        b[k++] = a[j++];
    }
    for (i = l; i <= r; i++) {
        a[i] = b[i]; // 将排序后的结果复制回a数组
    }
}

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); // 递归处理右半部分
    merge(l, mid, r); // 合并左右两部分并统计逆序对数量
}

int main() {
    int n; // 数组长度
    scanf("%d", &n); // 输入数组长度
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]); // 输入原始数据
    }
    merge_sort(1, n); // 调用归并排序函数求解逆序对数量
    printf("%d", ans); // 输出结果
    return 0;
}
方法三:在归并排序过程中直接统计逆序对
#include<stdio.h>
#define N 100010
typedef long long int ll;

int q[N];
ll result = 0;

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 k = 0, i = l, j = mid + 1, temp[r - l + 1];
    while (i <= mid && j <= r) {
        if (q[i] <= q[j]) {
            temp[k++] = q[i++];
        } else {
            temp[k++] = q[j++];
            result += mid - i + 1; // 记录逆序对
        }
    }
    while (i <= mid) temp[k++] = q[i++];
    while (j <= r) temp[k++] = q[j++];

    // 物归原主
    for (int i = l, k = 0; i <= r; i++, k++) q[i] = temp[k];
}

int main(void) {
    int n = 0;
    scanf("%d", &n);
    for (int i = 0; i < n; i++) scanf("%d", &q[i]);

    merge_sort(0, n - 1);

    printf("%lld\n", result);

    return 0;
}

🔍 代码分析

  1. 归并排序原理
    在归并排序中,我们每次将数组分成两部分并进行排序。在合并阶段,如果左侧的元素大于右侧的元素,就会统计逆序对。因为在归并排序的过程中,左侧子数组的元素总是排在右侧子数组的元素之前,所以我们可以通过这种方式来快速计算逆序对。

  2. 优化思路
    归并排序的时间复杂度是 O(n log n),这是因为每次都将数组分成两部分进行处理,每一部分的合并操作需要 O(n) 的时间。通过将逆序对的统计与排序过程结合,可以有效提高算法的效率。

💡 编程技巧与建议

  1. 避免重复计算
    在归并排序过程中,我们已经实现了将子数组合并的操作。在合并时直接统计逆序对数量,不必额外遍历数组,这可以显著提高效率。

  2. 使用合适的数据类型
    逆序对数量可能会非常大,因此建议使用 long long int 来存储结果,防止溢出。

  3. 递归终止条件
    在递归过程中,确保基准条件正确设定,避免无意义的递归调用。

  4. 数据结构的选择
    对于大规模数据,归并排序是一个非常高效的排序算法。你可以考虑使用其他排序算法(如快速排序)来对比性能,选择最适合的算法。

🌐 扩展知识:逆序对的应用

逆序对在很多实际应用中都有涉及,尤其在排序算法和数据分析中。例如:

  • 排序算法优化:很多排序算法(如归并排序、快速排序)都可以通过逆序对的数量来评估排序的“难度”。
  • 数据压缩:在数据压缩领域,逆序对可以用来判断数据的冗余度,帮助设计更高效的压缩算法。

🏁 总结

通过本篇博客,我们深入探讨了如何通过归并排序算法求解逆序对数量,并分析了优化方案。关键是将统计逆序对的过程与排序操作结合,在归并的同时进行统计,极大地提高了效率。

  • 归并排序提供了一个高效的解决方案,时间复杂度为 O(n log n)
  • 使用合适的数据类型来防止溢出,特别是在大数据量的情况下。
  • 在实际编码时,注意递归的终止条件和优化合并过程的效率。

如果你对逆序对或归并排序有任何疑问,欢迎留言讨论,和我们一起成长!

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值