归并排序——逆序数对的统计

逆序数对的统计

题目描述

运行代码

#include <iostream>
using namespace std;
#define LL long long 
const int N = 1e5 + 5;
int a[N], tmp[N];
LL merge_sort(int q[], int l, int r)
{if (l >= r)return 0;  int mid = l + r >> 1;  LL res = 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]) tmp[k ++ ] = q[i ++ ];else{res += mid - i + 1;tmp[k ++ ] = q[j ++ ];}while (i <= mid)tmp[k ++ ] = q[i ++ ];while (j <= r)tmp[k ++ ] = q[j ++ ];for (i = l, j = 0; i <= r; i ++, j ++ )q[i] = tmp[j];  return res;
}
int main()
{int n;scanf("%d", &n);for (int i = 0; i < n; i ++ ) 
scanf("%d", &a[i]);    cout << merge_sort(a, 0, n - 1) << endl;  return 0;
}

代码思路

宏定义与常量

  • #define LL long long:定义LLlong long类型的别名,用于存储较大的整数,例如逆序对的数量。
  • const int N = 1e5 + 5;:定义数组的最大容量,适用于最多100,005个元素的数组。

函数merge_sort此函数递归地将数组分成更小的部分,然后合并这些部分,同时计算逆序对总数。

  • 参数q[]是要排序的数组,lr分别是当前子数组的左右边界(索引)。
  • 基础情况:当左边界大于等于右边界时(即子数组只有一个或没有元素),返回0,表示该子数组没有逆序对。
  • 分解:计算中间索引mid,递归地对左右两部分[l, mid][mid+1, r]进行排序并计算逆序对,将结果累加到res中。
  • 合并
    • 初始化辅助数组tmp和三个指针k, i, j。当i未超过midj未超过r时,比较q[i]q[j]的大小:
      • 如果q[i] <= q[j],说明当前元素不会形成新的逆序对,将q[i]放入tmp,并移动ik
      • 否则,所有q[i]q[mid]之间的元素都比q[j]大,因此增加了mid - i + 1个逆序对,将q[j]放入tmp,移动jk
    • 分别处理剩余的元素,将它们依次放入tmp。将tmp中的元素复制回原数组q
  • 返回值:最终返回整个数组排序过程中的逆序对总数res
  • 主函数main读取数组长度n。通过循环读取数组a中的每个元素。调用merge_sort函数,传入数组a和其边界(0和n-1),输出计算得到的逆序对总数。

改进思路

  • 使用指针直接操作数组:在mergeSortAndCount函数中,直接使用指针ijk而非索引,这在某些情况下可以略微提高访问效率。
  • 保持数组作为参数:继续使用原生数组作为函数参数,保持与原始代码结构的一致性。
  • 代码格式和可读性:调整代码格式,确保良好的可读性和一致性,比如增加必要的空格和换行。
  • 去除不必要的类型别名:保留int64_t作为长整型的别名,因为它清晰地表达了数据类型的目的。

改进代码

#include <iostream>
using namespace std;
typedef long long int64_t;
// 使用指针代替数组索引来优化访问速度
int64_t mergeSortAndCount(int a[], int temp[], int left, int right) {if (left >= right) return 0;   int mid = left + (right - left) / 2;   // 递归排序并计数int64_t inv_count = mergeSortAndCount(a, temp, left, mid);inv_count += mergeSortAndCount(a, temp, mid + 1, right);   // 合并阶段计算逆序对int i = left, j = mid + 1, k = left;while (i <= mid && j <= right) {if (a[i] <= a[j]) {temp[k++] = a[i++];} else {inv_count += mid - i + 1;temp[k++] = a[j++];}}  // 复制剩余元素while (i <= mid) temp[k++] = a[i++];while (j <= right) temp[k++] = a[j++];    // 将排序结果复制回原数组for (int p = left; p <= right; ++p) a[p] = temp[p];   return inv_count;
}
int main() {const int N = 1e5 + 10;int a[N], temp[N];int n;scanf("%d", &n);   for (int i = 0; i < n; ++i) scanf("%d", &a[i]);   int64_t inv_count = mergeSortAndCount(a, temp, 0, n - 1);cout << inv_count << endl;   return 0;
}

其他方法(使用向量) AI生成

