题目来自剑指Offer
题目:
举例:
思路:
方法(1):
暴力方法:对于数组中任意两个数,均做一次判定,判断是否为逆序对。
时间复杂度:O(n^2)。
方法(2):类似于归并排序。
(1)首先把数组分成两个长度均等的数组
(2)分布对这两个数组排序(小到大)。
(3)之后再求逆序对,同时对把这两个数组合并到一个数组
时间复杂度为O(nlogn)
为什么这么可以减少时间消耗呢?
计算逆序对时,先算每一个区间内的逆序对,之后再算区间间的逆序对。
在算区间间的逆序对时,两个数组均是有序的,对于有序数组中的每一个元素,可以再O(1)的时间算出其逆序对
举例:
对于数组:7,5,4,6
先分成两个数组 7,5和4,6.
之后对两个数组进行排序,得到:
有序数组(1):5,7
有序数组(2):4,6
之后逆序归并,并求逆序
最大数为7,由于7在左边数组中,且7>6(右边数组的最大数),则7与第二个数组6左边的数据均产生逆序对。同时把7放到临时数组中。(本次循环找出两个逆序对)
之后取得最大值为6,由于6在右边的数组中,则不会产生逆序对。则直接放入临时数组中。
之后取得最大值为5、由于5在左边数组中,且5>4(右边数组的最大数),则5与第二个数组4左边的数据均产生逆序对。同时把5放到临时数组中。(本次循环找出一个逆序对)
。。。
代码:
#include <iostream>
#include <assert.h>
using namespace std;
/*对数组nArr的有序区间[nStart,nMid]和[nMid+1,nEnd]进行归并,放到nArr中*/
void Merge(int nArr[],int nStart,int nMid,int nEnd,int* pTmp,int& nCount)
{
int nLeftStart = nStart;
int nLeftEnd = nMid;
int nRightStart = nMid + 1;
int nRightEnd = nEnd;
assert(nEnd - nStart + 1 > 0);
int nCur = nEnd;
while (nLeftStart <= nLeftEnd && nRightStart <= nRightEnd)
{
if (nArr[nLeftEnd] > nArr[nRightEnd])
{
nCount += nRightEnd - nRightStart + 1;
pTmp[nCur--] = nArr[nLeftEnd];
nLeftEnd--;
}
else
{
pTmp[nCur--] = nArr[nRightEnd];
nRightEnd--;
}
}
assert(nCur >= nStart && nCur <= nEnd);
//处理剩余的数组,注意,剩余的肯定是那些比较小的数,此时无论这些小数在左边还是在右边都不对逆序对个数产生影响
while (nLeftStart <= nLeftEnd)
{
//剩余的数在左边数组中
pTmp[nCur--] = nArr[nLeftEnd];
nLeftEnd--;
}
while (nRightStart <= nRightEnd)
{
//剩余的数在右边数组中
pTmp[nCur--] = nArr[nRightEnd];
nRightEnd--;
}
assert(nCur == nStart - 1);
//把临时数组中的数据移动到新数组中
for (nCur = nEnd;nCur >= nStart;nCur--)//和下面的函数区别之处
{
nArr[nCur] = pTmp[nCur];
}
}
/*对nArr的区间[nStart,nEnd]进行排序,并把排序的结果放入nArr中*/
void FindReverseOrder(int nArr[],int nStart,int nEnd,int* pTmp,int& nCount)
{
if (nStart < nEnd)
{
int nMid = (nStart + nEnd) >> 1;
FindReverseOrder(nArr,nStart,nMid,pTmp,nCount);/*对nArr的区间[nStart,nMid]排序,并把结果放入nArr中*/
FindReverseOrder(nArr,nMid + 1,nEnd,pTmp,nCount);/*对nArr的区间[nMid+1,nEnd]排序,并把结果放入nArr中*/
Merge(nArr,nStart,nMid,nEnd,pTmp,nCount);/*对nArr的两个区间[nStart,nMid]和[nMid+1,nEnd]排序,并把结果放入nArr中*/
}
}
int FindReverseOrder(int nArr[],int nLen)
{
assert(nArr && nLen > 0);
int nCount = 0;
int* pTmp = new int[nLen];
FindReverseOrder(nArr,0,nLen - 1,pTmp,nCount);
return nCount;
}
int main()
{
//int nArr[100] = {7,5,6,4};
int nArr[100] = {7,5,6,4,1,9,4};
cout<<FindReverseOrder(nArr,7)<<endl;
system("pause");
return 1;
}
上述代码存在一个问题:
对于两个数组进行归并时,总是把归并的结果先放入pTmp中,之后在把排序好的数组转存到数组nArr中。
注意,这里就存在一个冗余操作。即数据的来回存放。不过该优化不会改变时间复杂度。
优化代码:
思想:当结果归并到临时数组pTmp后,就先不用归并到nArr中,可以直接在数组pTmp处理,在之后那次的处理中,临时数组就为nArr了。即临时数组不仅仅限定于pTmp,这样就不用对每次归并的结果转来转去的啦。
#include <iostream>
#include <assert.h>
using namespace std;
/*求解数组nArr中的两个有序区间的逆序对,并把两个有序区间归并到pTmp中*/
void Merge(int nArr[],int nStart,int nMid,int nEnd,int* pTmp,int& nCount)
{
int nLeftStart = nStart;
int nLeftEnd = nMid;
int nRightStart = nMid + 1;
int nRightEnd = nEnd;
assert(nEnd - nStart + 1 > 0);
int nCur = nEnd;
while (nLeftStart <= nLeftEnd && nRightStart <= nRightEnd)
{
if (nArr[nLeftEnd] > nArr[nRightEnd])
{
nCount += nRightEnd - nRightStart + 1;
pTmp[nCur--] = nArr[nLeftEnd];
nLeftEnd--;
}
else
{
pTmp[nCur--] = nArr[nRightEnd];
nRightEnd--;
}
}
assert(nCur >= nStart && nCur <= nEnd);
//处理剩余的数组,注意,剩余的肯定是那些比较小的数,此时无论这些小数在左边还是在右边都不对逆序对个数产生影响
while (nLeftStart <= nLeftEnd)
{
//剩余的数在左边数组中
pTmp[nCur--] = nArr[nLeftEnd];
nLeftEnd--;
}
while (nRightStart <= nRightEnd)
{
//剩余的数在右边数组中
pTmp[nCur--] = nArr[nRightEnd];
nRightEnd--;
}
assert(nCur == nStart - 1);
}
/*对nArr的区间[nStart,nEnd]进行排序,并把排序的结果放入pTmp中*/
void FindReverseOrder(int nArr[],int nStart,int nEnd,int* pTmp,int& nCount)
{
if (nStart < nEnd)
{
int nMid = (nStart + nEnd) >> 1;
FindReverseOrder(pTmp,nStart,nMid,nArr,nCount);//对数组pTmp的区间[nStart,nMid]进行排序,之后放到数组Arr中
FindReverseOrder(pTmp,nMid + 1,nEnd,nArr,nCount);//对数组pTmp的区间[nMid + 1,nEnd]进行排序,之后放到数组Arr中
Merge(nArr,nStart,nMid,nEnd,pTmp,nCount);//对数组nArr进行排序,把结果放到数组pTmp中
//在调用本次递归时,向nArr传参的是pTmp,而向pTmp传参的是nArr,即相对于上一层递归来说,实际上有序数组是在数组nArr中
}
}
int FindReverseOrder(int nArr[],int nLen)
{
assert(nArr && nLen > 0);
int nCount = 0;
int* pTmp = new int[nLen];
memcpy(pTmp,nArr,nLen * sizeof(int));//很重要,别忘了。
FindReverseOrder(pTmp,0,nLen - 1,nArr,nCount);
//此时数组nArr中存放的元素是有序的
//for (int i = 0;i < 7;i++)
//{
// cout<<nArr[i]<<" ";
//}
return nCount;
}
int main()
{
//int nArr[100] = {7,5,6,4};
//int nArr[100] = {7,5,6,4,1,9,4};
int nArr[100] = {7,5,6,4,1,9,4};
cout<<FindReverseOrder(nArr,7)<<endl;
system("pause");
return 1;
}