• 归并排序


    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);
    }
    
  • 相关阅读:
    诸葛亮的后半生:狗笼子里挥舞丈八蛇矛
    一句话摘录
    【书摘】The Joshua tree epiphany
    玩具程序:bigInt
    旅行的力量
    记忆的力量
    快的力量
    Windbg学习笔记【4】
    戴尔笔记本win8全新安装
    悟透JavaScript
  • 原文地址:https://www.cnblogs.com/EngineerZhang/p/9408976.html
Copyright © 2020-2023  润新知