左神数据结构与算法学习——归并排序及其拓展C++实现


简介

  看b站左神视频的学习笔记,按照他提出的几个针对归并排序的方法和问题,自己写了下C++的代码,可能有错,欢迎各位指出。

问题1

  基本的归并排序算法,原理在于利用递归的思想,不断将无序数组进行左右等分,并分别将左右部分进行同样的递归排序操作,然后将得到的左右有序数组进行合并。

代码实现

// 辅助函数,生成一个[start, end]的长度为size的随机数组
vector<int> RandVector(const int& start, const int& end, const int& size)
{
	vector<int> result(size);
	for (int& elem : result)
	{
		// mod (end - start + 1),它就仅能生成[0, end - start]范围内的整数,然后加上start,得到数的范围为[start, end]
		elem = start + (rand() % (end - start + 1));
	}
	return result;
}

// 该函数的效果是:将arr数组中[lowIndex, midIndex]和[midIndex + 1, highIndex]这两块区间的数值进行有序合并
void Merge(vector<int>& arr, const int& lowIndex, const int& midIndex, const int& highIndex)
{
	vector<int> help;
	int p1 = lowIndex;
	int p2 = midIndex + 1;
	while (p1 <= midIndex && p2 <= highIndex)
	{
		// 两部分均不越过对应边界的情况下,执行该循环,不断往help中拷贝值
		if (arr[p1] < arr[p2])
			help.push_back(arr[p1++]);
		else
			help.push_back(arr[p2++]);
	}
	if (p1 <= midIndex)
		while (p1 <= midIndex)
			// 将剩下的元素全部拷贝到help数组的末尾(如果还有)
			help.push_back(arr[p1++]);
	if (p2 <= highIndex)
		while (p2 <= highIndex)
			// 将剩下的元素全部拷贝到help数组的末尾(如果还有)
			help.push_back(arr[p2++]);
	// 给原数组[lowIndex, highIndex]挨个赋予有序数组的数值
	for (int i = lowIndex, j = 0; i <= highIndex; i++, j++)
	{
		arr[i] = help[j];
	}
}

// 归并排序
void MergeSort(vector<int>& arr, const int& lowIndex, const int& highIndex)
{
	// 如果数组arr中只有一个元素,则肯定有序,直接返回
	if (lowIndex == highIndex) return;
	// 求中间位置
	int midIndex = lowIndex + ((highIndex - lowIndex) >> 1);
	// 将[lowIndex, midIndex]之间进行排序
	MergeSort(arr, lowIndex, midIndex);
	// 将[lowIndex + 1, highIndex]之间进行排序
	MergeSort(arr, midIndex + 1, highIndex);
	// 将[lowIndex, midIndex]和[midIndex + 1, highIndex]这两块区间的数值进行有序合并
	Merge(arr, lowIndex, midIndex, highIndex);
}

void main()
{
	vector<int> arr = RandVector(10,1000,21);
	// === 显示原无序数组 === //
	cout << "原无序数组为:" << endl;
	for_each(arr.begin(), arr.end(), [](const int& elem) { cout << elem << " "; });	// 用算法中的for_each进行数组的显示
	cout << endl;
	// === 冒泡排序 === //
	MergeSort(arr, 0, arr.size() - 1);
	// === 显示排序后的数组 === //
	cout << "有序数组为:" << endl;
	for_each(arr.begin(), arr.end(), [](const int& elem) { cout << elem << " "; });
	cout << endl;
	system("pause");
}

问题2

问题概述

  小和问题,简而言之,就是在一个数组中,从左往右数,求出每一个数字左边比这个数字小的数字之和,这个和为该数字的小和,然后求出数组中所有数字的小和的和。

算法思路

  该问题是对归并算法的一种拓展,求每一个数字左边比它小的数之和,其实等价于统计每一个数字的右边有几个数比当前的数字大,然后用当前数字乘以右边比它大的数的个数,遍历所有数,然后求出得到结果的和。

代码实现

// 该函数用于合并两个子区间,同时统计在合并子区间过程中产生的小和
int Merge_SumofSmall(vector<int>& arr, const int& lowIndex, const int& midIndex, const int& highIndex)
{
	vector<int> help;
	int sum = 0;
	int p1 = lowIndex;
	int p2 = midIndex + 1;
	while (p1 <= midIndex && p2 <= highIndex)
	{
		if (arr[p1] < arr[p2])
		{
			// 值得注意的是,此处必须只有在arr[p1]严格小于arr[p2]的时候,才往数组里面推入arr[p1],否则都推入arr[p2]
			// 只有这样,才能正确统计每次推入左边小值的时候,右边有多少个数比该小值大,而不会计入相等的值
			help.push_back(arr[p1]);
			// 在一个数组范围内,统计每个数的左边有多少个数比它小,等价于对每一个数统计它右边有多少个数比它大
			sum += (highIndex - p2 + 1) * arr[p1++];
		}
		else
			help.push_back(arr[p2++]);
	}
	if (p1 <= midIndex)
		while (p1 <= midIndex)
			// 这个时候不要统计了,此时推入不是因为arr[p1]小,而是因为没有arr[p2]比它们大了
			help.push_back(arr[p1++]);
	if (p2 <= highIndex)
		while (p2 <= highIndex)
			help.push_back(arr[p2++]);
	// 将辅助数组中的有序数列复制到原数组对应位置
	for (int i = lowIndex, j = 0; i <= highIndex; i++, j++)
	{
		arr[i] = help[j];
	}
	return sum;
}