#include <iostream>
#include <vector>using namespace std;
using int64_t = long long;// 合并排序并计数逆序对
int64_t mergeSortAndCount(vector<int>& array, vector<int>& temp, int left, int right) {if (left >= right) return 0;int mid = left + (right - left) / 2;// 递归排序左右两边,并计算逆序对int64_t inv_count = mergeSortAndCount(array, temp, left, mid);inv_count += mergeSortAndCount(array, temp, mid + 1, right);// 合并阶段计算逆序对int i = left, j = mid + 1, k = 0;while (i <= mid && j <= right) {if (array[i] <= array[j]) {temp[k++] = array[i++];} else {inv_count += mid - i + 1; // 计算逆序对temp[k++] = array[j++];}}// 处理剩余元素while (i <= mid) temp[k++] = array[i++];while (j <= right) temp[k++] = array[j++];// 将排序结果复制回原数组copy(temp.begin(), temp.begin() + k, array.begin() + left);return inv_count;
}int main() {int n;cin >> n;vector<int> a(n);for (int& elem : a) cin >> elem;vector<int> temp(n); // 临时数组用于合并cout << mergeSortAndCount(a, temp, 0, n - 1) << endl;return 0;
}
  1. 添加函数注释:解释每个函数的作用,特别是merge_sort中的逻辑。
  2. 使用更明确的变量名:如将q[]改为array[],使代码意图更清晰。
  3. 去除全局变量:尽量减少全局变量的使用,改用函数参数传递。
  4. 优化类型别名的可读性:将LL改为更明确的别名,如typedef long long int64_t;
  5. 使用C++风格的输入输出:完全替换scanfprintfcincout

归并、插入和冒泡排序比较

归并排序

  • 优点:时间复杂度在平均和最坏情况下都为 ,性能比较稳定,适合大规模数据排序。
  • 缺点:需要额外的空间用于合并。

插入排序

  • 优点:对于接近有序的数据表现非常好,在小规模数据或部分有序数据上效率可能较高,代码简单。
  • 缺点:最坏情况时间复杂度为 。

冒泡排序

  • 优点:实现简单。
  • 缺点:时间复杂度较差,也是 。

一般来说,在数据规模较大且对性能要求较高时,归并排序通常表现更好。但如果数据规模较小或者数据有一定的特殊性(如接近有序),插入排序可能更合适。而冒泡排序相对来说在大多数情况下效率较低,较少被优先选用。

使用归并排序解决逆序数对统计问题思路:

在归并排序的合并过程中,当我们比较左右两部分元素时,左边部分一个较大元素如果出现在右边部分较小元素之前,那么就构成一个逆序数对。

当左边当前元素大于右边当前元素时,由于左右两边都是已排序的,那么左边该元素之后的所有元素与右边当前元素都构成逆序数对,数量为左边当前未处理元素的数量。我们可以在合并的同时统计这样的逆序数对数量。通过递归地执行归并排序,不断在合并过程中累加逆序数对的数量,最终就能得到总的逆序数对数量。

例如,假设有数组 [3, 1, 4, 2],在第一次合并 [3] 和 [1] 时,因为 3 大于 1,此时就找到一个逆序数对,然后继续递归合并其他部分并统计。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/25592.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

IAR仿真调试

1. 硬件连接 JTAG与SWD接口&#xff1a;IAR支持JTAG和SWD两种下载器和单片机之间的连接方式。JTAG使用标准的20Pin接口&#xff0c;而SWD则更为简化&#xff0c;只需要四根线&#xff08;GND、TCK、TMS、RST&#xff08;可选&#xff09;&#xff09;。连接方法&#xff1a;如…

华为OD刷题C卷 - 每日刷题 21(按身高和体重排队,解密犯罪时间)

三段代码分别解决了三个不同的算法问题&#xff0c;下面是对它们的概述&#xff1a; 1、&#xff08;按身高和体重排队&#xff09;&#xff1a; 这段代码是解决“按身高和体重排队”的问题。它提供了一个Java类Main&#xff0c;其中包含main方法&#xff0c;用于根据学生的身…

【数据结构(邓俊辉)学习笔记】图07——最短路径

文章目录 0. 概述1. 问题2. 最短路径2.1 最短路径树2.1.1 单调性2.1.2 歧义性2.1. 3 无环性 2.2 Dijkstra 算法2.2.1 贪心迭代2.2.2 实现2.2.3 实例2.2.4 复杂度 0. 概述 学习下最短路径和Dijistra算法 1. 问题 给定带权网络G (V, E)&#xff0c;以及源点&#xff08;source…

4.大模型微调技术LoRA

