归并排序
归并排序 Merge sort ,是创建在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用,且各层分治递归可以同时进行。归并排序的核心思想是将两个有序的数列合并成一个大的有序的序列。通过递归,层层合并,即为归并。
分治模式,在每层递归时都有三个步骤:
- 分解原问题为若干子问题,这些子问题是原问题的规模较小的实例。
- 解决这些子问题,递归的求解各子问题,然而,若子问题的规模足够小,则直接求解
- 合并这些子问题的解成原问题的解
归并排序完全遵循分治模式。直观上其操作如下:
- 分解:分解待排序的n个元素的序列成各具n/2个元素的两个子序列
- 解决:使用归并排序递归地排序两个子序列
- 合并:合并两个已排序好的子序列以产生已排序的答案
当待排序的序列长度为1时,递归开始回升。
归并排序C语言实例
#include <stdlib.h>
#include <stdio.h>
void Merge(int sourceArr[],int tempArr[], int startIndex, int midIndex, int endIndex)//负责整合
{
int i = startIndex, j=midIndex+1, k=startIndex;
while(i!=midIndex+1 && j!=endIndex+1)//两边都没到底时
{
if(sourceArr[i] > sourceArr[j])
tempArr[k++] = sourceArr[j++];
else //整体来说,这几步相当于一个在堆底设置的无穷大操作
tempArr[k++] = sourceArr[i++];
}
while(i!=midIndex+1) tempArr[k++]=sourceArr[i++];//说明右边那堆已经到底了,这时候应全填成左边的
while(j!=endIndex+1) tempArr[k++]=sourceArr[j++];//说明左边的到底了,才退出了while 循环,这时候应该全填成右边的
for(i=startIndex; i<=endIndex; i++)
sourceArr[i] = tempArr[i];//将排序好的temp再返回到原数组
}
//内部使用递归
void MergeSort(int sourceArr[], int tempArr[], int startIndex, int endIndex)//负责分割
{
int midIndex;
if(startIndex < endIndex)//直到一个元素时递归回升
{
midIndex = startIndex + (endIndex-startIndex) / 2;
MergeSort(sourceArr, tempArr, startIndex, midIndex);//引用自身分割左
MergeSort(sourceArr, tempArr, midIndex+1, endIndex);//引用自身分割右
Merge(sourceArr, tempArr, startIndex, midIndex, endIndex);//合并,这步操作完成后,开始上一层的递归分割
}
}
int main(int argc, char * argv[])
{
int a[8] = {50, 10, 20, 30, 70, 40, 80, 60};
int i, b[8];
MergeSort(a, b, 0, 7);
for(i=0; i<8; i++)
printf("%d ", a[i]);
printf("\n");
return 0;
}
通过画递归树,发现,n个数据大致会分为1og~2~n次年分割。分为lgn+1层。由于每层执行merge的总复杂度为O(n),因此归并排序的整体复杂度为O(nlogn)。
归并排序包含不相邻元素的比较,但并不会直接交换。在合并两个已排序数组时,如果遇到了相同元素,只要保证前半部分数组优先于后半部分数组,相同元素的顺序就不会颠倒。因此归并排序属于稳定的排序算法。
归并排序算法虽然高效且稳定,但它在处理过程中,除了用来保存输入的数据之外,还需要临时占用一部分内存空间。
为简化Merge的实现,另一段代码实现方式:
#include<iostream>
using namespace std;
#define MAX 500
#define SENRINEL 2000000000
int L[MAX/2+2],R[MAX/2+2];
void merge(int A[],int n,int left,int mid,int right)//合并
{
int n1=mid-left;
int n2=right-mid;
for(int i=0;i<n1;i++) L[i]=A[left+i];//分别拆到两个数组中
for(int i=0;i<n2;i++) R[i]=A[mid+i];
L[n1]=R[n2]=SENRINEL;//设置堆底为足够大
int i=0,j=0;
for(int k=left;k<right;k++)
{
if(L[i]<=R[j])
{
A[k]=L[i++];
//进行判断性填入操作,此时无需考虑是否有堆到底的情况,因为底部是无穷大,不会选取
}else
{
A[k]=R[j++];
}
}
}
void mergeSort(int A[],int n,int left,int right)//进行递归分割
{
if(left+1<right)
{
int mid =(left+right)/2;
mergeSort(A,n,left,mid);//递归分割左边
mergeSort(A,n,mid,right);//递归分割右边
merge(A,n,left,mid,right);//完成这步合并后,将会回到上一级的向下向右拆分
}
}
int main()
{
int A[MAX],n,i;
cin>>n;
for(i=0;i<n;i++) cin>>A[i];
mergeSort(A,n,0,n);
for(i=0;i<n;i++)
{
if(i)cout<<" ";
cout<<A[i];
}
return 0;
}
注意递归的思想,可根据过程顺序图来理解。