使用mpi并行技术实现快排Qsort()

快排基本原理:

快速排序可以说是最为常见的排序算法,冒泡排序时间复杂度达到了O(N2),而桶排序容易造成浪费空间。快排(Quicksort)就成为了不错的选择。

1、原理:快排需要找一个数作为基准数,用来参照。(可取第一个数为参照)

        基准数在中间某位置,两端有指针,找到相应数后,交换。

注意:若令第一个数为基准数,先从右往左找,再从左往右找。

2、优点:平均时间复杂度O(NlogN),相比冒泡排序每次交换可以是跳跃式的

排序过程:

使用mpi并行技术实现快排Qsort()_第1张图片

代码实现:

#include
#include
#include 
#include 

int Partition(int* data, int start, int end)   //划分数据
{
    int temp = data[start];   //以第一个元素为基准
    while (start < end) {
        while (start < end && data[end] >= temp)end--;   //找到第一个比基准小的数
        data[start] = data[end];
        while (start < end && data[start] <= temp)start++;    //找到第一个比基准大的数
        data[end] = data[start];
    }
    data[start] = temp;   //以基准作为分界线
    return start;
}

void QuickSort(int* data, int start, int end)  //串行快排
{
    if (start < end) {    //未划分完
        int r = Partition(data, start, end);   //继续划分,进行递归排序
        QuickSort(data, start, r - 1);
        QuickSort(data, r + 1, end);
    }
}

//求2的n次方
int exp2(int n)
{
    int i = 1;
    while (n-- > 0) i *= 2;
    return i;
}

//求以2为底n的对数,向下取整
int log2(int n)
{
    int i = 1, j = 2;
    while (j < n) {
        j *= 2;
        i++;
    }
    return i;
}

void paraQuickSort(int* data, int start, int end, int m, int id, int nowID, int N)
{
    int i, j, r = end, length = -1;  //r表示划分后数据前部分的末元素下标,length表示后部分数据的长度
    int* t;
    MPI_Status status;
    if (m == 0) {   //无进程可以调用
        if (nowID == id) QuickSort(data, start, end);
        return;
    }
    if (nowID == id) {    //当前进程是负责分发的
        while (id + exp2(m - 1) > N && m > 0) m--;   //寻找未分配数据的可用进程
        if (id + exp2(m - 1) < N) {  //还有未接收数据的进程,则划分数据
            r = Partition(data, start, end);
            length = end - r;
            MPI_Send(&length, 1, MPI_INT, id + exp2(m - 1), nowID, MPI_COMM_WORLD);
            if (length > 0)   //id进程将后部分数据发送给id+2^(m-1)进程
                MPI_Send(data + r + 1, length, MPI_INT, id + exp2(m - 1), nowID, MPI_COMM_WORLD);
        }
    }
    if (nowID == id + exp2(m - 1)) {    //当前进程是负责接收的
        MPI_Recv(&length, 1, MPI_INT, id, id, MPI_COMM_WORLD, &status);
        if (length > 0) {   //id+2^(m-1)进程从id进程接收后部分数据
            t = (int*)malloc(length * sizeof(int));
            if (t == 0) printf("Malloc memory error!");
            MPI_Recv(t, length, MPI_INT, id, id, MPI_COMM_WORLD, &status);
        }
    }
    j = r - 1 - start;
    MPI_Bcast(&j, 1, MPI_INT, id, MPI_COMM_WORLD);
    if (j > 0)     //负责分发的进程的数据不为空
        paraQuickSort(data, start, r - 1, m - 1, id, nowID, N);   //递归调用快排函数,对前部分数据进行排序
    j = length;
    MPI_Bcast(&j, 1, MPI_INT, id, MPI_COMM_WORLD);
    if (j > 0)     //负责接收的进程的数据不为空
        paraQuickSort(t, 0, length - 1, m - 1, id + exp2(m - 1), nowID, N);   //递归调用快排函数,对前部分数据进行排序
    if ((nowID == id + exp2(m - 1)) && (length > 0))     //id+2^(m-1)进程发送结果给id进程
        MPI_Send(t, length, MPI_INT, id, id + exp2(m - 1), MPI_COMM_WORLD);
    if ((nowID == id) && id + exp2(m - 1) < N && (length > 0))     //id进程接收id+2^(m-1)进程发送的结果
        MPI_Recv(data + r + 1, length, MPI_INT, id + exp2(m - 1), id + exp2(m - 1), MPI_COMM_WORLD, &status);
}

