相比于快速排序,归并排序就十分稳定,无论是最好还是最差的情况,它的时间复杂度都为O(nlogn)。归并排序同样利用了递归的思想,它的思想是把一个问题分解成多个小问题,然后一个一个解决再合并起来,这种思想对我们的工作生活也同样很有帮助,废话不多说,下面来实际看看归并排序。
归并排序会将数组对半分开直到不可分割为止。
然后每一个子块会排序后向上合并。
与快速排序不同的是,归并排序会递归到无法分割才会开始排序,然后不停合并。
下面我们来看看C++如何实现归并排序
归并排序代码
#include<iostream>
//使用动态数组存储数据
#include<vector>
using namespace std;
template<typename T>
//输出运算符重载模板,方便输出vector数组
template<typename T>
ostream& operator<<(ostream& os, vector<T> v)
{
os << "[";
for (auto i: v )
{
os << i << ",";
}
os << "\b]" << endl;
return os;
}
//合并,默认Ascending为true,升序合并,Ascending为false时降序合并
//vec 要进行归并的数组
//left 左边的位置
//mid 中间的位置
//right 右边的位置
template<typename T>
void merge(vector<T> &v, int left,int mid, int right,bool Ascending = true)
{
//拷贝一份数组
vector<T> temp(v.begin() + left, v.begin() + right + 1);
//指向左边块的元素下标
int lt = left;
//指向右边块的元素下标
int rt = mid+1;
//默认为升序排序
if (Ascending)
{
//一次合并
for (int i = left; i <= right; i++)
{
if (lt > mid)//左边的位置到底了,将右边的归并上去
{
v[i] = temp[rt - left];
rt++;
}
else if (rt > right)//右边的位置到底了,将左边的归并上去
{
v[i] = temp[lt - left];
lt++;
}
else if (temp[rt - left] > temp[lt - left])//左边和右边的索引元素进行比较
{
//左边的数据小,左边的归并上去
v[i] = temp[lt - left];
lt++;
}
else
{
//右边数据小,右边的归并上去
v[i] = temp[rt - left];
rt++;
}
}
}
//降序排序
else
{
for (int i = left; i <= right; i++)
{
if (lt > mid)
{
v[i] = temp[rt - left];
rt++;
}
else if (rt > right)
{
v[i] = temp[lt - left];
lt++;
}
else if (temp[rt - left] < temp[lt - left])
{
v[i] = temp[lt - left];
lt++;
}
else
{
v[i] = temp[rt - left];
rt++;
}
}
}
}
//归并排序,先分割到不可分割,再排序
template<typename T>
void merge_sort(vector<T> &v, int left, int right, bool Ascending = true)
{
if (left>=right)
{
return;
}
int mid = (left + right) / 2; //准备分解的中间位置
merge_sort(v, left, mid, Ascending);//继续分割左边的数据
merge_sort(v, mid + 1, right, Ascending);//继续分割右边的数据
merge(v, left, mid, right,Ascending);//合并左右的数据(执行到这时左右已经有序)
}
//随机给所有元素赋值
template<typename T>
void random(vector<T>& v)
{
srand((unsigned int)time(nullptr));
for (auto& e : v)
{
e = (rand() + rand()) % (v.size());
}
}
//主函数测试
int main(void)
{
vector<int> vec(100);
random(vec);
//如果需要降序排序,只需要再最后传入false
//fast_sort(vec, 0, vec.size() - 1, false);
merge_sort(vec, 0, vec.size() - 1);
cout << vec;
return 0;
}
运行结果如下:
虽然归并排序比快速排序的效率更加稳定,但是它是牺牲了空间的,它的空间复杂度为O(n)。在使用归并排序的时候一定要确保内存空间的大小足够。
百闻不如一见,下面博主通过将大小为一万的随机数组进行排序,通过比较冒泡排序,归并排序,快速排序所用的时间来直观地感受三者的效率。
下面给出冒泡排序和快速排序的函数,由于需要用到时间,请导入头文件#include <ctime>
冒泡排序
template<typename T>
void bubbling_sort(vector<T> &v)
{
int length = v.size();
for (unsigned int i = 0; i < v.size(); i++)
{
length--;
for (int j = 0; j < length; j++)
{
if (v[j]>v[j+1])
{
swap(v[j], v[j + 1]);
}
}
快速排序
template<typename T>
void fast_sort(vector<T> &v, int left, int right,bool Ascending=true)
{
//当左边的下标大于等于右边即视为不可分割,递归结束
if (left>=right)
{
return;
}
int pt = left+1;
//基准值
T val = v[left];
//默认为升序排序
if (Ascending)
{
//一次快速排序
for (int i = left + 1; i <= right; i++)
{
if (val > v[i])
{
swap(v[i], v[pt++]);
}
}
}
//降序排序
else
{
for (int i = left + 1; i <= right; i++)
{
if (val < v[i])
{
swap(v[i], v[pt++]);
}
}
}
swap(v[left], v[--pt]);
//将左边进行快速排序
fast_sort(v, left, pt-1,Ascending);
//将右边进行快速排序
fast_sort(v, pt+1, right,Ascending);
}
主函数
int main(void)
{
vector<int> vec(10000);
random(vec);
vector<int> vec1(vec);
vector<int> vec2(vec);
cout << "归并排序:" << endl;
clock_t start = clock();
merge_sort(vec, 0, vec.size() - 1);
clock_t end = clock();
cout << "排序的数据量:" << vec.size() << endl;
cout << "排序用到的时间:" << (float)(end - start) / 1000 << "s" << endl;
cout << "============" << endl;
cout << "快速排序:" << endl;
clock_t start1 = clock();
fast_sort(vec1, 0, vec1.size() - 1);
clock_t end1 = clock();
cout << "排序的数据量:" << vec.size() << endl;
cout << "排序用到的时间:" << (float)(end1 - start1) / 1000 << "s" << endl;
cout << "============" << endl;
cout << "冒泡排序:" << endl;
clock_t start2 = clock();
bubbling_sort(vec2);
clock_t end2 = clock();
cout << "排序的数据量:" << vec2.size() << endl;
cout << "排序用到的时间:" << (float)(end2 - start2) / 1000 << "s" << endl;
return 0;
}
运行结果如下:
可以看到归并排序和快速排序的效率都是很不错的,而冒泡排序的效率可就太低了。虽然看起来快速排序的效率最高,但是之前也说过了,快速排序在极端情况下可能效率与冒泡排序相当。
今天的分享就到这里了,希望大家能够有所收获。