分段双调排序

今天在"https://blog.csdn.net/u014226072/article/details/56840243"上看到一个关于分段双调排序的问题. 抄录如下:

问题说明:

***********************

给出分成m段的n个浮点数,输入数据已按段号有序,但每段内部无序。用C/C++ 编写一个分段双调排序(Bitonic sort)函数,对每一段内部的浮点数进行排序,但不要改变段间的位置。

接口方式:

***********************

void segmentedBitonicSort(float* data, int*seg_id, int* seg_start, int n, int m);

输入数据中,data包含需要分段排序的n个float值,seg_id给出data中n个元素各 自所在的 段编号。seg_start共有m+1个元素,前m个分别给 出0..m-1共m个段的起 始位置,seg_start[m]保证等于n。

seg_id中的元素保证单调不下降,即对任意的i<j,seg_id[i]<=seg_id[j]。 seg_id所有元 素均在0到m-1范围内。

输出结果覆盖data,保证每一段内排序,但不改变段间元素的顺序。

注意:

***********************

1、必须使用双调排序算法进行排序。

2、可以直接使用从网上下载的双调排序代码,但须注明出处。

样例输入:

***********************

float data[5]={0.8, 0.2, 0.4, 0.6, 0.5};

int seg_id[5]={0,   0,  1,   1,   1}

int seg_start[3]={0,2,5};

int n=5;

int m=2;

样例输出:

***********************

float data[5]={0.2, 0.8, 0.4, 0.5, 0.6};

加分挑战(非必需):

***********************

1、不递归:segmentedBitonicSort函数及其所调用的任何其他函数都不得直接或间接地进行递归。

2、不调用函数:segmentedBitonicSort不调用除标准库函数外的任何其他函数。

3、内存高效:segmentedBitonicSort及其所调用的任何其他函数都不得进行动态内存分配,包括malloc、new和静态定义的STL容器。

4、可并行:segmentedBitonicSort涉及到的所有时间复杂度O(n)以上的代码都写 在for循 环中,而且每个这样的for循环内部的循环顺序可 以任意改变,不影响程 序结果。注:自己测试时可以用rand()决定循环顺序。

5、不需内存:segmentedBitonicSort不调用任何函数(包括C/C++标准库函数), 不使用全局变量,所有局部变量都是int、float或指针类 型,C++程序不使用new关键字。

6、绝对鲁棒:在输入数据中包含NaN时(例如sqrt(-1.f)),保证除NaN以外 的数 据正确排序,NaN的个数保持不变。

你的程序每满足以上的一个条件都可以获得额外的加分。

为此, 我们先了解一下双调排序. 下图来自于:

https://www.cs.rutgers.edu/~venugopa/parallel_summer2012/bitonic_overview.html


双调排序是一种基于比较的可并行的排序算法. 

第一点, 如果一个序列前半部分和后半部分都单调, 那么称它为一个双调序列. (图中红色部分是前半部分, 递增. 蓝色部分递减)
第二点, 对一个双调序列, 依次比较两个单调部分的对应位置的元素, 视比较结果交换位置. 可以得到两个序列(s1和s2). 
第三点, 这两个序列都是双调序列, 并且前一个双调序列的每个元素都小于后一个双调序列. 
第四点, 此时, 对于整个数组来说, 前部分小于(同理也可以是大于)后半部分. 那么我们可以递归地使用这种拆分双调序列的方法, 直到序列长度为1, 这时, 整个数组将是有序的. 
------
但是我们需要排序的数组并不一定一开始就是双调序列的, 所以要应用这个排序方法还需要换个思考方式. 
- 注意, 长度为1的序列可以看作是单调的, 那么, 长度为2的序列一定是双调序列. 
- 双调序列可以排序, 所以我们先给这些长度为2的双调序列排序. 

- 排序的时候, 如果排序的方式我们选择递增和递减交替. 排序结束时, 我们将得到长度为4的双调序列. 


好了, 问题已经解决了. 我们终将得到长度为n的双调序列, 也就是把原数组变成了双调序列. 当然, 值得一提的是, 这种方法只能给长度为2的整数次幂的数组排序, 对于一个任意的数组, 需要用一个较大的值填充至长度为2^n之后才能用这种方法. 

不严格的时间复杂度分析: 容易看出, 给一个双调序列排序的时间复杂度是nlogn(一共logn轮比较), 而为了使原数组变成双调序列, 我们要进行logn轮, 每轮n/len个长度为len的排序, 其中在第i轮时, len=logi. 所以有一个很明显的上界O(n*logn*logn). 但是这是串行分析的结果. 事实上在并行条件下可以大大加速. 


