归并排序

来源:互联网 发布:美容美发软件 编辑:程序博客网 时间:2024/06/05 10:32

CLRS: 将原问题分解为几个规模较小但类似于原问题的子问题,递归地求解这些子问题,然后再合并这些子问题的解来建立原问题的解。

归并排序利用了分治法的思想,对数组元素进行切分,递归地排序左右部分数组,然后再将两部分进行合并,最终达到排序的目的。此算法的时间复杂度是O(nlgn)。

此算法的大体思路为:

  • 将待排序的数组划分为左右两个部分,分别递归地进行归并排序
  • 当划分到只有一个元素时,返回
  • 对划分的两个部分进行合并,即分别从已排序的两个部分中依次取最小值,放入原数组

有可能某个部分的值先被放入原数组,因此需要判断,当某个部分被取完时,另一个部分直接将剩下的元素放入原数组。但是如果每次循环都判断是否有部分被取完,会影响效率。因此可在两个部分的末尾添加哨兵元素,用于标记,阻绝再从此部分中取走元素。
具体做法:
复制左右两部分的元素到L[]和R[]中,L和R的长度需要至少比左右部分的元素长度大1。然后再将有效值后一个元素设置为哨兵值,如10000,保证此数大于待排序的所有数。即假如左部分中有已排序元素1 2 3,则L长度至少为4,元素值分别为1 2 3 10000。当某一个部分元素取完时,即L中有效的原始取完时,此时L中元素再与R中进行比较,则必然有10000>R[j],因此会选取R中有效元素放入原数组。最后当L和R中所有有效元素都取完时,循环停止,L和R中的10000不会进入到原数组,而破坏数据。

实现如下:

#include<iostream>using namespace std;//对由mid切分的左右块(low~mid和mid+1~high)进行合并void merge(int* arr,int low,int mid,int high){  int m=mid-low+1;  //左边块长度  int n=high-mid;  //右边块长度  //左边块的容器。因为C++需要在编译时知道数组长度,因此不能L[m],故设置为100。可根据需要自行设定  int L[100]={0};  int R[100]={0};  //右边块的容器  for(int i=0;i<m;i++)    L[i]=arr[low+i];  //复制左边块  for(int j=0;j<n;j++)    R[j]=arr[mid+j+1];  //复制右边块  L[m]=10000;  //哨兵,避免每次循环都判断是否L或R已空  R[n]=10000;  int i=0;  //左边块的索引  int j=0;  //右边块的索引  //对左右块的“首”元素循环判断,并将小的值添加到目的数组arr中  for(int k=low;k<=high;k++){    if(L[i]<=R[j]){      arr[k]=L[i];      i++;  //添加的是左边块的“首”元素,需要索引i+1,以指向左边块下一个元素    }    else{      arr[k]=R[j];      j++;  //同上    }  }}//归并排序,将数组中下标从low到high(包括)的元素进行排序void merge_sort(int* arr,int low,int high){  if(low<high){  //当元素至少还有两个时,进行递归归并排序    int mid=(low+high)/2;  //获取中间索引    merge_sort(arr,low,mid);  //递归排序左部分    merge_sort(arr,mid+1,high);  //递归排序右部分    merge(arr,low,mid,high);  //合并两个部分  }  //low==high,则只有一个元素,不进行排序,直接返回  //low>high,错误}int main(){  int arr[10]={5,4,3,2,1,8,7,6,5,4};  merge_sort(arr,0,9);  for(int i=0;i<10;i++)    cout<<arr[i]<<" ";  cout<<endl;  return 0;}RESULT:1 2 3 4 4 5 5 6 7 8
0 0