双调排序

有一定了解的可以直接看最后一部分的代码。

双调排序是一种并行排序算法。

  • 如果以串行的方式运行,其复杂度:O(nlog(n))
  • 如果有 n(其实是 n/2) 个可同时运行的线程,则复杂度:O(log(n))

上面的复杂度是基于输入序列为双调序列。

双调序列

双调序列(Bitonic Sequence) 是指由一个 非严格增序列X 和 非严格减序列Y 构成的序列,任意两个数,都是双调序列。
(非严格指的是可以出现重复元素,或者NaN不参与排序)

定义:一个序列 a1,a2, …,an 是双调序列(Bitonic Sequence),如果:
(1)存在一个 ak(1 ≤ k ≤ n),使得 a1 ≥ … ≥ ak ≤ … ≤ an 成立;或者
(2)序列能够循环移位满足条件(1)
条件(2)非常重要,因为好多序列表面不是,其实是双调的。

双调排序

基本的双调排序只能处理 2^n 个数的序列。

首先定义操作 sortSeq,输出是一个双调序列。
下图中 s 为双调序列,s1 为升序(红),s2 为降序(蓝)。

交换之后,s1 中的序列就是两两比较的较小值,s2 的序列就是两两比较的较大值。本来 s1是升序,s2 是降序的,经过如此操作之后,s1,s2 都是双调的,参考红蓝多边形。在构建 s1 和 s2 时,可以用 n/2 个线程对两两元素进行比较交换,从而实现并行运算。

再对 s1,s2 分别进行 sortSeq 操作,就这样递归操作,直到传入 sortSeq 的序列长度为2,最终 s 就是升序排列的。

下面举个例子说明双调排序过程:

初始值:(10 11 13 14 15 16 17 18, 19 15 13 12 12 11 9 8)

(10 11 13 14 15 16 17 18) (19 15 13 12 12 11 9 8) 
网络1排序: (10 11 13 12 12 11 9 8) (19 15 13 14 15 16 17 18)

(10 11 13 12) (12 11 9 8) (19 15 13 14) (15 16 17 18) 
网络2排序→ (10 11 9 8) (12 11 13 12) (15 15 13 14) (19 16 17 18)

(10 11) (9 8) (12 11) (13 12) (15 15) (13 14) (19 16) (17 18) 
网络3排序→ (9 8) (10 11) (12 11) (13 12) (13 14) (15 15) (17 16) (19 18)

最终结果:(8 9 10 11 11 12 12 13 13 14 15 15 16 17 18 19)

网络2排序的结果中 (12 11 13 12) 经过循环位移之后也是双调序列。

那么如何构建最初的双调序列?

双调排序的初始序列是双调序列,所以对于普通的无序数列要先转换。

算法采用 自底向上 的方法逐步构建双调序列,因为任意两个数都可以视为双调序列。

(1)将无序的输入序列转换成双调序列

对于无序数组 A,相邻的两个数肯定是双调序列,比如 (a0,a1), (a2,a3) 等等
首先步长为 2:(a0,a1) 传入 sortSeq,变成升序序列,
            (a2,a3) 传入 sortSeq,变成降序序列,……
然后步长为 4:(a0,a1,a2,a3) 是双调序列,传入 sortSeq 变成升序序列,
            (a4,a5,a6,a7) 也是双调的,传入 sortSeq 变成降序序列,……
步长依次为 2^n:
……
最后变为前 n/2 元素是升序,后 n/2 是降序的完整双调序列。
无序变双调
将无序的输入序列转换成双调序列

上图中,16个数据,8个比较器,无序数列需要 log(8) = 3 部分完成全局双调序列,步长分别为:20,21,22理解为 1 增序、2 增序、4 增序。

  • 设比较的步长为 s,则比较的层数为 log(s)+1,理解为自顶向下比较的层数
  • 每一层都比较 8 次。(8个比较器)

