归并排序

来源:互联网 发布:cpda数据分析师 含金量 编辑:程序博客网 时间:2024/06/06 03:47

1)算法简介

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

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

2)算法描述

    归并排序具体算法描述如下(递归版本):

    1、Divide: 把长度为n的输入序列分成两个长度为n/2的子序列。

    2、Conquer: 对这两个子序列分别采用归并排序。

    3、Combine: 将两个排序好的子序列合并成一个最终的排序序列。

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

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

图解:


4)算法代码:

[cpp] view plain copy
  1. //将有二个有序数列a[first...mid]和a[mid...last]合并。  
  2. void MergeArray(int a[], int first, int mid, int last, int temp[])  
  3. {  
  4. int i = first, j = mid + 1;  
  5. int m = mid,   n = last;  
  6. int k = 0;  
  7. while (i <= m && j <= n)  
  8. {  
  9. if (a[i] <= a[j])  
  10. temp[k++] = a[i++];  
  11. else  
  12. temp[k++] = a[j++];  
  13. }  
  14. while (i <= m)  
  15. temp[k++] = a[i++];  
  16. while (j <= n)  
  17. temp[k++] = a[j++];  
  18. for (i = 0; i < k; i++)  
  19. a[first + i] = temp[i];  
  20. }  
  21. //递归地完成归并排序  
  22. void MergeSort(int a[], int first, int last, int temp[])  
  23. {  
  24. if (first < last)  
  25. {  
  26. int mid = (first + last) / 2;  
  27. mergesort(a, first, mid, temp);    //左边有序  
  28. mergesort(a, mid + 1, last, temp); //右边有序  
  29. mergearray(a, first, mid, last, temp); //再将二个有序数列合并  
  30. }  
  31. }  


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]中的逆序对数。由于两个子序列本身都已经排序,所以查找起来非常方便。

代码如下:

[cpp] view plain copy
  1. #include<iostream>  
  2. #include<stdlib.h>  
  3. using namespace std;  
  4. void printArray(int arry[],int len)  
  5. {  
  6.     for(int i=0;i<len;i++)  
  7.         cout<<arry[i]<<" ";  
  8.     cout<<endl;  
  9. }  
  10. int MergeArray(int arry[],int start,int mid,int end,int temp[])//数组的归并操作  
  11. {  
  12.     //int leftLen=mid-start+1;//arry[start...mid]左半段长度  
  13.     //int rightLlen=end-mid;//arry[mid+1...end]右半段长度  
  14.     int i=mid;  
  15.     int j=end;  
  16.     int k=0;//临时数组末尾坐标  
  17.     int count=0;  
  18.     //设定两个指针ij分别指向两段有序数组的头元素,将小的那一个放入到临时数组中去。  
  19.     while(i>=start&&j>mid)  
  20.     {  
  21.         if(arry[i]>arry[j])  
  22.         {  
  23.             temp[k++]=arry[i--];//从临时数组的最后一个位置开始排序  
  24.             count+=j-mid;//因为arry[mid+1...j...end]是有序的,如果arry[i]>arry[j],那么也大于arry[j]之前的元素,从a[mid+1...j]一共有j-(mid+1)+1=j-mid  
  25.               
  26.         }  
  27.         else  
  28.         {  
  29.             temp[k++]=arry[j--];  
  30.         }  
  31.     }  
  32.     cout<<"调用MergeArray时的count:"<<count<<endl;  
  33.     while(i>=start)//表示前半段数组中还有元素未放入临时数组  
  34.     {  
  35.         temp[k++]=arry[i--];  
  36.     }  
  37.     while(j>mid)  
  38.     {  
  39.         temp[k++]=arry[j--];  
  40.     }  
  41.     //将临时数组中的元素写回到原数组当中去。  
  42.     for(i=0;i<k;i++)  
  43.         arry[end-i]=temp[i];  
  44.     printArray(arry,8);//输出进过一次归并以后的数组,用于理解整体过程  
  45.     return count;  
  46. }  
  47. int InversePairsCore(int arry[],int start,int end,int temp[])  
  48. {  
  49.     int inversions = 0;    
  50.     if(start<end)  
  51.     {  
  52.         int mid=(start+end)/2;  
  53.         inversions+=InversePairsCore(arry,start,mid,temp);//找左半段的逆序对数目  
  54.         inversions+=InversePairsCore(arry,mid+1,end,temp);//找右半段的逆序对数目  
  55.         inversions+=MergeArray(arry,start,mid,end,temp);//在找完左右半段逆序对以后两段数组有序,然后找两段之间的逆序对。最小的逆序段只有一个元素。  
  56.     }      
  57.     return inversions;  
  58. }  
  59. int InversePairs(int arry[],int len)  
  60. {  
  61.     int *temp=new int[len];  
  62.     int count=InversePairsCore(arry,0,len-1,temp);  
  63.     delete[] temp;  
  64.     return count;  
  65. }  
  66. void main()  
  67. {  
  68.     //int arry[]={7,5,6,4};  
  69.     int arry[]={1,3,7,8,2,4,6,5};  
  70.     int len=sizeof(arry)/sizeof(int);  
  71.     //printArray(arry,len);  
  72.     int count=InversePairs(arry,len);  
  73.     //printArray(arry,len);  
  74.     //cout<<count<<endl;  
  75.     system("pause");  
  76. }            