具体到这道题, 一个很容易想到的思路是把seg_id当作第一关键字, data当作第二关键字, 把整个数组补齐到2^n后整体排序. 据此, 可以很容易写出满足不递归, 不调用函数, 可并行, 绝对鲁棒的代码. 目前我还想不到如何不动态分配内存. 关于绝对鲁棒, 我是采用手动设置nan大于任何数字来避免因nan而出现排序错乱的问题的. 


但是这里还可以再有一点改进, 我们可以分别给每一段排序. 这样可以把时间复杂度从O(n*log^2n)降为近似O(m * n/m * log^2(n/m)). 所需额外空间也更少. 

最终代码如下:

#include <cstdio>
#include <stdlib.h>
#include <time.h>
#include <assert.h>
#include <cmath>
#include <iostream>

#define SEGN 50
#define SEGLEN 50

#define nan (std::sqrt(-1.f))

//#define debug

void seg3(float* data, int* seg_id, int* seg_start, int n, int m) 
{
    //#ifdef debug
    printf("seg3:\nn = %d, m = %d\n", n, m);
    for (int i = 0; i < n; i++) std::cout << data[i] << ", ";
    printf("\n");
    for (int i = 0; i < n; i++) printf("%d, ", seg_id[i]);
    printf("\n");
    for (int i = 0; i <= m; i++) printf("%d, ", seg_start[i]);
    printf("\n");
    //#endif
    
    for (int seg = 0; seg < m; seg++) {
        int curlen = seg_start[seg + 1] - seg_start[seg];
        int pow = 0;
        int tempn = curlen;
        while (tempn) {
            tempn >>= 1;
            pow++;
        }
        tempn = (1 << pow);
        if (tempn == (curlen << 1)) {
            tempn >>= 1;
        }
        int endid = seg_start[seg] + tempn;
        float* datab;
        int* segb;
        if (endid > n) {
            datab = (float*)malloc(sizeof(float) * (endid - n));
            segb = (int*)malloc(sizeof(int) * (endid - n));
            for (int i = 0; i < endid - n; i++) {
                segb[i] = m;
            }
        }
        else {
            datab = data + seg_start[seg + 1];
            segb = seg_id + seg_start[seg + 1];
        }
        // sort begin
        for (int s = 2; s <= tempn; s *= 2) {
            for (int itr = 0; itr < tempn; itr += s * 2) {
                // mergeup(arr+i, s)
                // float* arr = data + i;
                int offset = itr + seg_start[seg];
                int len = s;
                // begin
                int step = len / 2, i, j, k, temp;
                float tempf;
                while (step > 0) {
                    for (i = 0; i < len; i += step * 2) {
                        for (j = i, k = 0; k < step; j++, k++) {
                            // if arr[j] > arr[j + step], swap
                            int* seg_add_j = offset + j < n ? seg_id + offset + j : segb + offset + j - n;
                            int* seg_add_js = offset + j + step < n ? seg_id + offset + j + step : segb + offset + j + step - n;
                            float* data_add_j = offset + j < n ? data + offset + j : datab + offset + j - n;
                            float* data_add_js = offset + j + step < n ? data + offset + j + step : datab + offset + j + step - n;
                            bool swap = false;
                            
                            if (*seg_add_j > *seg_add_js) {
                                swap = true;
                            }
                            else if (*seg_add_j == *seg_add_js) {  
                                if (*data_add_j > *data_add_js) {
                                    swap = true;
                                }
                                else if (!(*data_add_j > 0) && !(*data_add_j <= 0)) {
                                    // *data_add_j = nan
                                    swap = true;
                                }
                            }
                            
                            if (swap) {
                                temp = *seg_add_j;
                                *seg_add_j = *seg_add_js;
                                *seg_add_js = temp;
                                tempf = *data_add_j;
                                *data_add_j = *data_add_js;
                                *data_add_js = tempf;
                            }
                        }
                    }
                    step >>= 1;
                }
                //mergedown(arr+i+s,s)
                //arr = data + i + s;
                if (s == tempn) continue;
                offset = itr + len + seg_start[seg];
                step = len / 2;
                // begin
                while (step > 0) {
                    for (i = 0; i < len; i += step * 2) {
                        for (j = i, k = 0; k < step; j++, k++) {
                            // if arr[j] < arr[j + step], swap
                            int* seg_add_j = offset + j < n ? seg_id + offset + j : segb + offset + j - n;
                            int* seg_add_js = offset + j + step < n ? seg_id + offset + j + step : segb + offset + j + step - n;
                            float* data_add_j = offset + j < n ? data + offset + j : datab + offset + j - n;
                            float* data_add_js = offset + j + step < n ? data + offset + j + step : datab + offset + j + step - n;
                            
                            bool swap = false;
                            if (*seg_add_j < *seg_add_js) {
                                swap = true;
                            }
                            else if (*seg_add_j == *seg_add_js) {  
                                if (*data_add_j < *data_add_js) {
                                    swap = true;
                                }
                                else if (!(*data_add_js > 0) && !(*data_add_js <= 0)) {
                                    // *data_add_js = nan
                                    swap = true;
                                }
                            }
                            
                            if (swap) {
                                temp = *seg_add_j;
                                *seg_add_j = *seg_add_js;
                                *seg_add_js = temp;
                                tempf = *data_add_j;
                                *data_add_j = *data_add_js;
                                *data_add_js = tempf;
                            }
                        }
                    }
                    step >>= 1;
                }
            }
        }
        if (endid > n) {
            free(datab);
            free(segb); 
        }
    }
}

