归并排序的相关用法
现在先来讲解什么是归并排序,(省略,因为网上有很多帖子分析和图解的很好)
下面的代码只是对很多帖子的代码进行了优化(当然还有更好的,也愿意一起探讨)
归并排序完整代码:
#include<iostream>
using namespace std;
void Merge(int num[], int low, int mid,int high)
{
int *p = new int[high - low + 1];
int i = low, j = mid + 1, k = 0;
while (i <= mid&&j <= high)
{
p[k++] = (num[i] < num[j]) ? num[i++] : num[j++]; 这段比较优化,但是电脑运算差不多的过程
}
while (i <= mid)
p[k++] = num[i++];
while (j <= high)
p[k++] = num[j++];
for (i = low; i <= high; i++)
num[i] = p[i - low];
delete[]p;
}
void MergeSort(int num[], int low, int high)
{
if (low < high)
{
int mid = low + ((high - low) >> 1); 利用位运算比较快,而且还做了防止溢出处理。相对(high+low)而言。
MergeSort(num, low, mid);
MergeSort(num, mid + 1, high);
Merge(num, low, mid, high);
}
}
int main()
{
int a[3] = { 45, 2, 12 };
MergeSort(a, 0, 2);
for (int i = 0; i < 3; i++)
cout << a[i] << " ";
return 0;
}
归并排序最大得特点就是,对两段已有顺序的段,进行在排,而在这个过程中就可以添加很多技巧或者这段代码
本身就是一段很有信息的资源。
比如,我们可以知道逆序数对的多少等等,相关的具有该代码逻辑特征的问题都可以使用并改进归并排序的代码来实现。
这些东西需要大家不断的理解和收集的,还要与各个知识建立相关联系。
下面非原创,大家可以找到相关帖子。
例1:
设a[1...n]是一个包含n个不同数的数组,若当i<j时,有a[i] >a[j],则称(i,j)就是一个逆序对,现在要求设计一个O(nlog(n))的算法来求解数组a中逆序对数。
例2:
下面我们再来看一个例子,该例子好像是08年ACM ICPC亚洲区预选赛网选赛的一道题目。题目的大意如下:
exp02: 假设有n个士兵站成一排,每个士兵都有一个分数(该分数为正整数),现在要从中选出若干个士兵(>=1),要求所选出的士兵必须是相邻的,而且所选出的士兵的分数的平均值必须大于等于预先给定的一个常数b,求有多少中选法?
分析:假设a[1...n]代表n个士兵的分数,即a[i]代表第i个士兵的分数,即问题是求
(a[i]+a[i+1]+...+a[j]) / (j - i +1) >= b的(i,j)的个数。我们假设s[i]=a[1]+...a[i],且设
s[0] = 0,则问题转换为求(s[j] - s[i]) / (j - i) >= b的(i,j)的个数。我们很容易发现,(s[j] - s[i]) / (j - i) 类似与数学中直线的斜率,我们把(i,s[i])视为直角坐标系中的一个点,那么(s[j] - s[i]) / (j - i) 表示通过(i,s[i])和(j,s[j])两点的直线的斜率。又因为s[i]数组具有严格的单调性,即s[i+1]>s[i](因为a[i+1]为正整数),所以我们就可以把问题转换为一个求逆序对的问题。我们将点(i,s[i])映射到y轴上,其映射方法是过点(i,s[i])作一条斜率为b的直线,该直线与y轴的交点即为我们所映射的点(假设该点的坐标为(0,y[i])),即显然有,y[i] = s[i] - b*i 。于是,我们只需要求解数组y[i]中的逆序对数sums即可,然后再(n+1)*n / 2 - sums即可为我们所要求的解。至此,我们就可以直接套用求逆序对的O(nlog(n))的算法。
附:这里需要注意的是,当序列中存在2个数相等时也要认为是逆序,序列1,2,2中的逆序对为1个,而不是0个。至于问题的解为什么是(n+1)*n / 2 - sums以及为什么对点(i,s[i])以斜率b映射到y轴上,这个问题读者自己琢磨一下就会明白,由于我这里没有画图工具,读者只需画一个直角坐标系就会明白的,问题的难点就在于将原问题转换为一个求逆序对的问题。