常用的排序算法:归并排序(逆序数问题)

快速排序是最常用最常考到的排序方式,但不够稳定。
今天我们来学习一种新的、稳定的排序方法:归并排序。

1 归并排序

1、假设一个序列有n个数字:那么我们将它看成n个子序列,每个子序列有一个数字;
2、此时,我们进行两两归并,得到n/2个长度为2的序列(也可能是(n-1)/2个长度为2和1个长度为1的序列);
3、继续两两归并,递归,保证每一个归并后的子序列都是有序的;
4、得到一个长度为n的有序序列,结束排序。

可见,归并排序中两个最重要的动作是:递归划分、合并排序。 下面我们需要实现这两个操作。

2 代码实现

划分过程Msort:将数组长苏变为1,存入另一个新开辟的数组中。
合并排序Merge:将两个有序数组(复制的数组)归并到另一个数组(最原始的数组)中。

如此动态图的排序过程,代码实现如下:

#include<iostream>

using namespace std;
int A[8] = { 6,5,3,1,8,7,2,4 };
int LENGTH = 8;

void Merge(int* data, int* copy, int start, int length, int end)//两个数组合并过程
{
	int i = start + length;		//原数组前一半的最后一个索引
	int j = end;				//原数组后一半的最后一个索引
	int newIndex = end;			//新数组最后一个索引

	while (i >= start && j >= start + length + 1)//归并过程
	{
		if (copy[i] > copy[j])
		{
			data[newIndex] = copy[i];
			newIndex--;
			i--;
		}
		else
		{
			data[newIndex] = copy[j];
			newIndex--;
			j--;
		}
	}
	while (i >= start)//处理没有归并进去的剩余部分(最小的一些数字)
	{
		data[newIndex] = copy[i];
		newIndex--;
		i--;
	}
	while (j >= start + length + 1)//处理没有归并进去的剩余部分(最小的一些数字)
	{
		data[newIndex] = copy[j];
		newIndex--;
		j--;
	}
}
void Msort(int* data, int* copy, int start, int end)
{
	if (start == end)
	{
		copy[start] = data[start];
		return;
	}

	int length = (end - start) / 2;
	Msort(copy, data, start, start + length);		//将前一半数组排序
	Msort(copy, data, start + length + 1, end);		//将后一半数组排序
	Merge(data, copy, start, length ,end);			//将前后两个排序数组合并
}

void mergeSort(int* a)
{
	int* data = a;
	int* copy = new int[LENGTH];
	for (int i = 0; i < LENGTH; i++)
	{
		copy[i] = data[i];
	}
	Msort(data, copy, 0, LENGTH - 1);
	delete[] copy;//防止内存泄漏
}

int main()
{
	mergeSort(A);
	for (int i = 0; i < LENGTH; i++)
	{
		printf("%d ", A[i]);
	}
	system("pause");
	return 0;
}

3 复杂度分析

这是个比较典型的“空间换时间”的例子。
空间复杂度:我们需要新开辟一个和原数组大小相同的数组,故空间复杂度稳定为O(n)
时间复杂度:由于归并算法是个类似“二叉树”分割的算法,故划分过程Msort的复杂度为O(logn);随后的合并排序Merge中有三个while语句,但并非嵌套,故复杂度为O(n)。因此时间复杂度稳定为O(nlogn)

要特别提一下,归并排序的优势在于稳定,不同于快速排序,会有最优或最坏情况。因此在面对不同问题时要分别考虑,找到稳定和效率的平衡点,再决定采用的排序方法。

4 可应对的题:逆序数

问题:在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。
解答:

#include<iostream>

using namespace std;
int A[4] = { 7,5,6,4 };
int LENGTH = 4;

int Merge(int* data, int* copy, int start, int length, int end)//两个数组合并过程
{
	int i = start + length;		//原数组前一半的最后一个索引
	int j = end;				//原数组后一半的最后一个索引
	int newIndex = end;			//新数组最后一个索引

	int middle = 0;

	while (i >= start && j >= start + length + 1)//归并过程
	{
		if (copy[i] > copy[j])
		{
			data[newIndex] = copy[i];
			newIndex--;
			i--;
			middle += j - start - length;//增加的部分:把前面的元素移到后面所增加的逆序数
		}
		else
		{
			data[newIndex] = copy[j];
			newIndex--;
			j--;
		}
	}
	while (i >= start)//处理没有归并进去的剩余部分(最小的一些数字)
	{
		data[newIndex] = copy[i];
		newIndex--;
		i--;
	}
	while (j >= start + length + 1)//处理没有归并进去的剩余部分(最小的一些数字)
	{
		data[newIndex] = copy[j];
		newIndex--;
		j--;
	}

	return middle;
}
int Msort(int* data, int* copy, int start, int end)
{
	if (start == end)
	{
		copy[start] = data[start];
		return 0;
	}

	int length = (end - start) / 2;
	int left = Msort(copy, data, start, start + length);		//将前一半数组排序
	int right = Msort(copy, data, start + length + 1, end);		//将后一半数组排序
	int middle = Merge(data, copy, start, length ,end);			//将前后两个排序数组合并

	return left + right + middle;
}

void mergeSort(int* a)
{
	int* data = a;
	int* copy = new int[LENGTH];
	for (int i = 0; i < LENGTH; i++)
	{
		copy[i] = data[i];
	}
	int count = Msort(data, copy, 0, LENGTH - 1);
	delete[] copy;
	printf("逆序数的数量:%d\n", count);//打印出逆序数的数量
}

int main()
{
	
	mergeSort(A);
	for (int i = 0; i < LENGTH; i++)
	{
		printf("%d ", A[i]);
	}
	system("pause");
	return 0;
}

结果如下:
在这里插入图片描述
最后对比一下归并排序和逆序数的代码差别作为本篇博客的结语吧:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值