归并排序

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);
}
posted @ 2018-08-02 18:33  神秘的火柴人  阅读(208)  评论(0编辑  收藏  举报