int main(int argc, char* argv[])
{
    int* data;
    int rank, size;
    int i, j, m, r, n = atoi(argv[1]);   //随机数组的长度
    double start_time, end_time;
    MPI_Status status;
    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);  //当前进程的进程号
    MPI_Comm_size(MPI_COMM_WORLD, &size);  //总进程数
    if (rank == 0) {   //根进程生成随机数组
        start_time = MPI_Wtime();
        data = (int*)malloc(n * sizeof(int));
        srand(time(NULL) + rand());   //随机数种子
        for (i = 0; i < n; i++)
            data[i] = (int)rand();   //获取n个随机整数
    }
    m = log2(size);  //第一次分发需要给第2^(m-1)个进程
    
    MPI_Bcast(&n, 1, MPI_INT, 0, MPI_COMM_WORLD);  //广播n
    paraQuickSort(data, 0, n - 1, m, 0, rank, size);  //执行快排
    

    if (rank == 0) {   //根进程输出并行时间
        end_time = MPI_Wtime();
        for (i = 0; i < 10 && i

运行结果:

Mpi快排结果:

使用mpi并行技术实现快排Qsort()_第2张图片

注1000后面的参数为生成随机数的个数

编译:mpicxx ./filename.cpp -o ./filename

Mpi基本原理:

  1.什么是MPI

Massage Passing Interface:是消息传递函数库的标准规范,由MPI论坛开发。

一种新的库描述,不是一种语言。共有上百个函数调用接口,提供与C和Fortran语言的绑定

MPI是一种标准或规范的代表,而不是特指某一个对它的具体实现

MPI是一种消息传递编程模型,并成为这种编程模型的代表和事实上的标准

2.MPI的特点

MPI有以下的特点:

消息传递式并行程序设计

指用户必须通过显式地发送和接收消息来实现处理机间的数据交换。

在这种并行编程中,每个并行进程均有自己独立的地址空间,相互之间访问不能直接进行,必须通过显式的消息传递来实现。

这种编程方式是大规模并行处理机(MPP)和机群(Cluster)采用的主要编程方式。

并行计算粒度大,特别适合于大规模可扩展并行算法

用户决定问题分解策略、进程间的数据交换策略,在挖掘潜在并行性方面更主动,并行计算粒度大,特别适合于大规模可扩展并行算法

消息传递是当前并行计算领域的一个非常重要的并行程序设计方式

二、MPI的基本函数

MPI调用借口的总数虽然庞大,但根据实际编写MPI的经验,常用的MPI函数是以下6个:

MPI_Init(…);

MPI_Comm_size(…);

MPI_Comm_rank(…);

MPI_Send(…);

MPI_Recv(…);

MPI_Finalize();

三、MPI的通信机制

MPI是一种基于消息传递的编程模型,不同进程间通过消息交换数据。

1.MPI点对点通信类型

所谓点对点的通信就是一个进程跟另一个进程的通信,而下面的聚合通信就是一个进程和多个进程的通信。

使用mpi并行技术实现快排Qsort()_第3张图片

  1. 标准模式:

使用mpi并行技术实现快排Qsort()_第4张图片

该模式下MPI有可能先缓冲该消息,也可能直接发送,可理解为直接送信或通过邮局送信。是最常用的发送方式。

由MPI决定是否缓冲消息

没有足够的系统缓冲区时或出于性能的考虑,MPI可能进行直接拷贝:仅当相应的接收完成后,发送语句才能返回。

这里的系统缓冲区是指由MPI系统管理的缓冲区。而非进程管理的缓冲区。

MPI环境定义有三种缓冲区:应用缓冲区、系统缓冲区、用户向系统注册的通信用缓冲区

MPI缓冲消息:发送语句在相应的接收语句完成前返回。

这时后发送的结束或称发送的完成== 消息已从发送方发出,而不是滞留在发送方的系统缓冲区中。

该模式发送操作的成功与否依赖于接收操作,我们称之为非本地的,即发送操作的成功与否跟本地没关系。

你可能感兴趣的:(算法,排序算法,数据结构)