1.概念
将一个数组排序,可以递归的将它们分成两半分别排序,然后将结果归并起来。体现了分治思想。算法时间复杂度:O(nlgn)
2.自顶向下的归并排序算法(递归实现)
上图所示为归并数组a[0...15]的调用轨迹图。
2.1初实现
template<typename T>
void mergeSort(T arr[], int n)
{
__mergeSort(arr, 0, n - 1);
}
//对arr[l...r]进行排序
template<typename T>
void __mergeSort(T arr[], int l, int r)
{
if (l >= r)
return;
int mid = (r + l) / 2; //int mid = l+(r-l)/2;
__mergeSort(arr, l, mid);
__mergeSort(arr, mid + 1, r);
__merge(arr, l, mid, r);
}
template<typename T>
void __merge(T arr[], int l, int mid, int r)
{
T *tmp = new T[r - l + 1];
for (int i = l; i <= r; i++)
{
tmp[i - l] = arr[i];
}
int i = l;
int j = mid + 1;
for (int k = l; k <= r; k++)
{
if (i > mid)
{
arr[k] = tmp[j - l];
j++;
}
else if (j > r)
{
arr[k] = tmp[i - l];
i++;
}
else if (tmp[i - l] < tmp[j - l])
{
arr[k] = tmp[i - l];
i++;
}
else
{
arr[k] = tmp[j - l];
j++;
}
}
delete[] tmp;
}
2.2 优化
//对arr[l...r]进行排序
template<typename T>
void __mergeSort(T arr[], int l, int r)
{
/*if (l >= r)
{
return;
}*/
//优化二 对于小范围的数据进行插入排序
if (r - l<15)
{
insertionSort(arr, l, r);
return;
}
int mid = (r + l) / 2; //int mid = l+(r-l)/2;
__mergeSort(arr, l, mid);
__mergeSort(arr, mid + 1, r);
if (arr[mid] <= arr[mid + 1]) //优化一
{
return;
}
__merge(arr, l, mid, r);
}
3.自底向上的归并排序(性能更优)
上图所示为归并数组a[0...15]的调用轨迹图。
3.1初实现
template<typename T>
void mergeSortBU(T arr[], int n)
{
//归并操作为两两操作
//sz: 归并时每组中元素的个数
for ( int sz=1; sz<=n; sz+=sz )
{
/*for (int i=0; i<n; i+=sz*2)
{
__merge(arr, i, i + sz - 1, i + sz * 2 - 1);
}*/
//i:代表数组下标,每次跳跃 (每组中元素的个数)*2组
//for终止条件:让第二个数组起始元素小于n
//将arr[i....i+sz-1]和arr[i+sz...i+sz*2-1] 归并
for ( int i=0; i+sz<n; i+=sz*2 )
{
__merge(arr, i, i + sz - 1, min(i + sz * 2 - 1, n));
}
}
}
template<typename T>
void __merge(T arr[], int l, int mid, int r)
{
//
T *tmp = new T[r - l + 1];
for (int i = l; i <= r; i++)
{
tmp[i - l] = arr[i];
}
int i = l;
int j = mid + 1;
for (int k = l; k <= r; k++)
{
if (i > mid)
{
arr[k] = tmp[j - l];
j++;
}
else if (j > r)
{
arr[k] = tmp[i - l];
i++;
}
else if (tmp[i - l] < tmp[j - l])
{
arr[k] = tmp[i - l];
i++;
}
else
{
arr[k] = tmp[j - l];
j++;
}
}
delete[] tmp;
}
3.2优化
根据2.2节提到的优化,可以得到如下代码。
template <typename T>
void mergeSortBU(T arr[], int n)
{
//首先,每16个元素为一组进行 插入排序
for( int i = 0 ; i < n ; i += 16 )
insertionSort(arr,i,min(i+15,n-1));
for( int sz = 16; sz < n ; sz += sz )
for( int i = 0 ; i < n - sz ; i += sz+sz )
// 对于arr[mid] <= arr[mid+1]的情况,不进行merge
if( arr[i+sz-1] > arr[i+sz] )
__merge(arr, i, i+sz-1, min(i+sz+sz-1,n-1) );
}
应用:求逆序对
归并排序中的__merge()函数,是将arr[l...mid]和arr[mid+1...r]两个有序数组合并,让j指向第一个数组中要归并的元素,让k指向第二个数组中要归并的元素,若当前第一个数组中i指向的元素大于第二个数组中j指向的元素,那么可知第一个数组中arr[j...mid]都可以与arr[k]组成逆序对,逆序对数为mid-j+1。
// 注:计算逆序数对的结果以long long返回,对于一个大小为N的数组, 其最大的逆序数对个数为 N*(N-1)/2, 非常容易产生整型溢出
// merge函数求出在arr[l...mid]和arr[mid+1...r]有序的基础上,求 arr[l...r]的逆序数对个数
long long __merge( int arr[], int l, int mid, int r){
int *aux = new int[r-l+1];
for( int i = l ; i <= r ; i ++ )
aux[i-l] = arr[i];
// 初始化逆序数对个数 res = 0
long long res = 0;
// 初始化,j指向左半部分的起始索引位置l;k指向右半部分起始索引位置mid+1
int j = l, k = mid + 1;
for( int i = l ; i <= r ; i ++ ){
if( j > mid ){ // 如果左半部分元素已经全部处理完毕
arr[i] = aux[k-l];
k ++;
}
else if( k > r ){ // 如果右半部分元素已经全部处理完毕
arr[i] = aux[j-l];
j ++;
}
else if( aux[j-l] <= aux[k-l] ){ // 左半部分所指元素 <= 右半部分所指元素
arr[i] = aux[j-l];
j ++;
}
else{ // 右半部分所指元素 < 左半部分所指元素
arr[i] = aux[k-l];
k ++;
// 此时, 因为右半部分k所指的元素小
// 这个元素和左半部分的所有未处理的元素都构成了逆序数对
// 左半部分此时未处理的元素个数为 mid - j + 1
res += (long long)(mid - j + 1);
}
}
delete[] aux;
return res;
}
// 求arr[l..r]范围的逆序数对个数
long long __inversionCount(int arr[], int l, int r){
if( l >= r )
return 0;
int mid = l + (r-l)/2;
// 求出 arr[l...mid] 范围的逆序数
long long res1 = __inversionCount( arr, l, mid);
// 求出 arr[mid+1...r] 范围的逆序数
long long res2 = __inversionCount( arr, mid+1, r);
return res1 + res2 + __merge( arr, l, mid, r);
}
// 递归求arr的逆序数对个数
long long inversionCount(int arr[], int n){
return __inversionCount(arr, 0, n-1);
}