前言
这篇博客是用来记录在牛客网上的一道编程题时遇到的问题,就是这道题: 求数组中的逆序对,这道题需要统计一个数组中的逆序对的数量,比如[2, 1],逆序对就是(2, 1),数量为1,[3, 2, 1]逆序对就是(3, 1),(2, 1),(3, 2),数量为3 。这道题的解法用到了归并排序的思想,基本思想就是分治的思想。将数组分成前后两段,先统计前段内的逆序数对,再统计后段内的逆序数对,再将排序后的得到的有序的前后段归并起来,在归并的过程中,可以统计得到前段与后段中的逆序数对。但是同一种思想不同的代码。我的代码总是有50%例子会超时。于是我统计了我的代码的运行时间和别人的代码的运行时间,差别非常大。
C++程序运行时间统计
#include <iostream>
#include <fstream>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <math.h>
#include <string>
#include <vector>
#include <stack>
#include <chrono>
class Solution {
public:
long long count = 0;
void MSort(vector<int> input, vector<int> &output, long long s, long long m)
{
if (s > m)
return;
else if (s == m)
{
output[s] = input[s];
}
else {
long long center = (s + m) / 2;
vector<int> temp = input;
MSort(input, temp, s, center);
MSort(input, temp, center + 1, m);
long long i = center, j = m, k = m;
for (; i >= s&&j >= center + 1; k--)
{
if (temp[i] > temp[j])
{
output[k] = temp[i];
i--;
count += j-center;
}
else
{
output[k] = temp[j];
j--;
}
}
while (i >= s) {
output[k] = temp[i];
i--;
k--;
}
while (j >= center + 1) {
output[k] = temp[j];
j--;
k--;
}
}
}
int InversePairs(vector<int> data)
{
vector<int> t(data);
MSort(data, t, 0, data.size() - 1);
int s = count % 1000000007;
return s;
}
// 后面是别人的代码
int InversePairs1(vector<int> data) {
//用long long类型否则会超限
long long num = 0;
mergesort(data, num, data.begin(), data.end() - 1);
return num % 1000000007;
}
//归并排序
void mergesort(vector<int> &data, long long &num, vector<int>::iterator start, vector<int>::iterator end) {
if (start == end) return;
vector<int>::iterator mid = start + (end - start) / 2;
mergesort(data, num, start, mid);
mergesort(data, num, mid + 1, end);
merge(data, num, start, end);
}
//合并
void merge(vector<int> &data, long long &num, vector<int>::iterator start, vector<int>::iterator end) {
if (start == end) return;
vector<int>::iterator mid = start + (end - start) / 2;
//把要合并的两个子数组保存出来
vector<int> data1(start, mid + 1);
vector<int> data2(mid + 1, end + 1);
//两个迭代器分别指向两个数组的末尾
vector<int>::iterator p = data1.end() - 1;
vector<int>::iterator q = data2.end() - 1;
vector<int>::iterator m = end;
int sz1 = data1.size();
int sz2 = data2.size();
//合并,当前面数组的元素大于后面时计数加一
while (sz1>0 && sz2>0) {
if (*q<*p) {
num = q - data2.begin() + 1 + num;//1、迭代器相减注意顺序
*m = *p;
--sz1;
--m;
if (sz1>0) --p;//2、有效迭代器的范围是[begin(),end()),begin()位置已经不可以再自减了
}
else {
*m = *q;
--sz2;
--m;
if (sz2>0) --q;
}
}
//3、把剩余的放入数组中,一开始分析出来其中一个数组最多只剩一个元素,而没有让迭代器自减
while (sz1--) {
*m = *p;
if (sz1 > 0) {
--m;
--p;
}
}
while (sz2--) {
*m = *q;
if (sz2 > 0) {
--m;
--q;
}
}
}
};
int main()
{
srand((unsigned)time(NULL));
int a[10000];
for (int i = 0; i < 10000; i++)
a[i] = rand();
Solution s;
// 手工填写的数据
vector<int> dat(a, a+sizeof(a)/sizeof(long long));
vector<int> dat1 = {1,2,3,4,5,6,7,0};
vector<int> dat2 = { 364,637,341,406,747,995,234,971,571,219,993,407,416,366,315,301,601,650,418,355,460,505,360,965,516,648,727,667,465,849,455,181,486,149,588,233,144,174,557,67,746,550,474,162,268,142,463,221,882,576,604,739,288,569,256,936,275,401,497,82,935,983,583,523,697,478,147,795,380,973,958,115,773,870,259,655,446,863,735,784,3,671,433,630,425,930,64,266,235,187,284,665,874,80,45,848,38,811,267,575 };
// 按题中的说明生成的数据
vector<int> data50(10000, 0);
vector<int> data75(100000, 0);
vector<int> data100(200000, 0);
for (int i = 0; i < 10000; i++)
{
data50[i] = rand()%50;
}
for (int i = 0; i < 100000; i++)
{
data75[i] = rand()%75;
}
for (int i = 0; i < 200000; i++)
{
data100[i] = rand()%100;
}
chrono::milliseconds t50;
chrono::milliseconds t75;
chrono::milliseconds t100;
chrono::nanoseconds nano;
std::nano nan;
chrono::steady_clock clock;
chrono::time_point<chrono::steady_clock, chrono::milliseconds> timep;
auto start = chrono::steady_clock::now();
int c50 = s.InversePairs1(data50);
auto tp50 = chrono::steady_clock::now();
int c75 = s.InversePairs1(data75);
auto tp75 = chrono::steady_clock::now();
int c100 = s.InversePairs1(data100);
auto tp100 = chrono::steady_clock::now();
t50 = chrono::duration_cast<chrono::milliseconds>(tp50 - start);
t75 = chrono::duration_cast<chrono::milliseconds>(tp75 - tp50);
t100 = chrono::duration_cast<chrono::milliseconds>(tp100 - tp75);
long long count50 = t50.count();
long long count75 = t75.count();
long long count100 = t100.count();
std::cout << "50: " << double(t50.count()) / 1000 << " 75: " << double(t75.count()) / 1000 << " 100: " << double(t100.count()) / 1000 << std::endl;
system("pause");
return 0;
}
得到的结果是我的代码在运行%100,size(2*10^5)的数据时需要170多秒,而别人的代码只需要5秒多,差别巨大。
代码优化的实例思考
仔细观察后发现,我的代码中,对传入的input数据在递归时进行了大量复制操作(vector temp(input)),而且这是深拷贝,在内存中复制大量数据时可能太耗时,而别人的代码中是使用下标进行合并等操作,不需要拷贝数据,可能是因为这个原因,待我改进代码后再进行观察。
改进后的代码
void MSort(vector<int> &input, vector<int> &output, long long s, long long m, long long &num)
{
if (s > m)
return;
else if (s == m)
{
output[s] = input[s];
}
else {
long long center = (s + m) / 2;
MSort(input, output, s, center, num);
MSort(input, output, center + 1, m, num);
long long i = center, j = m, k = m;
for (; i >= s&&j >= center + 1; k--)
{
if (input[i] > input[j])
{
output[k] = input[i];
i--;
num += j-center;
}
else
{
output[k] = input[j];
j--;
}
}
while (i >= s) {
output[k] = input[i];
i--;
k--;
}
while (j >= center + 1) {
output[k] = input[j];
j--;
k--;
}
// 完成排序的数据存储在output中,现在将其转移到input中
memcpy(input.data() + s, output.data() + s, (m - s+1)*sizeof(input.back()));
// input.cop(output.begin()+s,output.begin()+m);
}
}
int InversePairs(vector<int> data)
{
vector<int> t(data);
long long num = 0;
MSort(data, t, 0, data.size() - 1, num);
int s = num % 1000000007;
return s;
}
此代码顺利通过了OJ,且运行时间小于上面别人的代码。主要是将构造复制操作转为了下标内存拷贝操作,运行通过时间118ms。