theory
基本思想
归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。
分而治之
![](https://images2015.cnblogs.com/blog/1024555/201612/1024555-20161218163120151-452283750.png)
可以看到这种结构很像一棵完全二叉树,本文的归并排序我们采用递归去实现(也可采用迭代的方式去实现)。分阶段可以理解为就是递归拆分子序列的过程,递归深度为log2n。
合并相邻有序子序列
再来看看治阶段,我们需要将两个已经有序的子序列合并成一个有序序列,比如上图中的最后一次合并,要将[4,5,7,8]和[1,2,3,6]两个已经有序的子序列,合并为最终序列[1,2,3,4,5,6,7,8],来看下实现步骤。
![](https://images2015.cnblogs.com/blog/1024555/201612/1024555-20161218194508761-468169540.png)
![](https://images2015.cnblogs.com/blog/1024555/201612/1024555-20161218194621308-588010220.png)
code
<1> 递归
(a)使用vector
#include <algorithm>
#include <iostream>
#include <limits>
#include <vector>
using namespace std;
void Merge(vector<int>& Array, int front, int mid, int end)
{
vector<int> LeftSubArray(Array.begin() + front, Array.begin() + mid + 1);
vector<int> RightSubArray(Array.begin() + mid + 1, Array.begin() + end + 1);
int idxLeft = 0, idxRight = 0;
LeftSubArray.insert(LeftSubArray.end(), numeric_limits<int>::max());
RightSubArray.insert(RightSubArray.end(), numeric_limits<int>::max());
for(int i = front; i <= end; i++) {
if(LeftSubArray[idxLeft] < RightSubArray[idxRight]) {
Array[i] = LeftSubArray[idxLeft];
idxLeft++;
}
else {
Array[i] = RightSubArray[idxRight];
idxRight++;
}
}
}
void MergeSort(vector<int>& Array, int front, int end)
{
if(front >= end)
return;
int mid = (front + end) / 2;
MergeSort(Array, front, mid);
MergeSort(Array, mid + 1, end);
Merge(Array, front, mid, end);
}
int main()
{
int n = 12;
int num[n] = {23, 45, 17, 11, 13, 89, 72, 26, 3, 17, 11, 13};
vector<int> Array(num, num + n);
int i;
MergeSort(Array, 0, 11);
cout << "排序后的数组为:" << endl;
for(int i = 0; i < n; i++)
cout << Array[i] << ' ';
cout << endl;
return 0;
}
(b)使用一般数组
#include <algorithm>
#include <iostream>
#include <limits>
using namespace std;
void Merge(int* num, int front, int mid, int end)
{
int l, r;
int i;
int* left = NULL;
int* right = NULL;
int len = (end - front) / 2;
left = new int[len + 2];
right = new int[len + 2];
for(i = 0; i < len + 2; i++) {
left[i] = right[i] = INT_MAX;
}
for(i = 0; i <= mid - front; i++)
left[i] = num[front + i];
for(i = 0; i <= end - mid - 1; i++)
right[i] = num[mid + i + 1];
l = r = 0;
for(i = front; i <= end; i++) {
if(left[l] < right[r])
num[i] = left[l++];
else
num[i] = right[r++];
}
delete[] left;
delete[] right;
}
void MergeSort(int* num, int front, int end)
{
if(front >= end)
return;
int mid = (front + end) / 2;
MergeSort(num, front, mid);
MergeSort(num, mid + 1, end);
Merge(num, front, mid, end);
}
int main()
{
int n = 12;
int num[n] = {23, 45, 19, 11, 13, 89, 72, 26, 3, 17, 33, 13};
int i;
MergeSort(num, 0, 11);
cout << "排序后的数组为:" << endl;
for(int i = 0; i < n; i++)
cout << num[i] << ' ';
cout << endl;
return 0;
}
( c)不需要建立数组
老师说可以不建立数组,先占个坑,等到后面再写
填坑 只需要一开始建立两个数组,然后交替使用就好
#include <iostream>
using namespace std;
int* a = NULL;
int* b = NULL;
int cnt = 0;
void Merge(int left, int mid, int right) {
int i = left, j = mid + 1, k = left;
while(i <= mid && j <= right) {
cnt++;
if(a[i] <= a[j])
b[k++] = a[i++];
else
b[k++] = a[j++];
}
while(i <= mid)
b[k++] = a[i++];
while(j <= right)
b[k++] = a[j++];
}
void Copy(int left, int right) {
while(left <= right) {
a[left] = b[left];
left++;
}
}
void MergeSort(int left, int right) {
if(left < right) {
int mid = (left + right) >> 1;
MergeSort(left, mid);
MergeSort(mid + 1, right);
Merge(left, mid, right);
Copy(left, right);
}
}
int main() {
freopen("file.txt", "r", stdin);
int n;
cin >> n;
a = new int[n];
b = new int[n];
for(int i = 0; i < n; i++) {
scanf("%d", &a[i]);
}
MergeSort(0, n - 1);
cout << "排序后的数组为:" << endl;
for(int i = 0; i < n; i++) {
if(i) printf(" ");
printf("%d", a[i]);
}
cout << endl;
printf("%d\n", cnt);
return 0;
}
<2> 迭代
- 递归会占用大量的内存,使用迭代可以提升效率,大部分的递归都可以写成迭代
- 直接从最底层开始,两个一组,然后四个一组,八个一组,最后完成
#include <iostream>
using namespace std;
template <typename T>
void PrintArray(T arr[], int n)
{
int i;
for(i = 0; i < n; i++) {
cout << arr[i] << " ";
}
cout << endl;
}
template <typename T>
void MergeSort(T arr[], int n)
{
int step, left_min, left_max, right_min, right_max;
int cnt;
T* temp = new T[n];
for(step = 1; step < n; step += step) {
for(left_min = 0; left_min < n - step; left_min = right_max) {
right_min = left_max = left_min + step;
right_max = right_min + step;
if(right_max > n)
right_max = n;
cnt = 0;
while(left_min < left_max && right_min < right_max) {
if(arr[left_min] < arr[right_min])
temp[cnt++] = arr[left_min++];
else
temp[cnt++] = arr[right_min++];
}
while(left_min < left_max)
arr[--right_min] = arr[--left_max];
while(cnt > 0)
arr[--right_min] = temp[--cnt];
}
}
delete[] temp;
}
int main()
{
int n = 12;
int arr[n] = {23, 45, 19, 11, 13, 89, 72, 26, 3, 17, 33, 13};
int i;
MergeSort(arr, n);
cout << "排序后的数组为:" << endl;
PrintArray(arr, n);
return 0;
}
summary
-
归并排序是稳定排序,它也是一种十分高效的排序,能利用完全二叉树特性的排序一般性能都不会太差。每一层合并操作的平均时间复杂度为O(n),而完全二叉树的深度为|log2n|。总的平均时间复杂度为O(nlogn)。而且,归并排序的最好,最坏,平均时间复杂度均为O(nlogn)。
-
熟悉了Vector的使用https://blog.csdn.net/qq_41680771/article/details/121314596
-
递归版本中建立数组的一个致命bug记录
我刚把其中一个len+2,写成了mid+2,如下
![在这里插入图片描述](https://img-blog.csdnimg.cn/4d1d9562e428478d8f145b59307f27a5.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/c682c13d77964f28bd711a52de543ab4.png)
比如到了后面mid为4,len为1,我就会给本来没有申请的空间赋值,造成后delete回收出现问题
这是vscode运行时的报错
![在这里插入图片描述](https://img-blog.csdnimg.cn/a32a69b05be64f09b8eb13153554cbd7.png)
但是这样也能计算出最后正确的结果(因为后面只取front到end个值放在array里面,不会访问到后面那些内存不匹配的值),并且在调试窗口看数组的值的话是看不出来的,没有申请的内存也会赋值并且显示,运行exe文件的时候就出错了(当然exe文件不会报错,只会什么也没有,就消失了,或许这就是闪退(*^﹏^*)),system(“pause”)都没有用,我检查了好几遍都没有发现,最后还是请教了我的老师才发现了这个问题,他并没有直接看调试控制台,而是用了最朴素的方法,每次递归的时候打印输出front,mid和end的值,中途发现卡注了,然后发现了这个问题。
-
new和delete一般配对使用,如果没有手动delete的话,程序结束后系统也会自动回收这些内存。
-
new和delete属于c++的关键字,不需要头文件,但是使用c语言的malloc和free的话需要包含头文件 #include<stdlib.h>