题目来自剑指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; }