目录

引言:

了解:

什么是并行计算:

并行计算分为时间上并行和空间上并行:

并行排序算法与双调排序之间的关系:

学习:

双调排序(Bitonic_Sort)的介绍及原理:

双调排序中的双调序列:

贝切尔比较器(Batcher comparator):

Batcher定理:

Batcher归并网络:

双调排序:

网上资料中的双调排序图:

在画板上进行Batcher定理的实际运用:

任意序列构造双调序列(Bitonic_Merge):

网上资料中的构造双调序列图:

分析此过程:

类比归并排序的代码:

实现:

参考资料:


引言:

双调排序是我在网上看到的关于并行排序算法时偶然间了解到的一个算法,这个算法也拥有着优秀的排序效率和时间复杂度以及空间复杂度。个人认为理解和实现这个算法对于我们对排序算法的了解和提升是有一定的帮助的,但是在网上所查找的双调排序的资料和文章所用的图片几乎都是一样的,这篇文章我会独立的将双调排序的原理和过程通过画板画出来,并写下我自己对双调排序的认识,再用C语言进行实现,双调排序看着比较难懂,但是如果仔细拆分和研究,实际上还是比较容易懂的。

在写双调排序的代码之前,我们先对基于并行计算的并行排序算法以及双调排序做一个简单的了解。

了解:

什么是并行计算:

并行计算是一个相对比较庞大的知识体系,他被应用在很多的地方,例如硬件和计算机结构的设计上,这里我只是把并行计算当作对学习双调排序的前提进行了解,不会用太多的笔墨。

并行计算是相对于串行计算来讲的,在知乎上看见了一个特别通俗易懂的例子:

我们上小学每次考完试的时候可能会遇到的两种情况:

情况一:老师说,来,这次考试的所有卷子我批改完了,班长拿下去给每个人一发吧。

情况二:老师说,来,这次考试的所有卷子我批改完了,第一排每个人都过来我给你们一叠卷子你们给咱班娃一发。

其实情况一和二就分别对应着串行计算和并行计算。

并行计算分为时间上并行和空间上并行:

那么何谓时间上并行和空间上并行呢?这里再举两个个我在网上看到的通俗易懂的例子:

一:如果我们此时生产绿箭口香糖,我们就需要机器来进行以下四个步骤:清洗、消毒、切割、包装。在我们有很多包原料的前提下,我们通常会采用的方法就是等待一包原料将上述的四个步骤全部执行完之后再进行下一包原料的加工,有了现代机械化加工之后,我们就可以将上述的四个步骤进行机械化的流水线操作,用四台机器同时负责这四个过程。

上述就是并行计算中的时间上并行,也就是在同一时间同时启动多个操作来提升算法的效率。

二:比方说小明要在植树节进行植树活动,全程操作都由小明一人完成的话需要3小时,但是如果小明叫上他的两个朋友小红和小黑,它们三个一块儿进行植树操作的话每个人只会有1小时的工作量。

上述就是并行计算中的空间上并行,也就是将一个庞大的任务分成多个子任务分别来进行进而提升算法的效率。

并行排序算法与双调排序之间的关系:

并行排序算法是基于计算机并行计算的,并行计算或称平行计算是相对于串行计算来说的。它是一种一次可执行多个指令的算法,目的是提高计算速度,及通过扩大问题求解规模,解决大型而复杂的计算问题。并行排序算法就是在计算机并行计算大力发展的今天为了提高效率而被提出的排序算法,常见的并行排序算法有冒泡排序、归并排序、双调排序,而我们今天着重要了解和实现的算法就是双调排序。

学习:

双调排序(Bitonic_Sort)的介绍及原理:

双调排序是排序网络方法中的一种排序方法,同时也是排序网络中最快的方法之一,排序网络的意思就是网络比较顺序与数据无关的排序方法。

双调排序中的双调序列:

由非严格增序列X和非严格将序列Y组成的序列称之为双调序列。

一个序列:a1,a2,a3,...,an是双调序列,必须满足以下条件:

(1)存在一个ak(1 <= k <= n),使得a1 >= ... >= ak <= ... <= an;

(2)序列能够满足循环移位条件

例如:1,2,3,4,9,8,7,6,5

贝切尔比较器(Batcher comparator):

Batcher比较器是实现“比较和条件交换”操作的一种逻辑单元。往比较器里面输入两个值,比较器就会输出两个值对应的最大值和最小值。

