上两篇博客使用的选择排序和插入排序的算法复杂度都是O(n2),这在数组元素比较多的时候和一些算法复杂度为O(nlogn)的快速排序算法相比,差距还是很明显的,如下图
当有10万个元素的时候,快速排序算法比普通的选择排序算法要快了6000倍,
本篇博客介绍的是快速排序算法中的归并排序
归并排序是利用归并的思想实现的排序方法,该算法采用经典的分治策略(分治法将问题分成一些小的问题然后递归求解,而治的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。
可以看到这种结构很像一棵完全二叉树,本文的归并排序采用递归去实现,递归深度为logn。
分阶段就是将数组不断进行对半切分,直到最后每部分只剩下一个元素为止,此时只有一个元素我们就认为该部分是有序的。
治阶段,我们需要将两个已经有序的子序列合并成一个有序序列,比如上图中的最后一次合并,要将[4,5,7,8]和[1,2,3,6]两个已经有序的子序列,合并为最终序列[1,2,3,4,5,6,7,8],来看下实现步骤。
合并阶段需要在空间里另外开辟一个和数组相同大小的空间来存放排好序的数组,为上图的temp
定义指向temp的索引为k,指向左边序列的索引为i,指向右边序列的索引为j,左边序列最后一个元素的索引为mid
采用递归方法实现归并排序:
#include <iostream>
#ifndef _SORTINGHELP_H_
#define _SORTINGHELP_H_
#include "SortingHelp.h"
#endif // _SORTINGHELP_H_
using namespace std;
//将arr[l...mid]和arr[mid+1...r]两部分进行归并
template<typename T>
void __merge(T arr[], int l, int mid, int r){
T aux[r-l+1];
for (int i = l; i <= r; i++){
aux[i-l] = arr[i];
}
int i = l, j = mid + 1;
for (int k = l; k <= r; k++){
if (i > mid){
arr[k] = aux[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++;
}
}
}
//递归使用归并排序,对arr[l...r]的范围进行排序
template<typename T>
void __mergeSorting(T arr[], int l, int r){
if (l >= r)
return;
int mid = (l + r)/2;
__mergeSorting(arr, l, mid);
__mergeSorting(arr, mid+1, r);
if (arr[mid] < arr[mid+1])//如果左边的序列已经小于右边的序列,就不用合并了
__merge(arr, l, mid, r);
}
template<typename T>
void MergeSorting(T arr[], int n){
__mergeSorting(arr, 0, n-1);
}
int main()
{
int n = 50000;
int *arr = generateRandomArray(n, 0, n);
int *arr2 = copyIntArray(arr, n);
testSorting("InsertionSortingImproved", InsertionSortingImproved, arr2, n);
testSorting("MergeSorting", MergeSorting, arr2, n);
delete[] arr;//最后删除数组开辟的空间
delete[] arr2;
return 0;
}
SortingHelp.h定义为
#include <iostream>
#include <ctime> //time()函数
#include <cstdlib> //rand()函数
#include <cassert> //assert()函数
using namespace std;
int* generateRandomArray(int n, int rangeL, int rangeR){//生成随机数组
assert(rangeL < rangeR);
int *arr = new int[n];
srand(time(NULL));
for (int i = 0; i < n; i++){
arr[i] = rand() % (rangeR - rangeL + 1) + rangeL;
}
return arr;
}
int* generateNearlyOrderedArray(int n, int swapTimes){//生成近乎有序的数组
int *arr = new int[n];
for (int i = 0; i < n; i++){
arr[i] = i;
}
srand(time(NULL));
for (int i = 0; i < swapTimes; i++){
int posx = rand() % n;
int posy = rand() % n;
swap(arr[posx], arr[posy]);
}
return arr;
}
template<typename T>
void printArray(T arr[], int n){//打印数组元素
for (int i = 0; i < n; i ++){
cout<<arr[i]<<" ";
}
cout<<endl; //换行
return;
}
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 testSorting(string sortName, void(*sorting)(T[], int), T arr[], int n){
//第二个参数是传入排序函数的指针
clock_t startClock = clock();
sorting(arr, n);
clock_t endClock = clock();
assert(isSorted(arr, n));
cout<<sortName<<" : "<<double(endClock-startClock)/CLOCKS_PER_SEC<<" s"<<endl;
return;
}
int* copyIntArray(int arr[], int n){
int* arr2 = new int[n];
copy(arr, arr+n, arr2);
return arr2;
}
template<typename T> //定义模板类型,使对各种数据类型都适用,如double,float,string
void SelectionSorting(T a[], int n){//选择排序算法
for (int i = 0; i < n; i++){
int minIndex = i;
for (int j = i + 1; j < n; j++){
if (a[j] < a[minIndex])
minIndex = j;
}
swap(a[i], a[minIndex]);
}
}
template<typename T>
void InsertionSortingImproved(T arr[], int n){
for (int i = 0; i < n - 1; i++){
T temp = arr[i+1];
int j;
for (j = i + 1; j > 0; j--){
if (arr[j-1] > temp){
arr[j] = arr[j-1];
}
else{
break;
}
}
arr[j] = temp;
}
return;
}
运行时间为:
可以看出归并排序比普通的插入排序确实快了很多倍
对于近乎有序的数组来说,运行下面的测试程序
int main()
{
int n = 50000;
//int *arr = generateRandomArray(n, 0, n);
int *arr = generateNearlyOrderedArray(n, 10);//生成只有200个无序元素的数组
int *arr2 = copyIntArray(arr, n);
testSorting("InsertionSortingImproved", InsertionSortingImproved, arr, n);
testSorting("MergeSorting", MergeSorting, arr2, n);
delete[] arr;//最后删除数组开辟的空间
delete[] arr2;
return 0;
}
运行时间为
可以看出对于近乎有序的数组来说,插入排序的效果还是很好的,要优于归并排序