排序专题之归并排序

  • 基本原理
    归并(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);     //左右两数组归并
	}
}

猜你喜欢

转载自blog.csdn.net/qq_40432881/article/details/83003881