Batcher定理:

将任意一个长度为n的双调序列A分为等长的两半X、Y,将X中的元素与Y中的元素一一按照原序进行比较,也就是a [i] 与a [i + n/2] (i < n)进行比较,将较大者放入MAX序列中,较小者放入MIN序列,得到的MAX和MIN序列仍然是双调序列,并且MAX序列中任意一个元素是不小于MIN序列中任意一个元素的。我们可以把这个过程看作是一个逐步拆分序列的过程。

Batcher归并网络:

这个归并网络是Batcher这个人提出来的,就是由一系列的Batcher比较器组成的网络,在比较器的两个输入端输入两个值X和Y,然后比较器就会在两个输出端输出最大值和最小值,这是不是和双调序列有些相似?其实双调排序网络就是基于Batcher比较器实现的,Batcher归并网络采用的就是分治法的策略,就是把序列不断的对半分,把前半段进行分类也就是我们双调序列前半段的升序序列,后半段也进行分类也就是后半段的将序序列,然后再利用归并网络进行归并得到新序列。

注意:Batcher归并网络不仅有双调归并,还有奇偶归并。如果排序网络采用双调归并构造的话我们就称它为双调排序网络,如果采用奇偶排序网络构造我们就称它为奇偶排序网络。

双调排序:

现在假设我们有一个双调序列,我们根据上面的Batcher定理对序列进行双调划分,也就是将这个双调序列分成两个双调序列,递归此过程,也就是一个双调序列划分为两个双调序列,两个双调序列划分为四个双调序列,直到得到的子序列长度为1。

这个过程类似于希尔排序过程中的缩小增量步骤,在之前我曾经在这篇文章:C语言实现希尔排序中使用画板分析过希尔排序的过程,我们定义了一个名为gap的变量作为希尔排序中的增量,gap的初始值就为序列的总长度len,随后每次进行排序的时候gap的值都会缩小一半,直到增量缩小为1,此时就是希尔排序的倒数第二趟(因为最后一趟是对序列的检查),这一趟也就相当于是插入排序,在这里我们直接给出我之前写过的希尔排序代码用于我们对双调排序的类比和理解:

//希尔排序
void Shell_sort(int *ar, int len) {assert(ar != nullptr && len >= 0);int i = 0,j = 0;int temp = 0;//定义中间值用于存储数据int gap = 0;//定义排序增量gap = len;//将排序增量的初始值直接设置为数组的长度while(gap > 1){gap /= 2;for(i = gap;i < len;i += gap){if(ar[i] < ar[i - gap]){temp = ar[i];for(j = i - gap;j >= 0 && ar[j] > temp;j -= gap){ar[j + gap] = ar[j];}ar[j + gap] = temp;}}}
}

好了,对比希尔排序,我们在这里直接给出双调排序的排序图,在这里我给出两张图片,一张是网上都能找到的双调排序图,一张是我自己画出来的以便于大家进行理解:

网上资料中的双调排序图:

在画板上进行Batcher定理的实际运用:

这张图看着很抽象,实际上理解起来还行,接下来我在画板上进行讲述:

我们从上图得知,初始的双调序列为:3,5,8,9,10,12,14,20,95,90,60,40,35,23,18,0,一共是16个元素,也就是n的值此时为16,我们第一次进行双调划分的长度就是16 / 2 = 8,如图:

这时你应该能清楚的感觉到Batcher定理与希尔排序的相似之处,我画的这张图也就对应着Batcher定理中的将任意一个长度为n的双调序列A分为等长的两半X、Y,将X中的元素与Y中的元素一一按照原序进行比较,也就是a [i] 与a [i + n/2] (i < n)进行比较,序列总长度为n,左边长度为8的序列为X序列,右边为Y序列,用a[0](序列中的3)和a[0 + 8](序列中的95)进行比较,再用a[1]和a[1 + 8]进行比较,然后重复此过程。

接下来我们再将两个元素比较之后的较大者放入MAX序列,较小者放入MIN序列:

我交换了元素20和0 的位置,形成了两个序列注意此时MAX序列中任意一个值都是大于MIN序列中的数的,此时n的值更新为8,此时递归双调划分的过程,我们再次得到新的序列,并且再次将双调序列对半分,进行比较:

如图,在前面一整个双调序列的基础上又形成了两个双调序列,此时n的值更新为4,我们继续进行双调划分的过程,将序列对半分进行比较:

