- 基本原理
归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把 待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有 序序列。归并排序的平均时间复杂度 O(NlogN)。 归并操作(merge),也叫归并算法,指的是将两个已经排序的序列合并成一个序列的操 作。
如:设有数列{6,202,100,301,38,8,1}
初始状态: [6] [202] [100] [301] [38] [8] [1] 比较次数
i=1 [6 202 ] [ 100 301] [ 8 38] [ 1 ] 3
i=2 [ 6 100 202 301 ] [ 1 8 38 ] 4
i=3 [ 1 6 8 38 100 202 301 ] 4 总计: 11 次
归并操作的工作原理如下:
1.申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
2.设定两个指针,初位置分别为两个已经排序序列的起始位置
3.比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针 到下一位置
4.重复步骤 3 直到某一指针达到序列尾 5.将另一序列剩下的所有元素直接复制到合并序列尾
void Merge(int data[], int left, int mid, int right)
{//二路归并
int i, j, k;
int *pd = new int[right - left + 1]; //建立辅助数组
i = left; j = mid + 1; //i指向第一个数组,j指向第二个数组
k = 0; //k指向辅助数组
while (i <= mid && j <= right)
pd[k++] = data[i] < data[j] ? data[i++] : data[j++];//选择较小的元素放入
while (i <= mid) //右侧数组放完
pd[k++] = data[i++];
while (j <= right) //左侧数组放完
pd[k++] = data[j++];
for (k = 0, i = left;i <= right;i++, k++) //辅助数组元素放入原数组中
data[i] = pd[k];
delete[] pd; //销毁辅助数组
}
void MergeSort(int data[], int left, int right)
{//归并排序(先分治,后归并)
int mid;
if (left < right)
{
mid = (left + right) / 2; //当前数组一分为二
MergeSort(data, left, mid); //左侧分治(划至单个元素为止)
MergeSort(data, mid + 1, right); //右侧分治
Merge(data, left, mid, right); //左右两数组归并
}
}
- 应用
- 逆序对问题
1.题目出处/来源 POJ-2299 Ultra-QuickSort
2.题目描述 给定一个无序的序列,它们都是由 32 位整数组成的。序列长度大可达 500000.现在 通过不断的两两交换,我们可以把这个序列排成由小到大的有序序列。请问交换的次数是 多少。
3.分析 一提起交换,我们很容易想到冒泡排序法。而此题来说,冒泡排序是符合该题的解 的。只要将在序列后面的较小数不断与它前面比它大的数交换,终较小的数浮到了前面,我们就完成了对此数的交换次数统计,如此对每一个数都进行此操作,终我们可以 得到答案。但是序列长度可达 50 万之多,如果真的使用冒泡排序算法解决此问题的话, 其复杂度n2是难以在 7 秒内完成这么大的数据量的。因此我们需要换种算法。想想小的数 不断交换变到前面,那整个不就是求序列的逆序数嘛。有了求逆序数的思路我们由此展开。事先交待求逆序数的算法有归并排序和树状数组两种。本次我们讲述采用归并排序的 方式来解决这个问题。
归并排序的解法求此问题是如果左边第 i 个数(编号从 0 开始)大于右边某个数 x, 由于两个子序列都是有序的,那么左边序列第 i 个以及之后的数都大于 x,所以 flag 要加 上 m-i+1 后由于 50 万数的逆序数可能非常庞大,终的结果可能不是 int 所能装下的,因此 我们采用 long long 这个类型来存放结果。
#include <iostream>
#include <cstring>
#define MAXSIZE 500005
using namespace std; //使用名称空间std
long long flag;
int line[MAXSIZE];
int main()
{
void Merge(int data[], int left, int mid, int right);
void MergeSort(int data[], int left, int right);
long long num;
int i;
while (cin >> num)
{
if (num == 0) break;
memset(line,0,sizeof(line));
time1 = 0;
for (i = 0;i < num;i++)
cin >> line[i];
MergeSort(line, 0, num - 1);
cout << flag << endl;
}
return 0;
}
//归并排序
void Merge(int data[], int left, int mid, int right)
{//二路归并(左右数组均已有序)
int i, j, k;
int *pd = new int[right - left + 1]; //建立辅助数组
i = left; j = mid + 1; //i指向第一个数组,j指向第二个数组
k = 0; //k指向辅助数组
while (i <= mid && j <= right)
if (data[i] < data[j])
pd[k++] = data[i++];
else
{
pd[k++] = data[j++];
flag += mid - i + 1; /*计算逆序数*/
}
while (i <= mid) //右侧数组放完
pd[k++] = data[i++];
while (j <= right) //左侧数组放完
pd[k++] = data[j++];
for (k = 0, i = left;i <= right;i++, k++) //辅助数组元素放入原数组中
data[i] = pd[k];
delete[] pd; //销毁辅助数组
}
void MergeSort(int data[], int left, int right)
{//归并排序(先分治,后归并)
int mid;
if (left < right)
{
mid = (left + right) / 2; //当前数组一分为二
MergeSort(data, left, mid); //左侧分治(划至单个元素为止)
MergeSort(data, mid + 1, right); //右侧分治
Merge(data, left, mid, right); //左右两数组归并
}
}