实现归并排序的openmp程序_归并排序C++实现

归并排序是一种效率较高的排序算法,其平均时间复杂度为O(nlogn)。算法采用分治法,通过递归或循环实现。本文详细介绍了归并排序的原理、递归与循环两种实现方式,并探讨了针对小规模子数组的插入排序优化、检查序列是否已有序以及减少数组拷贝的优化方法。此外,还对比了递归版和循环版归并排序的流程和特点。
摘要由CSDN通过智能技术生成

在比较类排序中,归并排序号称最快,其次是快速排序和堆排序,两者不相伯仲,但是有一点需要注意,数据初始排序状态对堆排序不会产生太大的影响,而快速排序却恰恰相反。

归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。把长度为n的输入序列分成两个长度为n/2的子序列;对这两个子序列分别采用归并排序;将两个排序好的子序列合并成一个最终的排序序列。

从下图可以看出,每次合并操作的平均时间复杂度为O(n),而完全二叉树的深度为|log2n|。总的平均时间复杂度为O(nlogn)。而且,归并排序的最好,最坏,平均时间复杂度均为O(nlogn)。

455e94daa5f3105445a7135b93d157fb.png

图片来自:https://www.cnblogs.com/chengxiao/p/6194356.html

下面这个(递归版)动态图有助于理解。图片来自:https://www.jianshu.com/p/33cffa1ce613

0ddb2b26d319b1c085e7c16a0fc5f23d.gif

归并排序有两种实现方式: 基于递归的归并排序和基于循环的归并排序。(也叫自顶向下的归并排序和自底向上的归并排序)

这两种归并算法虽然实现方式不同,但还是有共同之处的:

1. 无论是基于递归还是循环的归并排序, 它们调用的核心方法都是相同的:完成一趟合并的算法,即两个已经有序的数组序列合并成一个更大的有序数组序列 (前提是两个原序列都是有序的!)

2. 从排序轨迹上看,合并序列的长度都是从小(一个元素)到大(整个数组)增长的

一、基于递归的归并排序(自顶向下的归并排序)

e8e4ffef27478f0874623211f731ff79.png

代码:

void MergeSort (int arr [], int low,int high) {
    if(low>=high) { return; } // 终止递归的条件,子序列长度为1
    int mid =  low + (high - low)/2;  // 取得序列中间的元素
    MergeSort(arr,low,mid);  // 对左半边递归
    MergeSort(arr,mid+1,high);  // 对右半边递归
    merge(arr,low,mid,high);  // 合并
  }

合并函数代码:

void Merge(int arr[],int low,int mid,int high){
    //low为第1有序区的第1个元素,i指向第1个元素, mid为第1有序区的最后1个元素
    int i=low,j=mid+1,k=0;  //mid+1为第2有序区第1个元素,j指向第1个元素
    int *temp=new int[high-low+1]; //temp数组暂存合并的有序序列
    while(i<=mid&&j<=high){
        if(arr[i]<=arr[j]) //较小的先存入temp中
            temp[k++]=arr[i++];
        else
            temp[k++]=arr[j++];
    }
    while(i<=mid)//若比较完之后,第一个有序区仍有剩余,则直接复制到t数组中
        temp[k++]=arr[i++];
    while(j<=high)//同上
        temp[k++]=arr[j++];
    for(i=low,k=0;i<=high;i++,k++)//将排好序的存回arr中low到high这区间
	arr[i]=temp[k];
    delete []temp;//释放内存,由于指向的是数组,必须用delete []
}

基于递归归并排序的优化方法

优化一:对小规模子数组使用插入排序

用不同的方法处理小规模问题能改进大多数递归算法的性能,因为递归会使小规模问题中方法调用太过频繁,所以改进对它们的处理方法就能改进整个算法。因为插入排序非常简单, 因此一般来说在小数组上比归并排序更快。 这种优化能使归并排序的运行时间缩短10%到15%。

怎么切换呢?只要把作为停止递归条件的

  if(low>=high) { return; }

改成

 if(high - low <= 10) { // 数组长度小于10的时候
      InsertSort(int arr[], int low,int high) // 切换到插入排序
      return;
 }

就可以了,这样的话,这条语句就具有了两个功能:

1. 在适当时候终止递归

2. 当数组长度小于M的时候(high-low <= M), 不进行归并排序,而进行插排

优化二: 测试待排序序列中左右半边是否已有序

通过测试待排序序列中左右半边是否已经有序, 在有序的情况下避免合并方法的调用。

