归并排序是一种时间赋值度为O(nlogn)级别的算法,其基本思想就是利用二分法将一个待排序列逐层向下划分,直到每一组中只有1个数据为止,然后将这些只有1个数据的序列都视为有序序列,逐层向上归并成有序序列。
根据二分法的思想可知,一个含有n个数据的序列,可以向下划分为logn个层级,而在每个层级上若可以使用时间复杂度为O(n)的算法来实现归并,则归并排序算法的时间复杂度即为O(nlogn)。
将有序序列两两向上归并时,采用的是一种递归思想,直到最后将序列的左右半有序序列归并成一个有序表,算法结束。
对于两个有序表的归并的图解如下:
归并排序算法c++代码如下:
SortTestHelper.h文件(辅助函数)
#include <iostream>
#include <cstdlib>
#include <ctime> //clock()、CLOCKS_PER_SEC
#include <cassert> //包含函数assert()
using namespace std;
namespace SortTestHelper
{
//辅助函数 - 随机产生一个数组
int* generateRandomArray(int n, int RangeL, int RangeR) //返回数组首地址
{
//判断RangeL是否<=RangeR
assert(RangeL <= RangeR); //参数为表达式,表达式为真时返回true,否则打印错误信息
int *arr = new int[n];
srand(time(0));
for(int i = 0; i < n ; i++)
{
arr[i] = rand() % (RangeR - RangeL + 1) + RangeL; //使得产生的随机数在RangeL和RangeR之间
}
return arr;
}
//辅助函数 - 产生一个近乎有序的随机数组
int* generateNearlyOrderedArray(int n, int swapTime)
{
int *arr = new int[n];
for(int i = 0; i < n; i++)
{
arr[i] = i; //先生成一个完全有序的数组
}
//然后交换几组元素,使之变成无序但近乎有序的数组
srand(time(0));
for(int j = 0; j < swapTime; j++)
{
//随机生成一个x位置和y位置
int posx = rand() % n;
int posy = rand() % n;
//交换x和y处的元素
swap(arr[posx], arr[posy]);
}
return arr;
}
//辅助数组 - 产生一个完全有序数组
int* generateTotallyOrderedArray(int n)
{
int *arr = new int[n];
for(int i = 0; i < n; i++)
{
arr[i] = i;
}
return arr;
}
//辅助函数 - 打印数组
template<typename T>
void printArray(T arr[], int n)
{
for(int i = 0; i < n; i++)
{
cout << arr[i] << " ";
}
cout << endl;
}
//辅助函数 - 判断数组是否有序(升序)
template<typename T>
bool isSorted(T arr[], int n)
{
for(int i = 0; i < n - 1; i++)
{
if(arr[i] > arr[i + 1])
{
return false;
}
}
return true;
}
//辅助函数 - 测试算法的时间
template<typename T>
void testSort(string sortname, void(*sort)(T[], int), T arr[], int n) //arr[]和n是函数指针需要的参数
{
clock_t starttime = clock();
sort(arr, n); //调用函数sort()
clock_t endtime = clock();
//判断排序是否成功
assert(isSorted(arr, n)); //若是数组无序,则assert会自动调用abort()退出程序,不会执行下面的语句
cout << sortname << " needs " << double(endtime - starttime) / CLOCKS_PER_SEC << "s." << endl;
}
//辅助函数 - 拷贝数组
int* copyIntArray(int a[], int n)
{
int *arr = new int[n];
//使用C++函数copy()
copy(a, a + n, arr);
return arr;
}
}
main.cpp文件(归并算法、插入算法,测试两者的时间性能)
#include <iostream>
#include "SortTestHelper.h"
/**
* 归并排序(递归过程):将有n个记录的数组采用二分法,直到每组只有1个记录(有序),然后再将有序序列两两归并;
*/
using namespace std;
template<typename T>
void __merge(T arr[], int l, int mid, int r)
{
//将左半部分arr[l...mid]和右半部分arr[mid + 1...r]进行归并
//辅助空间 - arr[l...r]的拷贝
T aux[r - l + 1];
for(int i = l; i <= r; i++) //这里的i是arr中的下标
{
//aux数组用于归并时用,归并结果存放在arr中
aux[i - l] = arr[i]; //将arr[l...r]拷贝给aux[0...r-l]
}
int i = l, j = mid + 1; //i,j是arr中的下标
for(int k = l; k <= r; k++) //k是arr中存放较小元素的位置下标的一个位置
{
//考虑数组是否越界(因为递归时已判断l < r,所以此处无需再判断l和r的大小)
if(i > mid)
{
arr[k] = arr[j - l];
j++;
}
else if(j > r)
{
arr[k] = aux[i - l];
i++;
}
else if(aux[i - l] < aux[j - l])
{
arr[k] = aux[i - l];
i++;
}
else
{
arr[k] = aux[j - l];
j++;
}
}
}
template<typename T>
void __mergeSort(T arr[], int l, int r) //一定要注意我设定的是左闭右闭[l...r](r是最后一个元素)
{
//递归使用归并排序,对arr[l...r]进行归并
//先考虑递归最底层
if(l >= r)
{
return;
}
else
{
int mid = (l + r) / 2;
//对左半部分arr[l...mid]进行归并排序
__mergeSort(arr, l, mid);
//再对右半部分arr[mid + 1...r]进行归并排序
__mergeSort(arr, mid + 1, r);
//然后将排好序的左右两部分归并到一起
__merge(arr, l, mid, r);
}
}
template<typename T>
void mergeSort(T arr[], int n)
{
//传递一个数组,调用归并排序算法归并arr[0...n-1]
__mergeSort(arr, 0, n - 1);
}
//插入排序
template<typename T>
void insertionSort(T arr[], int n)
{
for(int i = 1; i < n; i++) //0号单元已经有序
{
T e = arr[i]; //拷贝待插入元素,类型为T
//寻找arr[i]的合适的插入位置 - 循环
int j; //j就是插入位置的下标
for(j = i; j > 0 && arr[j - 1] > e; j--)
{
arr[j] = arr[j - 1];
}
//此时插入位置为j
arr[j] = e;
}
}
int main()
{
int n = 10000;
int *arr = SortTestHelper::generateRandomArray(n, 0, n);
int *arr2 = SortTestHelper::copyIntArray(arr, n);
SortTestHelper::testSort("mergeSort", mergeSort, arr, n);
SortTestHelper::testSort("insertionSort", insertionSort, arr2, n);
delete[] arr;
delete[] arr2;
return 0;
}
归并排序算法需要O(n)的辅助空间,而插入算法不需要辅助空间,但是一个算法的时间性能比空间性能重要的多,而从测试结果可以看到,归并算法的时间性能比插入算法好得多。