大模型低秩适配(LoRA)技术 现有PEFT 方法的局限与挑战 Adapter方法,通过增加模型深度而额外增加了模型推理延时。Prompt Tuning、Prefix Tuning、P-Tuning等方法中的提示较难训练,同时缩短了模型可用的序列长度。往往难以同时实现高效率和高质量,效果通常不及完全微调(f…

Vue2事件处理(v-on)

1.事件处理 &#xff08;1&#xff09;指令的语法格式 <标签 v-指令名:事件名"表达式">{{插值语法}}</标签>&#xff08;2&#xff09;"表达式"位置都可以写什么&#xff1f; 常量、JS表达式、Vue实例所管理的XXX 1.1 事件绑定 &#xff0…

51单片机单击按键,数码管显示学号;双击按键,数码管显示全8

1、功能描述 单击按键&#xff0c;数码管显示学号&#xff1b;双击按键&#xff0c;数码管显示全8 2、实验原理 按键输入检测:通过检测按键的电平变化来识别按键是否被按下。当按键被按下时&#xff0c;电平从高变低,单片机通过外部中断或轮询方式检测这一变化。 数码管显示…

Debian常用命令有哪些?

Debian是一种流行的Linux发行版&#xff0c;它提供了一组强大的命令行工具来帮助用户管理系统和文件。以下是一些Debian中常用的命令&#xff0c;按照不同的功能分类进行了归纳&#xff1a; 文件与目录操作 ls 功能&#xff1a;列出目录内容。常用选项&#xff1a; -l&#xf…

文章解读与仿真程序复现思路——电力自动化设备EI\CSCD\北大核心《计及电力不平衡风险的配电网分区协同规划》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

LLVM Cpu0 新后端6

想好好熟悉一下llvm开发一个新后端都要干什么&#xff0c;于是参考了老师的系列文章&#xff1a; LLVM 后端实践笔记 代码在这里&#xff08;还没来得及准备&#xff0c;先用网盘暂存一下&#xff09;&#xff1a; 链接: https://pan.baidu.com/s/1yLAtXs9XwtyEzYSlDCSlqw?…

【MySQL】 深入了解 MySQL 存储过程:定义、优势及示例

我已经从你的 全世界路过 像一颗流星 划过命运 的天空 很多话忍住了 不能说出口 珍藏在 我的心中 只留下一些回忆 &#x1f3b5; 牛奶咖啡《从你的全世界路过》 在数据库管理系统中&#xff0c;存储过程&#xff08;Stored Procedure&#xff09;是一种重…

探究IOC容器刷新环节初始化前的预处理

目录 一、IOC容器的刷新环节快速回顾 二、初始化前的预处理prepareRefresh源码分析 三、初始化属性源 &#xff08;一&#xff09;GenericWebApplicationContext初始化属性源 &#xff08;二&#xff09;StaticWebApplicationContext初始化属性源 四、初始化早期事件集合…

汇川CodeSysPLC教程03-2-4 RS485

前情回顾 在前面介绍串口通讯的视频当中&#xff0c;我们提到了RS232&#xff0c;也顺带提到了RS485&#xff0c;本期将会对RS485做比较全面的比较和盘点。 什么是RS485&#xff1f; RS485是一种用于串行通信的标准&#xff0c;通常用于工业自动化和远距离数据传输。它在电气…

3.大模型高效微调PEFT

大模型高效微调(PEFT)技术 预训练模型的背景 预训练与微调:传统的微调方法通常涉及对整个预训练模型的参数进行再训练,以适应特定任务。这虽然有效,但计算成本高,且需要大量的标记数据。模型结构:像BERT或GPT这样的模型通常包含数亿甚至数十亿个参数,构成一个深层次的…

Qt——升级系列(Level Four):控件概述、QWidget 核心属性、按钮类控件

目录 控件概述 QWidget 核心属性 核心属性概览 enabled geometry windowTitle windowIcon windowOpacity cursor font toolTip focusPolicy styleSheet 按钮类控件 Push Button Radio Buttion Check Box Tool Button 控件概述 Widget 是 Qt 中的核⼼概念. 英⽂原义是 "…

西门子学习笔记11 - PTO脉冲指令的使用

1、使用指令前的设置 1、打开一个脉冲发生器&#xff0c;并启用 2、选择使用PTO(脉冲A和方向B) 3、硬件设置输出 4、这样前期的准备工作就完成了 2、指令的使用 1、添加指令CTRL_PTO 2、配置如下 3、方向控制程序如下 4、最后进行测试即可

C语言之存储类、作用域、生命周期、链接属性

一 &#xff1a;概念解析 1&#xff1a; 存储类 &#xff08;1&#xff09;存储类就是存储类型&#xff0c;就是描述C语言变量存储在什么地方 &#xff08;2&#xff09;内存有多种管理方法&#xff1a;栈、堆数据段、bss段、.text段......一个变量的存储类属性就是描述…

html--万年历

<!DOCTYPE html> <html lang"zh_CN"><head><meta http-equiv"Content-Type" content"text/html; charsetUTF-8" /><meta charset"utf-8" /><title>万年历</title><link rel"styles…

C语言 | Leetcode C语言题解之第142题环形链表II

题目&#xff1a; 题解&#xff1a; struct ListNode* detectCycle(struct ListNode* head) {struct ListNode *slow head, *fast head;while (fast ! NULL) {slow slow->next;if (fast->next NULL) {return NULL;}fast fast->next->next;if (fast slow) {s…

LLVM Cpu0 新后端9 objdump readelf

想好好熟悉一下llvm开发一个新后端都要干什么&#xff0c;于是参考了老师的系列文章&#xff1a; LLVM 后端实践笔记 代码在这里&#xff08;还没来得及准备&#xff0c;先用网盘暂存一下&#xff09;&#xff1a; 链接: https://pan.baidu.com/s/1yLAtXs9XwtyEzYSlDCSlqw?…

C-Linux: 题集

1 C语言 1 简述 全局变量 和 局部变量的区别: 存储位置:全局变量存储在静态区,局部变量存储在栈上 作用范围:全局变量对整个程序可见,局部变量仅对其定义的函数可见 生命周期:全局变量在程序执行过程中都存在,局部变量的生命周期仅在其所在函数执行期间存在 2 死锁…