int main() {
    #ifdef debug
    float data[] = {1.08344, 2.6747, nan, 1.94815, 1.11102, 0.289917, 0.64205,
    6.59573, 0.00369898, 12.7636, 1.56606, 0.0572074, 0.380153, nan, 2.96348,
    3.89681, nan, 2.27465, 1.71466, 3.69074, 0.351484};
  int seg_id[] = {0, 0, 0, 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4};
  int seg_start[] = {0, 5, 7, 11, 16, 21};
  int n = 21;
  int m = 5;
  seg3(data, seg_id, seg_start, n, m);
        for (int j = 0; j < n; j++) {
            std::cout << data[j] << ' ';
        }
        std::cout << std::endl;
        //
        bool wrong = false;
        for (int j = 1; j < n; j++) {
            if (seg_id[j] < seg_id[j - 1]) {
                wrong = true;
                break;
            }
            
            if (!(seg_id[j] > seg_id[j - 1] || (!(data[j] < data[j - 1])))) {
                std::cout << "j :" << j << std::endl;
                std::cout << data[j] << ' ' << data[j-1] << std::endl;
                std::cout << seg_id[j] << ' ' << seg_id[j-1] << std::endl;
                wrong = true;
                break;
            }
        }
        if (wrong) std::cerr << "wrong^" << std::endl;
    #else
    srand((unsigned)time(NULL));
    // test 
    float* data;
    int* seg_id;
    int* seg_start;
    int n;
    int m;
    for (int i = 0; i < 10; i++) {
        m = rand() % SEGN + 5;
        seg_start = (int*)malloc(sizeof(int) * (m + 1));
        seg_start[0] = 0;
        for (int j = 1; j <= m; j++) {
            seg_start[j] = rand() % SEGLEN + 1 + seg_start[j-1];
        }
        n = seg_start[m];
        seg_id = (int*)malloc(sizeof(int)*n);
        data = (float*)malloc(sizeof(float)*n);
        for (int j = 1; j <= m; j++) {
            for (int k = seg_start[j - 1]; k < seg_start[j]; k++) {
                seg_id[k] = j - 1;
            }
        }
        for (int j = 0; j < n; j++) {
            float tempf = 1.0 * rand() / rand();
            if (tempf < 0.6 && tempf > 0.4) {
                data[j] = std::sqrt(-1.f);
                //data[j] = 0.5;
            }
            else {
                data[j] = tempf;
            }
        }
        // 
        seg3(data, seg_id, seg_start, n, m);
        //
        for (int j = 1; j < n; j++) {
            assert(seg_id[j] >= seg_id[j - 1]);
            assert(seg_id[j] > seg_id[j - 1] || (!(data[j] < data[j - 1])));
        }
        free(seg_start);
        free(seg_id);
        free(data);
    }
    #endif
    return 0;
}



后来我又找到两篇文章, 实现了不用动态分配内存的任意长度数组双调排序, 以后有时间试试把它改为不调用函数的形式. 
https://blog.csdn.net/ljiabin/article/details/8631374
https://blog.csdn.net/lqmiku/article/details/78834178


Ref:
https://blog.csdn.net/u014226072/article/details/56840243
https://www.cs.rutgers.edu/~venugopa/parallel_summer2012/bitonic_overview.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值