例题2

有10个文件,每个文件1G,每个文件的每一行存放的都是用户的query,每个文件的query都可能重复。要求你按照query的频度排序。

        1hash映射:顺序读取10个文件,按照hash(query)%10的结果将query写入到另外10个文件(记为)中。这样新生成的文件每个的大小大约也1G(假设hash函数是随机的)。

        2hash统计:找一台内存在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),但是速度上会有一定程度的下降。代码如下:

[cpp] view plain copy
  1. #include<iostream>  
  2. #include<cmath>  
  3. #include<cstdlib>  
  4. #include<Windows.h>  
  5. using namespace std;  
  6. void insert_sort(int arr[],int n)  
  7. {  
  8. for(int i=1;i<n;++i)  
  9. {  
  10. int val=arr[i];  
  11. int j=i-1;  
  12. while(arr[j]>val&&j>=0)  
  13. {  
  14. arr[j+1]=arr[j];  
  15. --j;  
  16. }  
  17. arr[j+1]=val;  
  18. }  
  19. }  
  20. void aux_merge(int arr[],int n,int m,int aux[])  
  21. {  
  22. for(int i=0;i<m;++i)  
  23. swap(aux[i],arr[n+i]);  
  24. int p=n-1,q=m-1;  
  25. int dst=n+m-1;  
  26. for(int i=0;i<n+m;++i)  
  27. {  
  28. if(p>=0)  
  29. {  
  30. if(q>=0)  
  31. {  
  32. if(arr[p]>aux[q])  
  33. {  
  34. swap(arr[p],arr[dst]);  
  35. p--;  
  36. }  
  37. else  
  38. {  
  39. swap(aux[q],arr[dst]);  
  40. q--;  
  41. }  
  42. }  
  43. else  
  44. break;  
  45. }  
  46. else  
  47. {  
  48. swap(aux[q],arr[dst]);  
  49. q--;  
  50. }  
  51. dst--;  
  52. }  
  53. }  
  54. void local_merge(int arr[],int n)  
  55. {  
  56. int m=sqrt((float)n);  
  57. int k=n/m;  
  58. for(int i=0;i<m;++i)  
  59. swap(arr[k*m-m+i],arr[n/2/m*m+i]);  
  60. for(int i=0;i<k-2;++i)  
  61. {  
  62. int index=i;  
  63. for(int j=i+1;j<k-1;++j)  
  64. if(arr[j*m]<arr[index*m])  
  65. index=j;  
  66. if(index!=i)  
  67. for(int j=0;j<m;++j)  
  68. swap(arr[i*m+j],arr[index*m+j]);  
  69. }  
  70. for(int i=0;i<k-2;++i)  
  71. aux_merge(arr+i*m,m,m,arr+(k-1)*m);  
  72. int s=n%m+m;  
  73. insert_sort(arr+(n-2*s),2*s);  
  74. aux_merge(arr,n-2*s,s,arr+(k-1)*m);  
  75. insert_sort(arr+(k-1)*m,s);  
  76. }  
  77. void local_merge_sort(int arr[],int n)  
  78. {  
  79. if(n<=1)  
  80. return;  
  81. if(n<=10)  
  82. {  
  83. insert_sort(arr,n);  
  84. return;  
  85. }  
  86. local_merge_sort(arr,n/2);  
  87. local_merge_sort(arr+n/2,n-n/2);  
  88. local_merge(arr,n);  
  89. }  
  90. void merge_sort(int arr[],int temp[],int n)  
  91. {  
  92. if(n<=1)  
  93. return;  
  94. if(n<=10)  
  95. {  
  96. insert_sort(arr,n);  
  97. return;  
  98. }  
  99. merge_sort(arr,temp,n/2);  
  100. merge_sort(arr+n/2,temp,n-n/2);  
  101. for(int i=0;i<n/2;++i)  
  102. temp[i]=arr[i];  
  103. for(int i=n/2;i<n;++i)  
  104. temp[n+n/2-i-1]=arr[i];  
  105. int left=0,right=n-1;  
  106. for(int i=0;i<n;++i)  
  107. if(temp[left]<temp[right])  
  108. arr[i]=temp[left++];  
  109. else  
  110. arr[i]=temp[right--];  
  111. }  
  112. const int n=2000000;  
  113. int arr1[n],arr2[n];  
  114. int temp[n];  
  115. int main()  
  116. {  
  117. for(int i=0;i<n;++i)  
  118. arr1[i]=arr2[i]=rand();  
  119. int begin=GetTickCount();  
  120. merge_sort(arr1,temp,n);  
  121. cout<<GetTickCount()-begin<<endl;  
  122. begin=GetTickCount();  
  123. local_merge_sort(arr2,n);  
  124. cout<<GetTickCount()-begin<<endl;  
  125. for(int i=0;i<n;++i)  
  126. if(arr1[i]!=arr2[i])  
  127. cout<<"ERROR"<<endl;  
  128. system("pause");  
  129. }  


原创粉丝点击