因为a[low...mid]和a[mid...high]本来就是有序的,存在a[low]<a[low+1]...<a[mid]和a[mid+1]<a[mid+2]...< a[high]这两种关系, 如果判断出a[mid]<=a[mid+1]的话,我们就认为数组已经是有序的并跳过merge() 方法。

void sort (int a[], int low,int high) {
    if(low>=high) {
      return;
    } // 终止递归的条件
    int mid =  low + (high - low)/2;  // 取得序列中间的元素
    sort(a,low,mid);  // 对左半边递归
    sort(a,mid+1,high);  // 对右半边递归
    if(a[mid]<=a[mid+1]) return; // 避免不必要的归并
    merge(a,low,mid,high);  // 单趟合并
  }

优化三:去除原数组序列到辅助数组的拷贝

在上面介绍的基于递归的归并排序的代码中, 我们在每次调用merge方法时候,我们都把arr对应的序列拷贝到辅助数组temp中去。

在递归调用的每个层次交换输入数组和输出数组的角色,从而不断地把输入数组排序到辅助数组,再将数据从辅助数组排序到输入数组,节省数组复制的时间。

注意, 外部的sort方法和内部sort方法接收的a和aux参数刚好是相反的

221afe731d43336427ac67f86a15bd60.png

在这里我们做了两个操作:

  • 在排序前拷贝一个和原数组元素完全一样的辅助数组(不再是创建一个空数组了!)
  • 在递归调用的每个层次交换输入数组和输出数组的角色

因为外部sort和merge的参数顺序是相同的,所以,无论递归过程中辅助数组和原数组的角色如何替换,对最后一次调用的merge而言(将整个数组左右半边合为有序的操作), 最终被排为有序的都是原数组,而不是辅助数组!

01100330daf334507185b8c1655e75fe.png

注意:优化结果虽然差不多,但是当其数组接近有序的时候,速度有了可观的提升。

递归归并过程示意图:

65f089245408927cb79d6eb2c379d33f.png

ff2cc1823f52816375e40d3536d0dc1c.png

二、基于循环的归并排序(自底向上)

72ff652867ea5f109808301e4a8e4527.png

代码:

void sort(int a []){
    int N = a.size();
    for (int size = 1; size < N; size *= 2){
      //  low+size=mid+1,为第二个分区第一个元素,它 < N,确保最后一次合并有2个区间
      for(int low = 0; low + size < N;low += 2 * size) {
        mid = low + size - 1;
        high = low + 2 * size - 1;
        if(high > N-1) high = N - 1;
        merge(a,low, mid, high);
      }
    }
  }

循环归并过程示意图、:

ffc5939db6bff6bde26fb00cff4f45e7.png

参考:https://www.cnblogs.com/penghuwan/p/7940440.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是使用OpenMP实现归并排序的示例程序: ```c++ #include <iostream> #include <omp.h> void merge(double* a, double* b, int start, int mid, int end) { int i = start, j = mid + 1, k = start; while (i <= mid && j <= end) { if (a[i] <= a[j]) { b[k++] = a[i++]; } else { b[k++] = a[j++]; } } while (i <= mid) { b[k++] = a[i++]; } while (j <= end) { b[k++] = a[j++]; } for (int i = start; i <= end; i++) { a[i] = b[i]; } } void mergeSort(double* a, double* b, int start, int end) { if (start < end) { int mid = (start + end) / 2; #pragma omp parallel sections { #pragma omp section { mergeSort(a, b, start, mid); } #pragma omp section { mergeSort(a, b, mid + 1, end); } } merge(a, b, start, mid, end); } } int main() { int n = 10; // 数组大小 int p = 4; // 线程数 double* a = new double[n]; double* b = new double[n]; // 初始化数组a和b #pragma omp parallel for for (int i = 0; i < n; i++) { a[i] = rand() / double(RAND_MAX); b[i] = 0; } // 排序数组a和b double start_time = omp_get_wtime(); mergeSort(a, b, 0, n - 1); double end_time = omp_get_wtime(); // 检测归并结果是否正确 bool isSorted = true; for (int i = 1; i < n; i++) { if (a[i] < a[i - 1]) { isSorted = false; break; } } // 输出排序结果和归并时间 if (isSorted) { std::cout << "归并排序结果:"; for (int i = 0; i < n; i++) { std::cout << a[i] << " "; } std::cout << std::endl; } else { std::cout << "归并排序结果不正确!" << std::endl; } std::cout << "归并时间:" << end_time - start_time << "秒" << std::endl; delete[] a; delete[] b; return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值