最近在看算法导论第三版,练习题2.3-2要求根据书中介绍,使用不带哨兵的归并排序算法。
归并排序的基本思想可以理解为:将数组二分,分成不能再分的单个元素为止,然后将两个单个的元素比较,按照大小顺序合并,一层一层向上递归。
如果不使用哨兵,就需要人工去检测边界,根据边界的变化处理合并时,可能出现的越界情况,算法性能分析参考算法导论。
归并算法流程:(注意这里使用的下标跟算法导论中原始的下标有出入,本作者自己觉着原书的写法用起来不方便)
算法流程:
Merge_sort(A,p,q):
if(p<q):
r=floor(p+q);
Merge_sort(A,p,r);
Merge_sort(A,r+1,q);
Merge(A,p,r,q);
template<typename T>
void merge_sort(std::vector<T> & input,int p,int q)
{
if (p < q) //左边界必须要于右边界
{
int r = (int)floor((q + p) / 2);
merge_sort(input, p, r); //将数组分成两个部分---前部分
merge_sort(input, r + 1, q); //将数组分成两个部分---后部分
merge<T>(input, p, r, q); //合并
}
}
最关键的部分就是合并!
上图为算法导论带哨兵的算法流程,我做的改进是在加入条件判断。在合并的时候,由于分出左右数组,左右数组的元素个数是在合并之前知道的,所以如果有一个数组的元素都被合并进去了,则直接将另外一个数组的元素按现有顺序放到新的数组中即可,知道填满。
template<typename T>
void merge(std::vector<T> &input, int p, int r, int q)
{
int n1 = r - p+1; //计算合并的所有左数组元素个数
int n2 = q - r; //计算合并的右数组元素个数
std::vector<T> l_vector, r_vector;
for (int i = 0; i < n1+n2; i++) //拷贝数据
{
if (i < n1)
l_vector.push_back(input[p + i]);
else
r_vector.push_back(input[p + i]);
}
int i = 0;
int j = 0;
for (int k = 0; k <= q - p; k++)
{
if (i < n1 && j < n2) //如果左右数组都存在未被拷贝完的元素,选择小的拷贝,并将相应的指针向后移动一个位置,直到结束
{
if (l_vector[i] < r_vector[j])
{
input[p+k] = l_vector[i];
i++;
}
else
{
input[p+k] = r_vector[j];
j++;
}
}
else if (i < n1 && j == n2) //这是右侧数组元素拷贝完成而左侧数组拷贝未完成,直接将左侧数组元素按照顺序向合并数组拷贝即可
{
input[p+k] = l_vector[i];
i++;
}
else if(i==n1 && j<n2) //这是左侧数组元素拷贝完成而左侧数组拷贝未完成,直接将右侧数组元素按照顺序向合并数组拷贝即可
{
input[p+k] = r_vector[j];
j++;
}
else if (n1 == 1 && n2 == 0) //处理边界情况,即存在级数个数是一定会发生只有一个元素,此时左侧有元素,右侧无元素,直接将单个元素拷贝即可,不需要比较大小
{
input[p+k] = l_vector[0];
}
}
}
测试用例如下:
void test_merge_sort()
{
std::vector<int> v = { 4,3,1,1 };
merge_sort(v, 0, v.size()-1);
for (auto x : v)
std::cout << x << std::endl;
}