归并排序的应用问题
归并排序可参考上一篇博客。
归并排序另一应用:逆序对。
问题描述:
一个数列,其中一个数p,其左边所有比p小的数的和,是数p的小和。
求这个数列所有数的小和之和。
思路
利用归并排序的过程完成求小和问题。
已知归并过程如下:
1、首先划分划分划分,一直划分到不能划分,即每个组都只有一个数值。
2、然后合并,合并的过程就是每个二划分排序的过程。
3、在合并的时候,开辟一个辅助数组,其大小等于这两个合并数列的大小。
4、设置两个指针分别指向每个数列的首部,然后比较得到其中较小的值,并将这个值放入辅助数组中。然后取出小值的那个数列的指针可以继续向前走,与另一个数列的指针所指向的值继续比较。
5、这样比较完成后,如果两个数列中有个数列的数值有剩余,即其指针没有走到末尾,则将这个数列直接赋到辅助数组末尾即可。
6、然后将辅助数组中的值拷贝回原数组中刚才合并的那两个数列的位置上。
下面举例:
3 7 5 9
合并:
设置辅助数组:
3 (5比3大,3放入数组中)
3 5 (7比5大,5放入数组中)
3 5 7 (9比7大,7放入数组中)
3 5 7 9 (数列1结束,直接将数列2放入辅助数组末尾,即9放入数组中)
完成一次合并排序。
下一次,3 5 7 9 可以和 1 4 6 8进行合并排序。
在上面例子中,左边数列3与5比较时,它是5左边的数,且比5小,则3会出现在5的小和中。求已知5所在的数列是有序的,则9定比3大,3也会出现在9的小和中。所以在此次合并排序的过程中,3会参与2次小和的计算,可以记录下3*2的值。
而由于这是个递归的过程,每个数列都是由包含一个数值开始合并排序的,所以所有数都会遇到其右边比它大的数,所以这个过程既不会少算也不会多算。
体现在代码中,就是当左边数列中的数p比右边数列中的数q小时,则直接可以记录有x个左边数p相乘即可,有x个就是右边数列q及其之后有几个数。count += a[p1] < a[p2] ? (r - p2 + 1)*a[p1] : 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] ? (r - p2 + 1)*a[p1] : 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