串行&并行快速排序

(一)串行快速排序

程序中 j = right - 1; 语句说明:此句直接从倒数第二个开始,同时由于--j会判断倒数第三个。这是因为在使用三数中值分割后:最后一个一定大于枢纽元,经过交换,倒数第二个就是枢纽元。

#include <iostream>
#define CUTOFF 10 // 定义快速排序的数组规模下界

// 交换两个整数
void swap(int *fir, int *sec)
{
    int temp = 0;
    temp = *fir;
    *fir = *sec;
    *sec = temp;
}

// 插入排序算法
void InsertionSort(int A[], int n)
{
    int j, p;
    int tmp;
    for (p = 1; p < n; p++)
    {
        tmp = A[p];
        for (j = p; j > 0 && A[j - 1] > tmp; j--)
            A[j] = A[j - 1];
        A[j] = tmp;
    }
}

// 三数中值分割
int Median3(int A[], int left, int right)
{
    // 计算中间位置
    int center = (left + right) / 2;

    if (A[left] > A[center])
        swap(&A[left], &A[center]);
    if (A[left] > A[right])
        swap(&A[left], &A[right]);
    if (A[center] > A[right])
        swap(&A[center], &A[right]);

    /* 不变等式: A[left] <= A[center] <= A[right] */

    swap(&A[center], &A[right - 1]); // 隐藏枢纽元
    return A[right - 1];
}

// 快速排序算法
void Qsort(int A[], int left, int right)
{
    int i, j;
    // 枢纽元
    int pivot;

    if (left + CUTOFF <= right)
    {
        pivot = Median3(A, left, right);
        // 初始化i,j的位置
        i = left;
        j = right - 1;
        for (;;)
        {
            while (A[++i] < pivot)
            {
            }
            while (A[--j] > pivot)
            {
            }
            if (i < j)
                swap(&A[i], &A[j]);
            else
                break;
        }
        // 将枢纽元与i所指向的元素交换
        swap(&A[i], &A[right - 1]);

        // 以i为分界线,分别对左右两端递归的进行快速排序
        Qsort(A, left, i - 1);
        Qsort(A, i + 1, right);
    }
    else
    {
        // 在该数组上做一次插入排序
        InsertionSort(A + left, right - left + 1);
    }
}

// 打印数组
void printArray(int A[], int len)
{
    int length = len;
    int index;
    for (index = 0; index <= len - 1; index++)
    {
        printf("%d ", A[index]);
    }
}

int main()
{
    // 函数声明
    void swap(int *fir, int *sec);
    void InsertionSort(int A[], int n);
    int Median3(int A[], int left, int right);
    void Qsort(int A[], int left, int right);
    void printArray(int A[], int len);

    int A[15] = {2, 5, 1, 7, 6, 19, 30, 9, 4, 8, 21, 11, 18, 22, 47};
    printf("快速排序之前: \n");
    printArray(A, 15);

    Qsort(A, 0, 14);

    printf("\n");
    printf("快速排序之后: \n");
    printArray(A, 15);
    system("pause");
    return 0;
}

(二)并行快速排序

并行化可以优化许多算法,前提是并行化的任务之间不会产生冲突。

快速排序的核心逻辑为分治,通过一次遍历,使得区间分成了两个子区间,其中一个子区间的元素恒小于等于另一个区间的元素,再用同样的方法分别处理这两个子区间。可以发现,这两个子区间不重叠,并且后续的操作都是在一个区间内继续完成,不会跨区间,因此满足了并行化的条件。

使用OpenMP(Linux&Windows环境)

#include <stdio.h>
#include <omp.h>
#include <time.h>
#include <fstream>
#include <stdlib.h>

#define CUTOFF 10 // 定义快速排序的数组规模下界

// 交换两个整数
void swap(int *fir, int *sec)
{
    int temp = 0;
    temp = *fir;
    *fir = *sec;
    *sec = temp;
}

