归并排序(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)