归并排序的应用问题
归并排序可参考上一篇博客。
归并排序另一应用:求数列中的小和。
问题描述:
一个数列,如果左边的数大,右边的数小,则称这两个数位一个逆序对。
求出一个数列中有多少个逆序对。
思路
利用归并排序的过程完成求逆序对问题。
已知归并过程如下:
1、首先划分划分划分,一直划分到不能划分,即每个组都只有一个数值。
2、然后合并,合并的过程就是每个二划分排序的过程。
3、在合并的时候,开辟一个辅助数组,其大小等于这两个合并数列的大小。
4、设置两个指针分别指向每个数列的首部,然后比较得到其中较小的值,并将这个值放入辅助数组中。然后取出小值的那个数列的指针可以继续向前走,与另一个数列的指针所指向的值继续比较。
5、这样比较完成后,如果两个数列中有个数列的数值有剩余,即其指针没有走到末尾,则将这个数列直接赋到辅助数组末尾即可。
6、然后将辅助数组中的值拷贝回原数组中刚才合并的那两个数列的位置上。
下面举例:
9 12 5 10
合并:
设置辅助数组:
5 (5比9小,5放入数组中)
5 9 (10比9大,9放入数组中)
5 9 10 (12比10大,10放入数组中)
5 9 10 12 (数列2结束,直接将数列1放入辅助数组末尾,即12放入数组中)
完成一次合并排序。
下一次,5 9 10 12 可以和 1 8 11 15进行合并排序。
在上面例子中,左边数列9与5比较时,它是5左边的数,且比5大,则9、5是一个逆序对。求已知9所在的数列是有序的,则12定比5也大,12、5也是一个逆序对。所以在此次合并排序的过程中,5共参与了两次逆序对。
而由于这是个递归的过程,每个数列都是由包含一个数值开始合并排序的,所以所有数都会遇到其右边比它小的数,所以这个过程既不会少算也不会多算。
体现在代码中,就是当左边数列中的数p比右边数列中的数q大时,则p之后的数也会比q大。则直接可以记录p右边有x个数即可,有x个就是左边数列p及其之后有几个数。count += a[p1] > a[p2] ? (mid - p1 + 1) : 0;
代码
#include <iostream>
#include <vector>
using namespace std;
/*
2017/11/12
利用归并排序求逆序对
*/
#if 1
int merge(vector<int>&a, int l, int mid, int r)
{
int count = 0;
int p1 = l;
int p2 = mid + 1;
int i = 0;
vector<int>help(r - l + 1);
while (p1 <= mid && p2 <= r)
{
count += a[p1] > a[p2] ? (mid - p1 + 1) : 0;
help[i++] = a[p1] <= a[p2] ? a[p1++] : a[p2++];
}
while (p1 <= mid)
help[i++] = a[p1++];
while (p2 <= r)
help[i++] = a[p2++];
for (i = 0; i < help.size(); i++)
a[l + i] = help[i];
return count;
}
int mergeSort(vector<int>&a, int l, int r)
{
if (l == r)
return 0;
int mid = l + (r - l) / 2;
return mergeSort(a, l, mid) + mergeSort(a, mid + 1, r) + merge(a, l, mid, r);
}
int SmallSum(vector<int>a)
{
if (a.size() < 2)
return 0;
return mergeSort(a, 0, a.size() - 1);
}
void main()
{
vector<int>a = { 7, 2, 14, 6, 14 };
cout << SmallSum(a) << endl;
system("pause");
}
#else
#endif