基于快排和二项归并改进的多线程排序


前言

去年操作系统的课程设计,要求设计一个多线程并行排序的程序,记录一下


一、关键结构体

因为线程创建函数用的是unsigned __stdcall function(void *p),所以这里传参需要用到结构体指针进行参数传递。对于快速排序和二路归并排序,每一次递归都需要传递不同的参数,包括:数组指针起始位置结束位置 等,所以需要一个结构体来传递这些参数。
结构体具体定义为:

 struct QckSortThread
 {
     int* p;    //指向数组的指针
     int i;     //起始
     int j;     //终止
     int f;     //记录当前层数
 };

这里的变量f的作用是为了记录生成树的层数,用于控制生成线程的数量。所以这里简单描述一下线程生成的过程。直接上图:
递归二叉树
我们期望的线程创建过程是如上图这样的,因为传统的快速排序和二路归并排序都是不停对原数组进行划分来进行排序,划分后一般是从左到右,从下到上进行归并。而我们需要多线程并行排序的目标为,如图所示的,在进行一次划分后,划分后的数组同时进行下一次划分或者同时向上归并,以此来达到并行的目的。
而具体的最高并行数取决于CPU的核心数,所以我们需要控制生成线程的数量来达到最高效率。如果线程数过多,反而会浪费资源在线程的切换调度上,从而降低效率。
所以这里的结构体QckSortThread中的变量f的作用就是用于记录层数,用层数来控制生成的线程的数目。3层二叉树的叶子节点也就是需要做最大运算量的线程数就是22 总共生成的线程数就是23

二、多线程快速排序

1.快速排序算法

数据结构都学过,就不过多解释了。
代码如下:

 void quick_sort(int* num, int star, int end)
 {
    
     int start = star;      //不变起始
     int ending = end;      //不变末位
     int key = num[start];
     while (star < end)
     {
         while ((star < end) && (num[end] >= key))
         {
             end--;
         }

         num[star] = num[end];

         while ((star < end) && ((num[star]) <= key))
         {
             star++;
         }

         num[end] = num[star];
     }
     num[star] = key;
     if ((star - start) > 1)
     {
         quick_sort(num,start,star-1);
     }
     if ((ending - end) > 1)
     {
         quick_sort(num, star+1, ending);
     }
 }

2.多线程快速排序函数

代码如下:

/*快排多线程递归*/
 unsigned __stdcall Sort_1(void* p)
 {
	/*
		取出传进来的值
	*/
     int star = ((QckSortThread*)p)->i;
     int end = ((QckSortThread*)p)->j;
     int* num = ((QckSortThread*)p)->p;
     int f1 = ((QckSortThread*)p)->f;
     f1++;	//层数+1

     int start = star;      //不变起始
     int ending = end;      //不变末位
     int key = num[start];
	
	/*
		下面是和快速排序相似的逻辑,将数组分成两组,然后保存关键位置,可以对照上面看一看
	*/
     while (star < end)
     {
         while ((star < end) && (num[end] >= key))
         {
             end--;
         }

         num[star] = num[end];

         while ((star < end) && ((num[star]) <= key))
         {
             star++;
         }       
         num[end] = num[star];
     }
    
     num[star] = key;

	/*
	下面是创建线程
	*/
     HANDLE hHandle_1 = 0;
     HANDLE hHandle_2 = 0;
     unsigned threadID_1;
     unsigned threadID_2;
	

	//将数组前半部分传入新线程进行计算
     QckSortThread* p1 = new QckSortThread;
     p1->i = start;
     p1->j = star - 1;
     p1->p = num;
     p1->f = f1;
     if ( (star -start) > 1 && (f1 < level))	//判断是否符合进一步递归条件,否则直接调用单线程快排算法进行归并
     {
         hHandle_1 = (HANDLE)_beginthreadex(NULL, 0, Sort_1, (void*)p1, 0, &threadID_1);
     }
     else
     {
         quick_sort(num, start, star - 1);
     }

	//将后半部分传入新线程,同上
     QckSortThread* p2 = new QckSortThread;
     p2->i = star + 1;
     p2->j = ending;
     p2->p = num;
     p2->f = f1;
     if ((ending - end) > 1&& (f1 < level) )
     {
         hHandle_2 = (HANDLE)_beginthreadex(NULL, 0, Sort_1, (void*)p2, 0, &threadID_2);
     }
     else
     {
         quick_sort(num, star + 1, ending);
     }
	
     delete[]p;	//一定要释放掉!!!!不然结果会不正确,而且不报错!!!

     WaitForSingleObject(hHandle_1, INFINITE); 
     WaitForSingleObject(hHandle_2, INFINITE); 
     CloseHandle(hHandle_1);
     CloseHandle(hHandle_2);
     return 1;
 }

