前言
本文继续排序算法的回顾。本文主要是针对归并排序进行分析,分别从递归的角度和分治的角度对归并进行认识,并附上c++的实现
一、归并排序介绍(代码在大标题二)
同样,这里我们以一个乱序数组为例。有数组 a[5]
a[0] | a[1] | a[2] | a[3] | a[4] |
---|---|---|---|---|
3 | 2 | 7 | 1 | 0 |
len = 5
startIndex = 0
endIndex = n - 1 = 5 - 1 = 4
1.1 思路
归并排序的中心思路可以概括为从局部有序到整体有序。具体来说,就是可以将原数组进行分割(分治),分别进行运算(递归),实现局部有序,然后通过一个外排进而实现整体有序。
1.2 基本流程
结合a[5],就是先将数组切成两半
然后进行局部的排序,并借助两个指针实现外排(借助辅助数组,a和b中的较小值填入辅助数组,并且右移,直到其中一个指针遍历了其所在的子数组的全部元素,结束并将另外的数组的剩余元素全部拷贝到辅助数组),进而实现整体有序。(图片这里仅展示了第一次分治的过程,实际上伴随着递归,子数组也会被再一次分治,外排,直到无法进行分割,直观来说,就是辅助数组被构建了logN次)
最后将辅助数组数据拷贝到原数组即可。
时间复杂度分析:
- 子数组元素的数量是原数组元素的数量的一半
T(N) = 2T(N/2) - 此外,外派的复杂度为O(N),故有
T(N) = 2T(N/2) + O(N) - 根据Master公式有
log(b, a) > d 复杂度为O(N^log(b, a) )
log(b, a) = d 复杂度为O((N^d) * logN)
log(b, a) < d 复杂度为O(N^d )
所以有归并的时间复杂度为:O(N*logN)
二、代码
#include <iostream>
#include <vector>
using namespace std;
void Merge(vector<int> &v, int start, int mid, int end)
{
vector<int> temp(end - start + 1, 0);
int i = 0;
int p1 = start; //左侧指针
int p2 = mid + 1; //右侧指针
//越界时跳出
while (p1 <= mid && p2 <= end)
{
temp[i++] = v[p1] < v[p2] ? v[p1++] : v[p2++];
}
//拷贝剩余的数据到辅助数组
while (p1 <= mid)
{
temp[i++] = v[p1++];
}
while (p2 <= end)
{
temp[i++] = v[p2++];
}
//数据拷贝
for (i = 0; i < temp.size(); i++)
{
v[start + i] = temp[i];
}
}
void SortProcess(vector<int> &v, int start, int end)
{
if (start == end)
{
return;
}
int mid = start + ((end - start) >> 1);
SortProcess(v, start, mid);
SortProcess(v, mid + 1, end);
//合并
Merge(v, start, mid, end);
}
void MergeSort(vector<int> &v)
{
if (v.size() < 2)
{
return;
}
//分插
SortProcess(v, 0, v.size()-1);
}
int main()
{
//测试
vector<int> v = { 3,2,7,1,0 };
for (int i = 0; i < v.size(); i++)
{
cout << v[i] << "\t";
}
cout << endl;
MergeSort(v);
for (int i = 0; i < v.size(); i++)
{
cout << v[i] << "\t";
}
cout << endl;
system("pause");
return 0;
}
总结
笔者在回顾的时候,这段编码还是错误挺多次的,主要问题出现在Merge函数部分,即辅助数组构建时候出了问题,究其原因,主要是对递归的理解不到位。总结来讲,归并排序的各个阶段不难理解,但是当这些内容串联起来是容易犯一些错误。