此时,序列已经被划分称为了4个双调序列,分别是3,0,8,5 | 10,9,14,12 | 18,20,35,23 | 60,40,95,90, 我们现在将n的值更新为2,n \ 2 = 1,现在子序列的长度为1,所以我们对相邻元素之间进行比较,并把相对较小的值放在前面,如图为比较后的序列:

此时我们经过四次依据Batcher定理的划分,成功将一个双调序列拆分成现在的升序序列,这也是整个双调排序中至关重要的一步。

现在我们可以将一个双调序列转化为升序序列,但是问题是在实际使用算法解决问题的过程中,开始不可能直接给我们提供双调序列而需要我们自己来构建双调序列,所以此时就有一个新的问题:如果把一个任意序列变成双调序列进而完成升序排序?

任意序列构造双调序列(Bitonic_Merge):

假设我们现在随意输入n个整形值,我们要对这n个元素进行双调排序形式的升序排序,我们必须要做的就是先将这n个元素构造成一个双调序列,然后再递归运用Batcher定理完成最终的升序排序。

那么如何将任意序列构造成双调序列呢?

构造双调序列的过程称为Bitonic_Merge,也许你也注意到了后面有一个Merge的单词,Merge在中文的意思就是合并、归并的意思,这时自然而然就想到了归并排序,其实这个过程是和归并排序(Merge_Sort)相类似的。

网上资料中的构造双调序列图:

这张图还是能轻易地读懂的,和上面的过程恰恰相反,我们首先设定子序列长度为1,将序列中的元素两两进行比较,第一趟构造一个长度为2的单调序列,序列的单调性依次为单调递减、单调递增这样往复下去,然后对长度为2的子序列进行两两归并,并再次对长度为4的序列中的元素进行排序,如此递归下去直到生成一个完整的双调序列。

注:相邻之间的线条我们可以理解为计算机中的比较器。

分析此过程:

我们从上图得知初始序列为:10,20,5,9,3,8,12,14,90,0,60,40,23,35,95,18。

Step 1 : 将子序列的长度设置为2,并按照单调递增、单调递减、单调递增、单调递减的顺序将子序列排序好,排序后的序列为:10,20,9,5,3,8,14,12,0,90,60,40,23,35,95,18

Step 2:将长度为2的子序列进行两两合并,此时子序列的长度为原子序列长度的2倍,重新进行排序,子序列的单调性依次为递增递减,排序后的序列为:5,9,10,20,14,12,8,3,0,40,60,90,95,35,23,18

Step 3 : 将长度为4的子序列继续进行归并,此时子序列的长度为8,且此时序列中只会出现一个单调递增序列和一个单调递减序列,排序后的序列为:3,5,8,9,10,12,14,20,95,90,60,40,35,23,18,0

Step 4:双调序列构造完成。

注意:小的双调子序列在归并的过程中,还是需要继承归并前的比较方式的,我们可以发现,长度为2的子序列在归并成长度为4的子序列时,拿递增序列举例子,不仅要进行原来的两两比较,还要进行Batcher定理中a [i] 与a [i + n/2]这种比较方式,只不过此时n的值从原来的2更新到了现在的4,同样在子序列长度从4变为8的过程中,还要继承原来子序列长度为2和长度为4的比较方式,并在此基础上添加a [i] 与a [i + n/2]的比较方式,此时n的值更新为归并后的8,也就是a[1]与a[1 + 8 / 2]进行比较,如此往复下去。

类比归并排序的代码:

在我之前写过的文章:C语言实现归并排序(Merge_sort)中提到过,归并排序就是将子序列一层一层的进行合并,并且按照升序的顺序将元素依次放置最终的到我们想要的序列,在这里我直接给出归并排序的核心代码我们进行类比分析,以便于我们更好地理解构造双调序列的过程:

归并排序的核心代码:

