zoukankan      html  css  js  c++  java
  • 【每日算法】归并排序算法

    1)算法简介

    归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。归并排序是一种稳定的排序方法。

    将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。

    2)算法描述

    归并排序具体算法描述如下(递归版本):
    1、Divide: 把长度为n的输入序列分成两个长度为n/2的子序列。
    2、Conquer: 对这两个子序列分别采用归并排序。
    3、Combine: 将两个排序好的子序列合并成一个最终的排序序列。

    归并排序的效率是比较高的,设数列长为N,将数列分开成小数列一共要logN步,每步都是一个合并有序数列的过程,时间复杂度可以记为O(N),故一共为O(NlogN)。因为归并排序每次都是在相邻的数据中进行操作,所以归并排序在O(NlogN)的几种排序方法(快速排序,归并排序,希尔排序,堆排序)也是效率比较高的。

    3)算法图解、flash演示、视频演示

    图解:

    Flash:
    可以参考http://ds.fzu.edu.cn/fine/resources/FlashContent.asp?id=93中的过程
    视频:舞动的排序算法 归并排序
    http://video.sina.com.cn/v/b/80012415-1642346981.html

    4)算法代码

    //将有二个有序数列a[first...mid]和a[mid...last]合并。  
    void MergeArray(int a[], int first, int mid, int last, int temp[])  
    {  
        int i = first, j = mid + 1;  
        int m = mid,   n = last;  
        int k = 0;  
        while (i <= m && j <= n) {  
            if (a[i] <= a[j])  
                temp[k++] = a[i++];  
            else  
                temp[k++] = a[j++];  
        }  
        while (i <= m)  
            temp[k++] = a[i++];  
        while (j <= n)  
            temp[k++] = a[j++];  
        for (i = 0; i < k; i++)  
            a[first + i] = temp[i];  
    }  
    
    //递归地完成归并排序  
    void MergeSort(int a[], int first, int last, int temp[]) {  
        if (first < last) {  
            int mid = (first + last) / 2;  
            mergesort(a, first, mid, temp);    //左边有序  
            mergesort(a, mid + 1, last, temp); //右边有序  
            mergearray(a, first, mid, last, temp); //再将二个有序数列合并  
        }  
    }  
    

    5)考察点、重点和频度分析

    归并排序本身作为一种高效的排序算法,也是常会被问到的。尤其是归并排序体现的递归思路很重要,在递归的过程中可以完成很多事情,很多算法题也是使用的这个思路,可见下面7)部分的笔试面试算法题。

    6)笔试面试题

    例题1、题目输入一个数组,数组元素的大小在0->999.999.999的范围内,元素个数为0-500000范围。题目要求通过相邻的元素的交换,使得输入的数组变为有序,要求输出交换的次数
    这题求解的其实就是一个逆序对。我们回想一下归并排序的过程:

    归并排序是用分治思想,分治模式在每一层递归上有三个步骤:
    分解:将n个元素分成个含n/2个元素的子序列。
    解决:用合并排序法对两个子序列递归的排序。
    合并:合并两个已排序的子序列已得到排序结果。

    在归并排序算法中稍作修改,就可以在n log n的时间内求逆序对。
    将数组A[1...size],划分为A[1...mid] 和 A[mid+1...size].那么逆序对数的个数为 f(1, size) = f(1, mid) + f(mid+1, size) + s(1, mid, size),这里s(1, mid, size)代表左值在[1---mid]中,右值在[mid+1, size]中的逆序对数。由于两个子序列本身都已经排序,所以查找起来非常方便。

    代码如下:

    #include<iostream>  
    #include<stdlib.h>  
    using namespace std;  
    void printArray(int arry[],int len)  
    {  
        for(int i=0;i<len;i++)  
            cout<<arry[i]<<" ";  
        cout<<endl;  
    }  
    int MergeArray(int arry[],int start,int mid,int end,int temp[])//数组的归并操作  
    {  
        //int leftLen=mid-start+1;//arry[start...mid]左半段长度  
        //int rightLlen=end-mid;//arry[mid+1...end]右半段长度  
        int i=mid;  
        int j=end;  
        int k=0;//临时数组末尾坐标  
        int count=0;  
        //设定两个指针ij分别指向两段有序数组的头元素,将小的那一个放入到临时数组中去。  
        while(i>=start&&j>mid)  
        {  
            if(arry[i]>arry[j])  
            {  
                temp[k++]=arry[i--];//从临时数组的最后一个位置开始排序  
                count+=j-mid;//因为arry[mid+1...j...end]是有序的,如果arry[i]>arry[j],那么也大于arry[j]之前的元素,从a[mid+1...j]一共有j-(mid+1)+1=j-mid  
                  
            }  
            else  
            {  
                temp[k++]=arry[j--];  
            }  
        }  
        cout<<"调用MergeArray时的count:"<<count<<endl;  
        while(i>=start)//表示前半段数组中还有元素未放入临时数组  
        {  
            temp[k++]=arry[i--];  
        }  
        while(j>mid)  
        {  
            temp[k++]=arry[j--];  
        }  
        //将临时数组中的元素写回到原数组当中去。  
        for(i=0;i<k;i++)  
            arry[end-i]=temp[i];  
        printArray(arry,8);//输出进过一次归并以后的数组,用于理解整体过程  
        return count;  
    }  
    int InversePairsCore(int arry[],int start,int end,int temp[])  
    {  
        int inversions = 0;    
        if(start<end)  
        {  
            int mid=(start+end)/2;  
            inversions+=InversePairsCore(arry,start,mid,temp);//找左半段的逆序对数目  
            inversions+=InversePairsCore(arry,mid+1,end,temp);//找右半段的逆序对数目  
            inversions+=MergeArray(arry,start,mid,end,temp);//在找完左右半段逆序对以后两段数组有序,然后找两段之间的逆序对。最小的逆序段只有一个元素。  
        }      
        return inversions;  
    }  
    int InversePairs(int arry[],int len)  
    {  
        int *temp=new int[len];  
        int count=InversePairsCore(arry,0,len-1,temp);  
        delete[] temp;  
        return count;  
    }  
    void main()  
    {  
        //int arry[]={7,5,6,4};  
        int arry[]={1,3,7,8,2,4,6,5};  
        int len=sizeof(arry)/sizeof(int);  
        //printArray(arry,len);  
        int count=InversePairs(arry,len);  
        //printArray(arry,len);  
        //cout<<count<<endl;  
        system("pause");  
    }        
    

    例题2、有10个文件,每个文件1G,每个文件的每一行存放的都是用户的query,每个文件的query都可能重复。要求你按照query的频度排序。
    1、hash映射:顺序读取10个文件,按照hash(query)%10的结果将query写入到另外10个文件(记为)中。这样新生成的文件每个的大小大约也1G(假设hash函数是随机的)。
    2、hash统计:找一台内存在2G左右的机器,依次对用hash_map(query, query_count)来统计每个query出现的次数。注:hash_map(query,query_count)是用来统计每个query的出现次数,不是存储他们的值,出现一次,则count+1。
    3、堆/快速/归并排序:利用快速/堆/归并排序按照出现次数进行排序。将排序好的query和对应的query_cout输出到文件中。这样得到了10个排好序的文件(记为)。对这10个文件进行归并排序(内排序与外排序相结合)。

    例题3、归并一个左右两边分别排好序的数组,空间复杂度要求O(1)。
    使用原地归并,能够让归并排序的空间复杂度降为O(1),但是速度上会有一定程度的下降。代码如下:

    #include<iostream>  
    #include<cmath>  
    #include<cstdlib>  
    #include<Windows.h>  
    using namespace std;  
    
    void insert_sort(int arr[],int n) {  
        for(int i=1;i<n;++i) {  
            int val=arr[i];  
            int j=i-1;  
            while(arr[j]>val&&j>=0) {  
                arr[j+1]=arr[j];  
                --j;  
            }  
            arr[j+1]=val;  
        }  
    }  
    
    void aux_merge(int arr[],int n,int m,int aux[]) {  
        for(int i=0;i<m;++i)  
        swap(aux[i],arr[n+i]);  
        int p=n-1,q=m-1;  
        int dst=n+m-1;  
        for(int i=0;i<n+m;++i) {  
            if(p>=0) {  
                if(q>=0) {  
                    if(arr[p]>aux[q]) {  
                        swap(arr[p],arr[dst]);  
                        p--;  
                    } else {  
                        swap(aux[q],arr[dst]);  
                        q--;  
                    }  
                } else 
                    break;  
            } else {  
                swap(aux[q],arr[dst]);  
                q--;  
            }  
            dst--;  
        }  
    }  
    
    void local_merge(int arr[],int n) {  
        int m=sqrt((float)n);  
        int k=n/m;  
        for(int i=0;i<m;++i)  
            swap(arr[k*m-m+i],arr[n/2/m*m+i]);  
        for(int i=0;i<k-2;++i) {  
            int index=i;  
            for(int j=i+1;j<k-1;++j)  
                if(arr[j*m]<arr[index*m])  
                    index=j;  
                if(index!=i)  
                    for(int j=0;j<m;++j)  
                        swap(arr[i*m+j],arr[index*m+j]);  
        }  
        for(int i=0;i<k-2;++i)  
            aux_merge(arr+i*m,m,m,arr+(k-1)*m);  
        int s=n%m+m;  
        insert_sort(arr+(n-2*s),2*s);  
        aux_merge(arr,n-2*s,s,arr+(k-1)*m);  
        insert_sort(arr+(k-1)*m,s);  
    }  
    
    void local_merge_sort(int arr[],int n) {  
        if(n<=1)  
            return;  
        if(n<=10) {  
            insert_sort(arr,n);  
            return;  
        }  
        local_merge_sort(arr,n/2);  
        local_merge_sort(arr+n/2,n-n/2);  
        local_merge(arr,n);  
    }  
    
    void merge_sort(int arr[],int temp[],int n) {  
        if(n<=1)  
            return;  
        if(n<=10) {  
            insert_sort(arr,n);  
            return;  
        }  
        merge_sort(arr,temp,n/2);  
        merge_sort(arr+n/2,temp,n-n/2);  
        for(int i=0;i<n/2;++i)  
            temp[i]=arr[i];  
        for(int i=n/2;i<n;++i)  
            temp[n+n/2-i-1]=arr[i];  
        int left=0,right=n-1;  
        for(int i=0;i<n;++i)  
            if(temp[left]<temp[right])  
                arr[i]=temp[left++];  
            else  
                arr[i]=temp[right--];  
    }  
    
    const int n=2000000;  
    int arr1[n],arr2[n];  
    int temp[n];
    
    int main() {  
        for(int i=0;i<n;++i)  
        arr1[i]=arr2[i]=rand();  
        int begin=GetTickCount();  
        merge_sort(arr1,temp,n);  
        cout<<GetTickCount()-begin<<endl;  
        begin=GetTickCount();  
        local_merge_sort(arr2,n);  
        cout<<GetTickCount()-begin<<endl;  
        for(int i=0;i<n;++i)  
            if(arr1[i]!=arr2[i])  
        cout<<"ERROR"<<endl;  
        system("pause");  
    }  
    
  • 相关阅读:
    了解windows下的npm
    “jupyter notebook 不能导入python库但是终端上可以实现”的问题的解决
    本地创建的jupyter notebook 无法连接本地环境(即不能运行代码)
    win10 + ubuntu 下右键新建md文件(转载)
    LeetCode刷题2
    【入门】离散化
    【10.5NOIP普及模拟】sum
    【10.5NOIP普及模拟】sort
    【2020.02.01NOIP普及模拟4】怪兽
    [图论]最小花费
  • 原文地址:https://www.cnblogs.com/shih/p/6660136.html
Copyright © 2011-2022 走看看