所以比较的总次数为:(1+2+3) * 8 = 48

(2)双调序列合并成为有序序列

完整的双调序列传入 sortSeq 变成完整的升序序列。
这一部分,步长为 8,比较层数为 4。

构建双调序列:自底向上
排序双调序列:自顶向下
双调合并网络

综上,双调排序比较的总次数为:(1+2+3+4) * 8 = 80


下面的代码分为:

  1. 基本双调排序(非递归)(2^n)
  2. 显示排序过程的双调排序(非递归)(2^n)
  3. 基本双调排序(递归)(2^n)
  4. 任意双调排序(递归)(任意正数)(真正普适的双调排序

1. 基本双调排序(非递归)(2^n)

#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;
}

// 双调排序
void bitonicSort(int *arr, int len, bool asd) { // asd 升序
    int step = len / 2; // len = 2^k
    for (; step > 0; step /= 2) {
        for (int i = 0; i < len; i += 2 * step) { // 2 * step 一组序列
            for (int j = i; j < i + step; ++j) { // 遍历序列内部,i是序列起点
                if (asd) {
                    if (arr[j] > arr[j + step]) swap(arr[j], arr[j + step]);
                } else {
                    if (arr[j] < arr[j + step]) swap(arr[j], arr[j + step]);
                }
            }
        }
    }
}


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.将无序的输入序列转换成双调序列
    for (int step = 2; step < len; step *= 2) {
        for (int i = 0; i < len; i += 2 * step) {
            bitonicSort(arr + i, step, asd);
            bitonicSort(arr + step + i, step, !asd);
        }
    }

    cout << "双调序列: ";
    printArr(arr, len); // 双调序列

    // 3.双调序列合并成为有序序列
    bitonicSort(arr, len, asd);

    cout << "有序序列: ";
    printArr(arr, len); // 有序序列

}
无序序列:  26 25 38 50 29 19 91 80  7 46 15 78 19 32 86 66

双调序列:  19 25 26 29 38 50 80 91 86 78 66 46 32 19 15  7

有序序列:   7 15 19 19 25 26 29 32 38 46 50 66 78 80 86 91

2. 显示排序过程的双调排序(非递归)(2^n)

#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;
}


// 双调排序
void sortSeq(int *arr, int len, bool asd) {
    int step = len / 2;
    for (; step > 0; step /= 2) {
        for (int i = 0; i < len; i += 2 * step) {
            for (int j = 0; j < step; ++j) {
                if (asd) {
                    if (arr[i + j] > arr[i + step + j]) swap(arr[i + j], arr[i + step + j]);
                } else {
                    if (arr[i + j] < arr[i + step + j]) swap(arr[i + j], arr[i + step + j]);
                }
            }
        }
        cout << "步长为: " << step << ", ";
        cout << "序列长: " << len << ", ";
        printArr(arr, len);
    }
}


int main() {

    int len = 16;
    int *arr = new int[len];

    srand((unsigned) time(NULL));

    for (int i = 0; i < len; ++i) {
        arr[i] = rand() % 100;
    }

    cout << "初始数据" << endl;
    printArr(arr, len);

    bool asd = true;


    for (int step = 2; step < len; step *= 2) {
        for (int i = 0; i < len; i += 2 * step) {
            sortSeq(arr + i, step, asd); // 前半升序
            sortSeq(arr + i + step, step, !asd); // 后半降序
        }
    }
    cout << "双调序列" << endl;
    printArr(arr, len);

    sortSeq(arr, len, asd);
    cout << "双调排序" << endl;
    printArr(arr, len);

}
初始数据
 36 15 27 57  4 32 69 46 76 99 21 31 92 31 90 13

# 自底向上的构建过程中也包含了自顶向下的排序过程

步长为: 1, 序列长: 2,  15 36

步长为: 1, 序列长: 2,  57 27

步长为: 1, 序列长: 2,   4 32

步长为: 1, 序列长: 2,  69 46

