归并排序的扩展问题
——先了解数据结构之归并排序,更容易理解以下的问题。
- 小和问题和逆序对问题。
在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。在一个数组中,左边的数如果比右边的数大,则这两个数构成一个逆序对。(据说,小和问题面试必考)
-
小和问题可以反向思维,从左往右找比这个数大的数有几个。
-
用归并的方法。
-
逆序对问题与小和问题时相同的。只需要将相加改成打印逆序对。
例如,该数组为 a[6]={1,3,4,2,5,4};
如下给出完整的计算小和的示意过程:
(1)在(1~1)范围上比较
(2)在(2~2)范围上比较
(3)在(4~4)范围上比较
(4)在(5~5)范围上比较
(5)在(3~5)范围上比较
将所有荧光笔标记的数值相加即最终的答案。
如下是计算小和的完整代码:(与归并排序只有两行不同)
#include <iostream>
int merge(int a[], int l, int m, int r)
{
int *help = new int[6];
int i = 0;
int p1 = l;
int p2 = m + 1;
int res = 0;
while (p1 <= m && p2 <= r)
{
//小和问题的条件 左边的数小于右边的数
res += a[p1] < a[p2] ? (a[p1] * (r - p2 + 1)) : 0;
//当左边严格小于右边时,才将左边的数字拷贝到help[]
//不能是小于等于,因为这样才不会少统计
help[i++] = a[p1] < a[p2] ? a[p1++] : a[p2++];
}
while (p1 <= m)
{
help[i++] = a[p1++];
}
while (p2 <= r)
{
help[i++] = a[p2++];
}
for (int j = 0; j < i; j++)
{
a[l + j] = help[j];
}
return res;
}
int MergeSort(int a[], int l, int r)
{
if (l < r) {
int mid = l + ((r - l) >> 1);
//左侧范围上排序求小和
return MergeSort(a, l, mid)
//右侧范围上求小和
+MergeSort(a, mid + 1, r)
//左组和右组合并的情况下求小和
+merge(a, l, mid, r);
}
return 0;
}
int main()
{
int a[] = { 1,3,4,2,5,4 };
int i = 0, n = 6;
int ans = MergeSort(a, i, n-1);
std::cout << ans;
}
下面是逆序对问题的完整代码:
#include <iostream>
void merge(int a[], int l, int m, int r)
{
int *help = new int[6];
int i = 0;
int p1 = l;
int p2 = m + 1;
int res = 0;
while (p1 <= m && p2 <= r)
{
//逆序对的条件 左边的数大于右边的数
if (a[p1] > a[p2])
{
std::cout << a[p1] << " " << a[p2] << std::endl;
}
//当左边严格大于右边时,才将左边的数字拷贝到help[]
//不能是大于等于,因为这样才不会少统计
help[i++] = a[p1] > a[p2] ? a[p1++] : a[p2++];
}
while (p1 <= m)
{
help[i++] = a[p1++];
}
while (p2 <= r)
{
help[i++] = a[p2++];
}
for (int j = 0; j < i; j++)
{
a[l + j] = help[j];
}
}
void MergeSort(int a[], int l, int r)
{
if (l < r) {
int mid = l + ((r - l) >> 1);
//左侧范围上排序求逆序对
MergeSort(a, l, mid);
//右侧范围上求逆序对
MergeSort(a, mid + 1, r);
//左组和右组合并的情况下求逆序对
merge(a, l, mid, r);
}
}
int main()
{
int a[] = { 1,3,4,2,5,4 };
int i = 0, n = 6;
MergeSort(a, i, n-1);
return 0;
}