void merge(int *ar,int low,int middle,int high,int *temp)//归并排序
{int i = low;//通过I遍历左半边数组int j = middle + 1;//通过j遍历右半边数组int k = low;//通过k遍历整个数组while(i <= middle && j <= high){//循环通过I和j分别遍历从中间划分开来的两个数组,并分开比较大小temp[k++] = ar[i] < ar[j] ? ar[i++] : ar[j++];//如果前者小于后者就将前者直接添加至新数组的后面,依此类推}while(i <= middle)//如果比较完了,但左半边数组还有剩余的元素,直接将这些元素添加至新数组的后方temp[k++] = ar[i++];while(j <= high)//同理,比较完了右半边还有剩余的,也直接添加至新数组的后方temp[k++] = ar[j++];for(i = low;i <= high;i++){//最后将新数组的所有元素赋值给ar数组ar[i] = temp[i];}
}
void merge_sort(int *ar,int low,int high,int *temp)
{if(low >= high){//如果左边界大于右边界,直接返回return;}int middle = low + (high - low) / 2;//相比较(low + high)/ 2;的优化写法,可以防止越界merge_sort(ar,low,middle,temp);递归调用,左半部分递归归并merge_sort(ar,middle + 1,high,temp);//右半部分递归归并merge(ar,low,middle,high,temp);//递归操作
}
void mergesort(int *ar,int len)//最终归并函数
{int *temp = (int *)malloc(sizeof(int)*len);//向堆区申请len个int类型大小的空间赋给temp新数组assert(temp != nullptr);//断言新数组不为空merge_sort(ar,0,len - 1,temp);//递归归并操作free(temp);//释放temp的内存空间temp = NULL;//将temp置空
}

在归并排序中,一共有三个函数,分别是排序函数,递归归并边界函数、最终归并函数,也就是说我们要归并子序列并且排序它,需要有三个功能,分别是按照排序规则将子序列中的元素进行排序 、递归将边界进行合并并再次进行排序、开辟空间并合并为最终序列。此时我们即将要进行的构造双调序列与归并排序类似,只不过我们需要将排序的规则替换为双调序列的规则也就是从小到大再从大到小,并继承原先子序列中的比较方式。

实现:

刚才在分析双调排序的过程中我们不难发现,实现这个排序首先要做的就是交换函数,其次为序列等分和升序降序的排序和归并,所以我们对这几个功能来分别实现。

#include <iostream>
#include <iomanip>using namespace std;void printArr(int *arr, int len) {for (int i = 0; i < len; ++i) {cout << setw(3) << arr[i];}cout << endl << endl;
}// 由bitonicSort中的顺序可知,这里传入的arr已是双调序列
void bitonicMerge(int *arr, int len, bool asd) {if (len > 1) {int m = len / 2;for (int i = 0; i < m; ++i) {if (arr[i] > arr[i + m] == asd)swap(arr[i], arr[i + m]); // 根据asd判断是否交换}// for循环结束后又生成了2个双调序列,分别merge直到序列长度为1bitonicMerge(arr, m, asd); // 都是按照asd进行mergebitonicMerge(arr + m, m, asd);}
}void bitonicSort(int *arr, int len, bool asd) { // asd 升序if (len > 1) {int m = len / 2;bitonicSort(arr, m, !asd); // 前半降序bitonicSort(arr + m, len - m, asd); // 后半升序// 前2个sort之后形成了1个双调序列,然后传入merge合并成asd规定的序列bitonicMerge(arr, len, asd); // 合并}
}int main() {int len = 16;auto *arr = new int[len]; // 自动识别类型bool asd = true;// 1.随机生成无序序列srand((unsigned int) time(nullptr));for (int i = 0; i < len; ++i) {arr[i] = (int) random() % 100;}cout << "无序序列: ";printArr(arr, len);// 2.双调排序bitonicSort(arr, len, asd);cout << "双调排序: ";printArr(arr, len);
}

文章未完成,待补充。

参考资料:

CUDA(六). 从并行排序方法理解并行化思维——冒泡、归并、双调排序的GPU实现_Rachel-Zhang的博客-CSDN博客

双调排序Bitonic Sort,适合并行计算的排序算法_不动明王1984的博客-CSDN博客

谢小帅的博客-双调排序

双调排序_Icoding_F2014的博客-CSDN博客_双调排序时间复杂度

什么是双调排序?(双调归并/双调排序整理)_QuteMelon的博客-CSDN博客_双调排序

算法 - 并行排序算法之双调排序(Bitonic_Sort)相关推荐

  1. 蛮力法查找有序数列c语言,算法——蛮力法之选择排序和冒泡排序c++实现

    这次实现的是蛮力法中的两个例子,选择排序法和冒泡排序法,使用的编译环境是vs2013,下面对这两个算法做一个简单介绍,然后是两个算法的c++实现代码. 选择排序法比较的范围是整个列表,每次扫描结束找出 ...

  2. 关于算法--蛮力法篇--选择排序

    近日开始学习算法,所看课本为清华大学出版社的<算法设计与分析基础>,对简单的数据结构进行了复习,已经学习了算法效率分析基础. 本篇开始对所学算法的思想进行实际JS编码,看学习的进度,每日写 ...

  3. C++Bitonic Sort双调排序/比并排序的实现算法(附完整源码)

    C++Bitonic Sort双调排序/比并排序的实现算法 C++Bitonic Sort双调排序/比并排序的实现算法完整源码(定义,实现,main函数测试) C++Bitonic Sort双调排序/ ...

  4. 输入法按照选字频率排序的C语言程序算法,算法与数据结构之选择排序(C语言)...

    #include #include void SelectSort(int *a,int n);//预声明要调用的函数 int main(void) { int k; int x[]={,,,,,,, ...

  5. Bitonic sort(双调排序)

    最近在上Udacity的<Introduction to parallel programming>课程,里面介绍了一种很有趣的平行算法--Bitonic sort(双调排序)算法.在课程 ...

  6. 三十分钟理解:双调排序Bitonic Sort,适合并行计算的排序算法

    双调排序是data-independent的排序, 即比较顺序与数据无关的排序方法, 特别适合做并行计算,例如用GPU.fpga来计算. 1.双调序列 在了解双调排序算法之前,我们先来看看什么是双调序 ...

  7. 抢红包算法 c++_十大排序算法(一):冒泡排序法

    作者:绿皮长条瓜 目录 一.算法介绍 二.MATLAB实现 三.两个例子 一.算法介绍 冒泡排序(Bubble Sort)算法是一种计算科学领域的较简单的排序算法.它重复地走访过要排序的元素列,如果顺 ...

  8. bucket sort sample sort 并行_双调排序Bitonic Sort,适合并行计算的排序算法

    双调排序是data-independent的排序, 即比较顺序与数据无关的排序方法, 特别适合做并行计算,例如用GPU.fpga来计算. 1.双调序列 在了解双调排序算法之前,我们先来看看什么是双调序 ...

  9. python中怎么比较两个列表的大小_python中对列表元素大小排序(冒泡排序法,选择排序法和插入排序法)—排序算法...

    前言 排序(Sorting) 是计算机程序设计中的一种重要操作,它的功能是将一个数据元素(或记录)的任意序列,重新排列成一个关键字有序的序列.本文主要讲述python中经常用的三种排序算法,选择排序法 ...

最新文章

  1. 在 Linux 中查看时区
  2. Squid处理web数据请求
  3. 15 个 JavaScript Web UI 库 (转)
  4. bootsrtap h5 移动版页面 在苹果手机ios滑动上下拉动滚动卡顿问题解决方法
  5. www.javaei.com网站建设手记——(15)h2p被开源中国收录为开源项目
  6. mysql-增删改(DML)
  7. 关于MyAccount无法在standalone环境下运行的问题
  8. [leetcode-108,109] 将有序数组转换为二叉搜索树
  9. 《C专家编程》第二章——这不是Bug,而是语言特性
  10. 【报告分享】2021上半年短视频及电商生态研究报告.pdf(附下载链接)
  11. 【27】Python100例基础练习题6
  12. 阿里4万亿市值是怎样炼成的?(深度)
  13. Spring源码分析
  14. 十大验证码解决方案服务比较
  15. 计算机中心pdca,信息中心日常运维工作PDCA持续改进.docx
  16. oracle 经纬度格式转换,GOOGLE与百度经纬度互转(plsql版)
  17. 图扑数字孪生智慧隧道,新基建带来新生活
  18. 从 KPI 到 OKR,高阶产品人如何推动业务高速增长
  19. c语言中eof的作用,C语言中,怎么用EOF结束输入?
  20. 机器学习-波澜壮阔40年

热门文章

  1. Matlab不规则区域渐变色填充及注意事项
  2. MetaMask 添加BSC、Polygon网络配置
  3. 关闭浏览器窗口的时候,如何清空localStorage的数据
  4. 300年保险即将被颠覆!MediShares ICO将启动全球首个智能合约市场
  5. 关于12036的系统优化那点想法
  6. linux服务器安装mysql8、开启mysql外部远程访问权限、卸载mysql
  7. 关于人工智能造假的一点思考
  8. 在计算机系统中 一个以科学的方法组织,东北大学16春学期《管理学原理》在线作业1...
  9. vcscoverage
  10. Java之jmap命令内存详情