// 求解小和问题
int SumofSmall(vector<int>& arr, const int& lowIndex, const int& highIndex)
{
	// 如果只有一个数,无序排序,也无小和,直接返回0
	if (lowIndex == highIndex) return 0;
	// 求中间位置
	int midIndex = lowIndex + ((highIndex - lowIndex) >> 1);
	// 容易想到,母区间的小和等于左子区间的小和+右子区间的小和+左右子区间合并过程中产生的小和
	// 不知为何,直接写成这样结果并不正确
	/*return SumofSmall(arr, lowIndex, midIndex) \
		+SumofSmall(arr, midIndex + 1, highIndex) \
		+Merge_SumofSmall(arr, lowIndex, midIndex, highIndex);*/
	int sum_Left = SumofSmall(arr, lowIndex, midIndex);
	int sum_Right = SumofSmall(arr, midIndex + 1, highIndex);
	return sum_Left + sum_Right + Merge_SumofSmall(arr, lowIndex, midIndex, highIndex);
}

void main()
{
	vector<int>arr = RandVector(1, 10, 7);
	cout << "原数组为:" << endl;
	ShowVector(arr);
	cout << "数组的小和为:" << SumofSmall(arr, 0, arr.size() - 1) << endl;
	cout << "新数组为:" << endl;
	ShowVector(arr);

	system("pause");
}

问题3

问题描述

  在一个数组中,左边的数如果比右边的数大,则拆两个数构成一个逆序对,请打印所有逆序对。

问题思路

  依然考虑采用归并法升序排序,然后当从右边部分拷贝数的时候,统计当前左边部分有多少个数,当然,此时如果左右两边的数相同,优先拷贝左边的数。

代码实现

// 二分法查找无重复元素无序数组的局部最小值
static int pairNum = 1;
// 该函数用于合并两个子区间,同时统计在合并子区间过程中产生的小和
void Merge_PrintRevOrder(vector<int>& arr, const int& lowIndex, const int& midIndex, const int& highIndex)
{
	vector<int> help;
	int sum = 0;
	int p1 = lowIndex;
	int p2 = midIndex + 1;
	while (p1 <= midIndex && p2 <= highIndex)
	{
		if (arr[p1] > arr[p2])
		{
			// 如果arr[p2]严格小于arr[p1],则打印逆序对
			for (int i = p1; i <= midIndex; i++)
			{
				cout << "逆序对" << pairNum++ << ": (" << arr[i] << ", " << arr[p2] << ")" << endl;
			}
			/*for_each(arr.begin() + p1, arr.begin() + midIndex + 1, [=](const int& i) 
				{
					cout << "逆序对" << pairNum++ << ": (" << arr[p1] << ", " << arr[p2] << ")" << endl;
				});*/
			help.push_back(arr[p2++]);
		}
		else
		{
			help.push_back(arr[p1++]);
		}
			
	}
	if (p1 <= midIndex)
		while (p1 <= midIndex)
			// 这个时候不要统计了,此时推入不是因为arr[p1]小,而是因为没有arr[p2]比它们大了
			help.push_back(arr[p1++]);
	if (p2 <= highIndex)
		while (p2 <= highIndex)
			help.push_back(arr[p2++]);
	// 将辅助数组中的有序数列复制到原数组对应位置
	for (int i = lowIndex, j = 0; i <= highIndex; i++, j++)
	{
		arr[i] = help[j];
	}
}

// 求解小和问题
void PrintRevOrder(vector<int>& arr, const int& lowIndex, const int& highIndex)
{
	// 如果只有一个数,无序排序,也无小和,直接返回0
	if (lowIndex == highIndex) return;
	// 求中间位置
	int midIndex = lowIndex + ((highIndex - lowIndex) >> 1);
	// 容易想到,母区间的小和等于左子区间的小和+右子区间的小和+左右子区间合并过程中产生的小和
	// 不知为何,直接写成这样结果并不正确
	PrintRevOrder(arr, lowIndex, midIndex);
	PrintRevOrder(arr, midIndex + 1, highIndex);
	Merge_PrintRevOrder(arr, lowIndex, midIndex, highIndex);
}

void main()
{
	vector<int>arr = RandVector(1, 10, 7);
	cout << "原数组为:" << endl;
	ShowVector(arr);
	cout << "// ===== 打印逆序对 ===== //" << endl;
	PrintRevOrder(arr, 0, arr.size() - 1);
	cout << "新数组为:" << endl;
	ShowVector(arr);
	system("pause");
}

性质

时间复杂度: O(N*logN)
空间复杂度: O(N)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值