// 三数中值分割
int Median3(int A[], int left, int right)
{
    // 计算中间位置
    int center = (left + right) / 2;

    if (A[left] > A[center])
    {
        swap(&A[left], &A[center]);
    }
    if (A[left] > A[right])
    {
        swap(&A[left], &A[right]);
    }
    if (A[center] > A[right])
    {
        swap(&A[center], &A[right]);
    }

    /* 不变等式: A[left] <= A[center] <= A[right] */

    swap(&A[center], &A[right - 1]); // 隐藏枢纽元
    return A[right - 1];
}

// 插入排序算法
void InsertionSort(int A[], int n)
{
    int j, p;
    int tmp;
    for (p = 1; p < n; p++)
    {
        tmp = A[p];
        for (j = p; j > 0 && A[j - 1] > tmp; j--)
        {
            A[j] = A[j - 1];
        }
        A[j] = tmp;
    }
}

void Qsort(int A[], int left, int right)
{
    int i, j;
    // 枢纽元
    int pivot;

    if (left + CUTOFF <= right)
    {
        pivot = Median3(A, left, right);
        // 初始化i,j的位置
        i = left;
        j = right - 1;
        for (;;)
        {
            while (A[++i] < pivot)
            {
            }
            while (A[--j] > pivot)
            {
            }
            if (i < j)
                swap(&A[i], &A[j]);
            else
                break;
        }
        // 将枢纽元与i所指向的元素交换
        swap(&A[i], &A[right - 1]);

// 以i为分界线,分别对左右两端递归的进行快速排序
#pragma omp parallel sections
        {
#pragma omp section
            Qsort(A, left, i - 1);
#pragma omp section
            Qsort(A, i + 1, right);
        }
    }
    else
    {
        // 在该数组上做一次插入排序
        InsertionSort(A + left, right - left + 1);
    }
}

int main()
{
    int n = 4;
    int size = 1000000;

    int *A = (int *)malloc(sizeof(int) * size);
    int *B = (int *)malloc(sizeof(int) * size);

    srand(time(NULL));

    for (int i = 0; i < size; i++)
    {
        A[i] = rand() % size;
        B[i] = A[i];
    }

    /*********以下为并行**************/

    double starttime1 = omp_get_wtime(); 
    omp_set_num_threads(n); 
    Qsort(A, 0, size - 1);
    double endtime1 = omp_get_wtime();

    std::ofstream fout1("outA.txt");
    for (int i = 0; i < size; i++)
    {
        fout1 << A[i] << ' ';
    }
    fout1 << std::endl;
    fout1.close();

    printf("\nparallel time : %lf s\n", endtime1 - starttime1);

    /*********以上为并行*************/

    /*********以下为串行*************/

    double starttime2 = omp_get_wtime();
    omp_set_num_threads(1);
    Qsort(B, 0, size - 1);
    double endtime2 = omp_get_wtime();

    std::ofstream fout2("outB.txt");
    for (int i = 0; i < size; i++)
    {
        fout2 << B[i] << ' ';
    }
    fout2 << std::endl;
    fout2.close();

    printf("\nserial time : %lf s\n", endtime2 - starttime2);

    /*********以上为串行*************/

    system("pause");

    return 0;
}

和归并排序相比,为什么快速排序可以采用多线程?{两个算法思路似乎都是分治去处理)

归并排序必须要同一层同时归并。但是快速排序是彻彻底底的分治,对一个区间操作,快速排序可以产生此区间:1.一定是父区间以枢纽元分界结束(并不是父区间的进程结束)。一个进程顺序可能是把 父区间分界结束,左子进程加入任务,左子进程开始,右子进程加入任务,父区间进程结束;2.不会影响同级区间(分治);3.没有分好界之前不会产生子进程。

posted @ 2023-03-01 07:44  ImreW  阅读(31)  评论(0编辑  收藏  举报