代码思路很简单,逻辑和快速排序差不多。添加了层数的判断,如果层数达到预设值的值或者已经不能再进行分组,就进行单线程排序,否则创建线程继续排序。


三、多线程二路归并排序

1.二路归并算法

数据结构有讲具体算法,不多赘述。
代码如下:

 void  Merge(int* a, int low, int mid, int high)
 {
     int i = low, j = mid + 1, p = low;//对应a数组的下标
     //int* r = new int[high - low + 1];//申请另一个对应大小的数组来存放排好序的数据
     while (i <= mid && j <= high)
     {
         r[p++] = (a[i] <= a[j]) ? a[i++] : a[j++];
     }
     while (i <= mid)
         r[p++] = a[i++];
     while (j <= high)
         r[p++] = a[j++];
     for (p = low, i = low; i <= high; p++, i++)
         a[i] = r[p];//最后再把有序数据存进a数组中,使得a数组对应部分数据有序
 }
 
  void MSort(int* a, int low, int high)
 {
     if (low < high )
     {
        
         int mid = (low + high) / 2;
         MSort(a, low, mid);
        
         MSort(a, mid + 1, high);
         Merge(a, low, mid, high);
     }
 }

这里把原算法Merge中的int* r = new int[high - low + 1];改掉了,因为在并行算法中不断的申请和释放空间会非常非常影响排序的效率,所以这里在函数Merge中的数组r[]是一个预先开辟好空间的全局变量,大小至少大于你需要排序的数量。在之后的并行排序中,各线程就直接在全局变量的对应位置中进行排序操作,不需要重复地申请和释放空间,可节省很多资源。

2.多线程二路归并算法

 /*二路归并多线程递归*/
 unsigned __stdcall Sort_3(void* p)
 {
 	//传参和分割数据
     int star = ((QckSortThread*)p)->i;
     int end = ((QckSortThread*)p)->j;
     int* num = ((QckSortThread*)p)->p;
     int f1 = ((QckSortThread*)p)->f;
     f1++;//记录层数
     
	//和MSort一样的算法逻辑可以对照着看一看
     if (star < end)
     {
         int mid = (star + end) / 2;
         
         HANDLE hHandle_1 = 0;
         HANDLE hHandle_2 = 0;
         unsigned threadID_1;
         unsigned threadID_2;

		//这里判断思想和快速排序一样
         if (f1 < level)
         {
             QckSortThread* p1 = new QckSortThread;
             p1->i = star;
             p1->j = mid;
             p1->p = num;
             p1->f = f1;

             hHandle_1 = (HANDLE)_beginthreadex(NULL, 0, Sort_3, (void*)p1, 0, &threadID_1);
         }
         else
         {
             MSort(num, star, mid);
         }
         if (f1 < level)
         {
             QckSortThread* p2 = new QckSortThread;
             p2->i = mid + 1;
             p2->j = end;
             p2->p = num;
             p2->f = f1;

             hHandle_2 = (HANDLE)_beginthreadex(NULL, 0, Sort_3, (void*)p2, 0, &threadID_2);
         }
         else
         {
             MSort(num, mid + 1, end);
         }
         delete[]p;//一定一定要释放!!!!
         WaitForSingleObject(hHandle_1, INFINITE);
         WaitForSingleObject(hHandle_2, INFINITE);
         CloseHandle(hHandle_1);
         CloseHandle(hHandle_2);
         //这里在分组分结束后需要在向上归并的时候进行Merge排序
        Merge(num, star, mid, end);
     }
     return 1;
 }

这里并行排序的思想和快排很像,就不用多讲了。主要是全局变量的开辟会节省很多时间,读者起始可以试一试不使用全局变量,使用传统算法会慢多少。

四、所有代码

这里将所有代码全部贴出来,包括生成随机数,随机数导出txt,排序导出txt,不同线程数的对比,不同算法所用的时间,需要的可以参考一下。

// 多线程测试.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。

#include <Windows.h>
#include <iostream>
#include <process.h>
#include <time.h>
#include<cstdio>
#include<fstream>
#include<string>
#include<sstream>
#include<algorithm>
using namespace std;
 

double time1[6];

