C++ 归并排序算法的实现与改进(含笔试面试题)

来源:互联网 发布:淘宝人生小说下载 编辑:程序博客网 时间:2024/06/03 16:34
       归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。

算法步骤:

1:申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列

2:设定两个指针,最初位置分别为两个已经排序序列的起始位置

3:比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置

4:重复步骤3直到某一指针达到序列尾

将另一序列剩下的所有元素直接复制到合并序列尾

实现代码:两种方法,重载了排序函数,并改进排序算法,使用了STL

/*************************************************************************** *  @file       main.cpp *  @author     MISAYAONE *  @date       26  March 2017 *  @remark     26  March 2017  *  @theme      Merge Sort  ***************************************************************************/#include <iostream>#include <vector>#include <time.h>#include <Windows.h>using namespace std;static int Max = 7;//自定义的使用插入排序的门限int _count = 0;//逆序对的个数/*第一种方法,传入参数为数组指针,左右限*///函数作用:合并[left,mid][mid+1,right]void Merge(int a[],int left,int mid,int right){//两段区间的长度int length1 = mid-left+1;int length2 = right-mid;//分配两段新的内存空间存储原内容int *l1 = new int[length1];int *l2 = new int[length2];for (int i = 0; i < length1; ++i){l1[i] = a[left+i];}for (int j = 0; j < length2; ++j){l2[j] = a[j+mid+1];}//存入原内容之后,比较两个序列int i = 0,j = 0;int k = length1;//比较两个序列的重合部分,进行排序while (i<length1 && j<length2){if (l1[i] < l2[j]){a[left++] = l1[i++];}else{a[left++] = l2[j++];//因为l2[j]大于l1[i],所以l2[j]肯定大于[0,length-1]之中[0,i]之间的所有数,产生逆序对if (l2[j] > l1[i]){_count +=  length1-i+1;}}}//两序列的剩余部分分别放于结尾while (i<length1){a[left++] = l1[i++];}while (j<length2){a[left++] = l2[j++];}//分配的内存需要释放掉delete []l1;delete []l2;}void Merge_sort(int a[],int left,int right){if (left < right){int mid = (left+right)/2;//首先进行分区,然后递归操作Merge_sort(a,left,mid);Merge_sort(a,mid+1,right);//第一次将其分为两个区间,进行合并操作Merge(a,left,mid,right);}}/*第二种方法,传入参数两个迭代器*///Merge函数的重载,接受三个迭代器void Merge(vector<int>::iterator begin,vector<int>::iterator mid,vector<int>::iterator end){int length1 = (mid-begin)+1;int length2 = end-mid;int *p1 = new int[length1];int *p2 = new int[length2];for (int i=0; i < length1; ++i){p1[i] = *(begin+i);}for (int j=0; j < length2; ++j){p2[j] = *(mid+1+j);}int i = 0;int j = 0;int k = length1;while (i<length1 && j<length2){if(p1[i] < p2[j]){*(begin++) = p1[i++];}else{*(begin++) = p2[j++];}}while(i < length1){*begin++ = p1[i++];}while(j < length2){*begin++ = p2[j++];}delete []p1;delete []p2;}//对函数进行重载,传入一对迭代器,并进行第一次改进void Merge_sort(vector<int>::iterator begin, vector<int>::iterator end){//若只传递了一个参数,无需进行排序,直接输出if (begin >= end){return;}//对长度较小的自序列使用插入排序// if ((end-begin) <= Max)// {// Insertion_sort(begin,end);// }if (begin != end){auto mid = begin+(end - begin)/2;Merge_sort(begin,mid);Merge_sort(mid+1,end);//第一次将其分为两个区间,进行合并操作if (*(mid+1) >= *mid) {return;}Merge(begin,mid,end);}}int main(int argc, char **argv){clock_t Start_time = clock();int a[10] = {23,56,78,6,59,15,49,81,15,56};vector<int> vec(a,a+10);Merge_sort(a,0,9);for (size_t i = 0; i != 10; ++i){cout<<a[i]<<" ";}cout<<endl;Merge_sort(vec.begin(),vec.end()-1);//使用迭代器的版本出现的错误很低级,困扰了我一晚上,结果发现是传入的end迭代器是不可访问的for (size_t i = 0; i != 10; ++i){cout<<vec[i]<<" ";}cout<<endl;cout<<"逆序对个数:"<<_count<<endl;clock_t End_time = clock();cout<<"Running time is :"<<static_cast<double>(End_time-Start_time)/CLOCKS_PER_SEC*1000<<" ms"<<endl;cin.get();return 0;}


小规模子数组使用插入排序,用不同的方法处理小规模问题能改进大多数递归算法的性能,因为递归会使小规模问题中方法的调用过于频繁,所以改进对它们的处理方法就能改进整个算法。对排序来说,我们已经知道插入排序(或者选择排序)非常简单,因此很可能在小数组上比归并排序更快。和之前一样,一幅可视轨迹图能够很好地说明归并排序的行为方式。图中的可视轨迹图显示的是改良后的归并排序的所有操作。使用插入排序处理小规模的子数组(比如长度小于15)一般可以将归并排序的运行时间缩短10%~15%。


我们可以添加一个判断条件,如果a[mid]小于等于a[mid+1],我们就认为数组已经是有序的并跳过merge()方法。这个改动不影响排序的递归调用,但是任意有序的子数组算法的运行时间就变为线性的了。


复杂度分析:

思想:运用分治法思想解决排序问题。
最坏情况运行时间:O(nlgn)
最佳运行时间:O(nlgn)

最优时间复杂度 O(n)
最差空间复杂度 O(n)


特点分析:稳定算法stable sort、Out-place sort

例题1:有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个文件进行归并排序(内排序与外排序相结合)。


例题2:微软2010年笔试题,在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序数对。一个排列中逆序的总数就称为这个排列的逆序数。如{2,4,3,1}中,2和1,4和3,4和1,3和1是逆序数对,因此整个数组的逆序数对个数为4,现在给定一数组,要求统计出该数组的逆序数对个数。


见上面程序。加入一个判断即可,且时间复杂度为O(nlogn)





1 0
原创粉丝点击