步长为: 1, 序列长: 2,  76 99

步长为: 1, 序列长: 2,  31 21

步长为: 1, 序列长: 2,  31 92

步长为: 1, 序列长: 2,  90 13

步长为: 2, 序列长: 4,  15 27 57 36

步长为: 1, 序列长: 4,  15 27 36 57

步长为: 2, 序列长: 4,  69 46  4 32

步长为: 1, 序列长: 4,  69 46 32  4

步长为: 2, 序列长: 4,  31 21 76 99

步长为: 1, 序列长: 4,  21 31 76 99

步长为: 2, 序列长: 4,  90 92 31 13

步长为: 1, 序列长: 4,  92 90 31 13

步长为: 4, 序列长: 8,  15 27 32  4 69 46 36 57

步长为: 2, 序列长: 8,  15  4 32 27 36 46 69 57

步长为: 1, 序列长: 8,   4 15 27 32 36 46 57 69

步长为: 4, 序列长: 8,  92 90 76 99 21 31 31 13

步长为: 2, 序列长: 8,  92 99 76 90 31 31 21 13

步长为: 1, 序列长: 8,  99 92 90 76 31 31 21 13

双调序列
  4 15 27 32 36 46 57 69 99 92 90 76 31 31 21 13

# 自顶向下完成双调序列的排序

步长为: 8, 序列长: 16,   4 15 27 32 31 31 21 13 99 92 90 76 36 46 57 69

步长为: 4, 序列长: 16,   4 15 21 13 31 31 27 32 36 46 57 69 99 92 90 76

步长为: 2, 序列长: 16,   4 13 21 15 27 31 31 32 36 46 57 69 90 76 99 92

步长为: 1, 序列长: 16,   4 13 15 21 27 31 31 32 36 46 57 69 76 90 92 99

双调排序
  4 13 15 21 27 31 31 32 36 46 57 69 76 90 92 99

3. 基本双调排序(递归)(2^n)

根据 01 原则,递归时分治的两段必须 前半降序,后半升序

#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直到序列长度为1
        bitonicMerge(arr, m, asd); // 都是按照asd进行merge
        bitonicMerge(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);
}
无序序列:  83 86 77 15 93 35 86 92 49 21 62 27 90 59 63 26

双调排序:  15 21 26 27 35 49 59 62 63 77 83 86 86 90 92 93

4. 任意双调排序(递归)(任意正数)

说说双调排序 - ddppqq - CSDN

递归实现的任意双调排序与基本双调排序区别在于归并部分

// 基本双调排序归并
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直到序列长度为1
        bitonicMerge(arr, m, asd);
        bitonicMerge(arr + m, m, asd);
    }
}

// 任意双调排序归并
void bitonicMergeAnyN(int *arr, int len, bool asd) {
    if (len > 1) {
        int m = getGreatest2nLessThan(len);
        for (int i = 0; i < len - m; ++i) {
            if (arr[i] > arr[i + m] == asd)
                swap(arr[i], arr[i + m]); // 根据asd判断是否交换
        }
        bitonicMergeAnyN(arr, m, asd); // 一般情况下,m > len-m
        bitonicMergeAnyN(arr + m, len - m, asd);
    }
}

对于任意双调排序,因为 len 不一定等于 2^n,所以 m 取 Greatest2nLessThan(len),即在满足 2^n < len 的情况下,n 取最大值时,m = 2^n,对于基本双调排序,len = 2^n,m = len/2 也即为Greatest2nLessThan(len)