int NUM[3000000];                   //待排数组
int r[3000000];                     //临时存储数组
int w =3000000;                 //记录数据量
int level = 2;  //      记录层数
int level_2 = 10;//最大层数

 struct QckSortThread
 {
     int* p;    //指向数组的指针
     int i;     //起始
     int j;     //终止
     int f;     //记录当前层数
 };

 /*二项归并*/
 void  Merge(int* a, int low, int mid, int high)
 {
     int i = low, j = mid + 1, p = low;//对应a数组的下标
     //int* r = new int[high - low + 1];//申请另一个对应大小的数组来存放排好序的数据
     while (i <= mid && j <= high)
     {
         r[p++] = (a[i] <= a[j]) ? a[i++] : a[j++];
     }
     while (i <= mid)
         r[p++] = a[i++];
     while (j <= high)
         r[p++] = a[j++];
     for (p = low, i = low; i <= high; p++, i++)
         a[i] = r[p];//最后再把有序数据存进a数组中,使得a数组对应部分数据有序
    
 }
 //自顶向下(递归实现)
 void MSort(int* a, int low, int high)
 {
     if (low < high )
     {
        
         int mid = (low + high) / 2;
         MSort(a, low, mid);
        
         MSort(a, mid + 1, high);
         Merge(a, low, mid, high);
     }
 }
 /*快排函数*/
 void quick_sort(int* num, int star, int end)
 {
    
     int start = star;      //不变起始
     int ending = end;      //不变末位
     int key = num[start];

     while (star < end)
     {
         while ((star < end) && (num[end] >= key))
         {
             end--;
         }

         num[star] = num[end];

         while ((star < end) && ((num[star]) <= key))
         {
             star++;
         }

         num[end] = num[star];

     }
     num[star] = key;
     if ((star - start) > 1)
     {
         quick_sort(num,start,star-1);
     }
     if ((ending - end) > 1)
     {
         quick_sort(num, star+1, ending);
     }
 }
/*写随机数*/
void WriteRand()
 {
     FILE* fp;
     string ch;

     srand((unsigned)time(NULL));
     for (int i = 0; i < w; i++)
     {
         NUM[i] = rand()  + rand()  + rand() ;
     }

     fopen_s(&fp, ".\Source.txt", "w");
     for (int i = 0; i < w; i++)
     {
         fprintf(fp, "%d ", NUM[i]);
     }
     fclose(fp);


 }
 /*写文件*/
void Write(const char* filename)
 {
     FILE* fp_1;
     fopen_s(&fp_1, filename, "w");
     for(int i = 0; i < w; i++)
        fprintf(fp_1, "%d ", NUM[i]);
 }
