简介
看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)