任意双调排序主要思想:

  1. 首先不断的二分,直到每组只剩下一个元素,然后开始归并。
  2. 双调排序归并时以不大于 n 的最大 2 的幂次方 2k 为界限,把 2k..n 的元素分别与 0..(n-2k) 的元素比较,然后再分别在0..2k 和 2k..n 这两段上分别应用比较网络。
  3. 双调排序难以理解的地方就在于这个归并的过程,假设我们要把长度为 n 的序列 arr 升序排列,由于二分时是把前 n/2 的序列降序排列后 n/2 的序列升序排列的,而 n-2k < n/2,所以前 n-2k 和后 n-2k 个元素都大于中间的元素,当前 n-2k 个元素和后 n-2k 个元素比较时,会把序列中最大的 n-2k 个元素放到最后 n-2k 个位置上,也就是说比较后,2k..n 的元素都比 0..2k 的元素大(这一点是确定的,因为前半段递减,后半段递增,比较的时候相当于“极大值”与“极小值”的比较,有点田忌赛马的味道),这样在分别对这两段用同样的方法归并,最终得到完整的升序序列。

双调排序分治的核心理论:任意的正整数都能表示成 2 的幂指数和的形式,其实就是所有的正整数都能用二进制表示。因为序列在不断地拆分成 2 的幂指数的长度。

n = 6, 4+2
n = 7, 4+3 = 4+2+1
n = 8, 4+4

举个例子:

以6个元素的序列为例,前3个元素已经降序排好,后3个元素已经升序排好(递归到底时是从1个元素开始返回的,所以对6个元素归并时前后3个元素已经排好序),这个以4(不大于6的最大2的幂次方)为界限,分别让第1个和第5个、第2个和第6个元素比较,使得这6个元素中最大的两个处于第5、6个位置上,然后再分别对前4个和后2个元素按此方法归并,最终递归完成排序。

然后在 bitonicSort 函数中,只需将 bitonicMerge 改为 bitonicMergeAnyN,另外,bitonicSort 分治的两段长度不一定相等,即 m >= len-m,只有当 len = 2^n 时,m = len-m = len/2,此时任意双调排序实际为基本双调排序。

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之后形成了双调序列,然后传入merge合并成asd规定的序列
//        bitonicMerge(arr, len, asd); // 注释掉基本双调排序
        bitonicMergeAnyN(arr, len, asd);
    }
}

完整代码

#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;
}

int getGreatest2nLessThan(int len) {
    int k = 1;
    while (k < len) k = k << 1; // 注意一定要加k=
    return k >> 1;
}

// 基本双调排序归并
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直到序列长度为1
        bitonicMerge(arr, m, asd);
        bitonicMerge(arr + m, m, asd);
    }
}

// 任意双调排序归并
void bitonicMergeAnyN(int *arr, int len, bool asd) {
    if (len > 1) {
        int m = getGreatest2nLessThan(len);
        for (int i = 0; i < len - m; ++i) {
            if (arr[i] > arr[i + m] == asd)
                swap(arr[i], arr[i + m]); // 根据asd判断是否交换
        }
        bitonicMergeAnyN(arr, m, asd); // 一般情况下,m > len-m
        bitonicMergeAnyN(arr + m, len - 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之后形成了双调序列,然后传入merge合并成asd规定的序列
//        bitonicMerge(arr, len, asd); // 注释掉基本双调排序
        bitonicMergeAnyN(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);
}
无序序列:  83 86 77 15 93 35 86 92 49 21 62

双调排序:  15 21 35 49 62 77 83 86 86 92 93
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容

  • 分段双调排序 - Shuai-Xie -Github 问题说明 给出分成 m 段的 n 个浮点数,输入数据已按段号...
    谢小帅阅读 1,359评论 0 0
  • 概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    蚁前阅读 5,167评论 0 52
  • 概述:排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    每天刷两次牙阅读 3,727评论 0 15
  • 1.插入排序—直接插入排序(Straight Insertion Sort) 基本思想: 将一个记录插入到已排序好...
    依依玖玥阅读 1,239评论 0 2
  • 一座华南城,一个经济圈。 深圳华南城是一家综合商贸物流上市企业,在全国8个首府城市有项目。我们大南宁也有一座48...
    南宁唐方阅读 440评论 4 4