/*快排多线程递归*/
 unsigned __stdcall Sort_1(void* p)
 {


     int star = ((QckSortThread*)p)->i;
     int end = ((QckSortThread*)p)->j;
     int* num = ((QckSortThread*)p)->p;
     int f1 = ((QckSortThread*)p)->f;
     f1++;

     int start = star;      //不变起始
     int ending = end;      //不变末位
     int key = num[start];

     while (star < end)
     {
         while ((star < end) && (num[end] >= key))
         {
             end--;
         }

         num[star] = num[end];

         while ((star < end) && ((num[star]) <= key))
         {
             star++;
         }       

         num[end] = num[star];

     }
    
     num[star] = key;


     HANDLE hHandle_1 = 0;
     HANDLE hHandle_2 = 0;
     unsigned threadID_1;
     unsigned threadID_2;

     QckSortThread* p1 = new QckSortThread;
     p1->i = start;
     p1->j = star - 1;
     p1->p = num;
     p1->f = f1;
     if ( (star -start) > 1 && (f1 < level))
     {
         hHandle_1 = (HANDLE)_beginthreadex(NULL, 0, Sort_1, (void*)p1, 0, &threadID_1);
     }
     else
     {
         quick_sort(num, start, star - 1);
     }

     QckSortThread* p2 = new QckSortThread;
     p2->i = star + 1;
     p2->j = ending;
     p2->p = num;
     p2->f = f1;
     if ((ending - end) > 1&& (f1 < level) )
     {
         hHandle_2 = (HANDLE)_beginthreadex(NULL, 0, Sort_1, (void*)p2, 0, &threadID_2);
     }
     else
     {
         quick_sort(num, star + 1, ending);
     }



     delete[]p;

     WaitForSingleObject(hHandle_1, INFINITE); 
     WaitForSingleObject(hHandle_2, INFINITE); 
     CloseHandle(hHandle_1);
     CloseHandle(hHandle_2);

     return 1;
 }
 /*二路+快排的递归线程1.0*/
 unsigned __stdcall Sort_2(void* p)
 {
     int star = ((QckSortThread*)p)->i;
     int end = ((QckSortThread*)p)->j;
     int* num = ((QckSortThread*)p)->p;
     int f1 = ((QckSortThread*)p)->f;
     f1++;
       int mid = (star + end) / 2;

       HANDLE hHandle_1 = 0;
       HANDLE hHandle_2 = 0;
       unsigned threadID_1;
       unsigned threadID_2;

       QckSortThread* p1 = new QckSortThread;
       p1->i = star;
       p1->j = mid;
       p1->p = num;
       p1->f = f1;
       hHandle_1 = (HANDLE)_beginthreadex(NULL, 0, Sort_1, (void*)p1, 0, &threadID_1);

       QckSortThread* p2 = new QckSortThread;
       p2->i = mid + 1;
       p2->j = end;
       p2->p = num;
       p2->f = f1;
       hHandle_2 = (HANDLE)_beginthreadex(NULL, 0, Sort_1, (void*)p2, 0, &threadID_2);
       delete[]p;
       WaitForSingleObject(hHandle_1, INFINITE);
       WaitForSingleObject(hHandle_2, INFINITE);
       CloseHandle(hHandle_1);
       CloseHandle(hHandle_2);
       Merge(num, star, mid, end);
     return 0;
 }
 /*二路归并多线程递归*/
 unsigned __stdcall Sort_3(void* p)
 {
     int star = ((QckSortThread*)p)->i;
     int end = ((QckSortThread*)p)->j;
     int* num = ((QckSortThread*)p)->p;
     int f1 = ((QckSortThread*)p)->f;
     f1++;

     if (star < end)
     {
         int mid = (star + end) / 2;
         
         HANDLE hHandle_1 = 0;
         HANDLE hHandle_2 = 0;
         unsigned threadID_1;
         unsigned threadID_2;

         if (f1 < level)
         {
             QckSortThread* p1 = new QckSortThread;
             p1->i = star;
             p1->j = mid;
             p1->p = num;
             p1->f = f1;

             hHandle_1 = (HANDLE)_beginthreadex(NULL, 0, Sort_3, (void*)p1, 0, &threadID_1);
         }
         else
         {
             MSort(num, star, mid);
         }
         if (f1 < level)
         {
             QckSortThread* p2 = new QckSortThread;
             p2->i = mid + 1;
             p2->j = end;
             p2->p = num;
             p2->f = f1;

             hHandle_2 = (HANDLE)_beginthreadex(NULL, 0, Sort_3, (void*)p2, 0, &threadID_2);
         }
         else
         {
             MSort(num, mid + 1, end);
         }
         delete[]p;
         WaitForSingleObject(hHandle_1, INFINITE);
         WaitForSingleObject(hHandle_2, INFINITE);
         CloseHandle(hHandle_1);
         CloseHandle(hHandle_2);
        Merge(num, star, mid, end);
     }
     return 1;
 }
 /*二路+快排的递归线程2.0*/
 unsigned __stdcall Sort_5(void* p)
 {
     int star = ((QckSortThread*)p)->i;
     int end = ((QckSortThread*)p)->j;
     int* num = ((QckSortThread*)p)->p;
     int f1 = ((QckSortThread*)p)->f;
     f1++;

     if (star < end)
     {
         int mid = (star + end) / 2;

         HANDLE hHandle_1 = 0;
         HANDLE hHandle_2 = 0;
         unsigned threadID_1;
         unsigned threadID_2;
         QckSortThread* p1 = new QckSortThread;
         p1->i = star;
         p1->j = mid;
         p1->p = num;
         p1->f = f1;
         if (f1 < level)
         {

             hHandle_1 = (HANDLE)_beginthreadex(NULL, 0, Sort_5, (void*)p1, 0, &threadID_1);
         }
         else
             hHandle_1 = (HANDLE)_beginthreadex(NULL, 0, Sort_1, (void*)p1, 0, &threadID_1);

         QckSortThread* p2 = new QckSortThread;
         p2->i = mid + 1;
         p2->j = end;
         p2->p = num;
         p2->f = f1;
         if (f1 < level)
         {
             hHandle_2 = (HANDLE)_beginthreadex(NULL, 0, Sort_5, (void*)p2, 0, &threadID_2);
         }
         else
             hHandle_2 = (HANDLE)_beginthreadex(NULL, 0, Sort_1, (void*)p2, 0, &threadID_2);
         delete[]p;
         WaitForSingleObject(hHandle_1, INFINITE);
         WaitForSingleObject(hHandle_2, INFINITE);
         CloseHandle(hHandle_1);
         CloseHandle(hHandle_2);
         Merge(num, star, mid, end);
     }
     return 0;
 }

 
 void readfile()
 {
     FILE* file;
     fopen_s(&file, ".\Source.txt", "r");
     for (int i = 0; i < w; i++)
        fscanf_s(file, "%d", &NUM[i]);
         
 }

 int main()
 {
     clock_t start, end;//计时
     HANDLE hThread;
     unsigned threadID;


     QckSortThread* date = new QckSortThread;
     WriteRand();
     int* num = NUM;
     while (level <= level_2)
         /*
            多线程快排
         */  
     {
         readfile();

         QckSortThread* data = new QckSortThread;
         data->p = num;
         data->i = 0;
         data->j = w - 1;
         data->f = 0;
         start = clock();
         hThread = (HANDLE)_beginthreadex(NULL, 0, Sort_1, (void*)data, 0, &threadID);
         WaitForSingleObject(hThread, INFINITE);
         CloseHandle(hThread);
         end = clock();
         double time = (double)(end - start) / CLOCKS_PER_SEC;
         time1[0] = time;
         cout << "\n多线程快速排序time: " << time;
         //Write("./多线程快排.txt");

         /*
         * 单线程快排
         */
         readfile();
         start = clock();
         quick_sort(num, 0, w - 1);
         end = clock();
         time = (double)(end - start) / CLOCKS_PER_SEC;
         time1[1] = time;
         cout << "\n单线程快速排序time: " << time;
         //Write("./单线程快排.txt");

         /*
            多线程归并排序
         */
         readfile();
         data = new QckSortThread;
         data->p = num;
         data->i = 0;
         data->j = w - 1;
         data->f = 0;

         start = clock();
         hThread = (HANDLE)_beginthreadex(NULL, 0, Sort_3, (void*)data, 0, &threadID);
         WaitForSingleObject(hThread, INFINITE);
         CloseHandle(hThread);
         end = clock();
         time = (double)(end - start) / CLOCKS_PER_SEC;
         time1[2] = time;
         cout << "\n多线程归并排序time:  " << time;
         //Write("./多线程归并.txt");

         /*
         * 单线程归并排序
         */
         readfile();
         start = clock();
         MSort(num, 0, w - 1);
         end = clock();
         time = (double)(end - start) / CLOCKS_PER_SEC;
         time1[3] = time;
         cout << "\n单线程归并排序time: " << time;
         //Write("./单线程归并.txt");

         /*多线程快速+归并*/
         readfile();
         data = new QckSortThread;
         data->p = num;
         data->i = 0;
         data->j = w - 1;
         data->f = 0;

         start = clock();
         hThread = (HANDLE)_beginthreadex(NULL, 0, Sort_2, (void*)data, 0, &threadID);
         WaitForSingleObject(hThread, INFINITE);
         CloseHandle(hThread);
         end = clock();
         time = (double)(end - start) / CLOCKS_PER_SEC;
         time1[4] = time;
         cout << "\n多线程快速+归并排序1.0time:  " << time;
         //Write("./多线程线程快排+归并1.0.txt");
         /*多线程快速+归并2.0*/
         readfile();
         data = new QckSortThread;
         data->p = num;
         data->i = 0;
         data->j = w - 1;
         data->f = 0;

         start = clock();
         hThread= (HANDLE)_beginthreadex(NULL, 0, Sort_5, (void*)data, 0, &threadID);
         WaitForSingleObject(hThread, INFINITE);
         CloseHandle(hThread);
         end = clock();
         time = (double)(end - start) / CLOCKS_PER_SEC;
         time1[5] = time;
         cout << "\n多线程快速+归并排序2.0time:  " << time << endl;

        // Write("./多线程快排+归并2.0.txt");
         cout << "快排加速比:" << (time1[1] - time1[0]) / time1[1] << endl;
         cout << "归并加速比:" << (time1[3] - time1[2]) / time1[3] << endl;
         cout << "快+归1.0加速比" << (time1[1] - time1[4]) / time1[1] << endl;
         cout << "快+归2.0加速比" << (time1[1] - time1[5]) / time1[1] << endl;
         cout << level << endl;
         level++;
     }
     return 0;
 }




总结

多线程排序的难点还是对原始数据的分块排序和最后的归并,这里作者用了比较容易的两种经典排序算法进行多线程并行排序的改进,属于捡漏的方法,毕竟这两种算法自带了数据的分块和合并,不需要另外去写如何分块和归并的策略。没写过多少文章,如有错误还请各位大佬指出。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值