并行计算实验快速排序的并行算法
3.1实验目的与要求
1、熟悉快速排序的串行算法
2、熟悉快速排序的并行算法
3、实现快速排序的并行算法
3.2 实验环境及软件
单台或联网的多台PC机,Linux操作系统,MPI系统。
3.3实验内容
1、快速排序的基本思想
2、单处理机上快速排序算法
3、快速排序算法的性能
4、快速排序算法并行化
5、描述了使用2m个处理器完成对n个输入数据排序的并行算法。
6、在最优的情况下并行算法形成一个高度为log n的排序树
7、完成快速排序的并行实现的流程图
8、完成快速排序的并行算法的实现
3.4实验步骤
3.4.1、快速排序(Quick Sort)是一种最基本的排序算法,它的基本思想是:在当前无序区R[1,n]中取一个记录作为比较的“基准”(一般取第一个、最后一个或中间位置的元素),用此基准将当前的无序区R[1,n]划分成左右两个无序的子区R[1,i-1]和R[i,n](1≤i≤n),且左边的无序子区中记录的所有关键字均小于等于基准的关键字,右边的无序子区中记录的所有关键字均大于等于基准的关键字;当R[1,i-1]和R[i,n]非空时,分别对它们重复上述的划分过程,直到所有的无序子区中的记录均排好序为止。
3.4.2、单处理机上快速排序算法
输入:无序数组data[1,n]
输出:有序数组data[1,n]
Begin
call procedure quicksort(data,1,n)
End
procedure quicksort(data,i,j)
Begin
(1) if (i (1.1)r = partition(data,i,j) (1.2)quicksort(data,i,r-1); (1.3)quicksort(data,r+1,j); end if End procedure partition(data,k,l) Begin (1) pivo=data[l] (2) i=k-1 (3) for j=k to l-1 do if data[j]≤pivo then i=i+1 exchange data[i] and data[j] end if end for (4) exchange data[i+1] and data[l] (5) return i+1 End 3.4.3、快速排序算法的性能主要决定于输入数组的划分是否均衡,而这与基准元素的选择密切相关。在最坏的情况下,划分的结果是一边有n-1个元素,而另一边有0个元素(除去被选中的基准元素)。如果每次递归排序中的划分都产生这种极度的不平衡,那么整个算法的复杂度将是Θ(n2)。在最好的情况下,每次划分都使得输入数组平均分为两半,那么算法的复杂度为O(n log n)。在一般的情况下该算法仍能保持O(n log n)的复杂度,只不过其具有更高的常数因子。 3.4.4、快速排序算法并行化的一个简单思想是,对每次划分过后所得到的两个序列分别使用两个处理器完成递归排序。例如对一个长为n的序列,首先划分得到两个长为n/2的序列,将其交给两个处理器分别处理;而后进一步划分得到四个长为n/4的序列,再分别交给四个处理器处理;如此递归下去最终得到排序好的序列。当然这里举的是理想的划分情况,如果划分步骤不能达到平均分配的目的,那么排序的效率会相对较差。 3.4.5、描述了使用2m个处理器完成对n个输入数据排序的并行算法。 快速排序并行算法 输入:无序数组data[1,n],使用的处理器个数2m 输出:有序数组data[1,n] Begin para_quicksort(data,1,n,m,0) End procedure para_quicksort(data,i,j,m,id) Begin (1) if (j-i)≤k or m=0 then (1.1) Pid call quicksort(data,i,j) else (1.2) Pid: r=patrition(data,i,j) (1.3) P id send data[r+1,m-1] to Pid+2m-1 (1.4) para_quicksort(data,i,r-1,m-1,id) (1.5) para_quicksort(data,r+1,j,m-1,id+2m-1) (1.6) Pid+2m-1 send data[r+1,m-1] back to Pid end if End 3.4.6、在最优的情况下该并行算法形成一个高度为log n的排序树,其计算复杂度为O(n),通信复杂度也为O(n)。同串行算法一样,在最坏情况下其计算复杂度降为O(n2)。正常情况下该算法的计算复杂度也为O(n),只不过具有更高的常数因子。 3.4.7、完成快速排序的并行实现的流程图 3.4.8、完成快速排序的并行算法的实现 #include #include #define TRUE 1 /* * 函数名: main * 功能:实现快速排序的主程序 * 输入:argc为命令行参数个数; * argv为每个命令行参数组成的字符串数组。 * 输出:返回0代表程序正常结束 */ int GetDataSize(); para_QuickSort(int *data,int start,int end,int m,int id,int MyID); QuickSort(int *data,int start,int end); int Partition(int *data,int start,int end); int exp2(int num); int log2(int num); ErrMsg(char *msg); main(int argc,char *argv[]) { int DataSize; int *data; /*MyID表示进程标志符;SumID表示组内进程数*/ int MyID, SumID; int i, j; int m, r; MPI_Status status; /*启动MPI计算*/ MPI_Init(&argc,&argv); /*MPI_COMM_WORLD是通信子*/ /*确定自己的进程标志符MyID*/ MPI_Comm_rank(MPI_COMM_WORLD,&MyID); /*组内进程数是SumID*/ MPI_Comm_size(MPI_COMM_WORLD,&SumID); /*根处理机(MyID=0)获取必要信息,并分配各处理机进行工作*/ if(MyID==0) { /*获取待排序数组的长度*/ DataSize=GetDataSize(); data=(int *)malloc(DataSize*sizeof(int)); /*内存分配错误*/ if(data==0) ErrMsg("Malloc memory error!"); /*动态生成待排序序列*/ srand(396); for(i=0;i { data[i]=(int)rand(); printf("%10d",data[i]); } printf("\n"); } m=log2(SumID); //调用函数:求以2为底的SumID的对数 /* 从根处理器将数据序列广播到其他处理器*/ /*{"1"表示传送的输入缓冲中的元素的个数, */ /* "MPI_INT"表示输入元素的类型, */ /* "0"表示root processor的ID } */ MPI_Bcast(&DataSize,1,MPI_INT,0,MPI_COMM_WORLD); /*ID号为0的处理器调度执行排序*/ para_QuickSort(data,0,DataSize-1,m,0,MyID); /*ID号为0的处理器打印排序完的有序序列*/ if(MyID==0) { printf("实际应用处理器数:%d\n ",exp2(m-1)); for(i=0;i { printf("%10d",data[i]); } printf("\n"); } MPI_Finalize(); //结束计算 return 0; }/* * 函数名: para_QuickSort * 功能:并行快速排序,对起止位置为start和end的序列,使用2的m次幂个处理器进行排序* 输入:无序数组data[1,n],使用的处理器个数2^m * 输出:有序数组data[1,n] */ para_QuickSort(int *data,int start,int end,int m,int id,int MyID) { int i, j; int r; int MyLength; int *tmp; MPI_Status status; MyLength=-1; /*如果可供选择的处理器只有一个,那么由处理器id调用串行排序,对应于算法步骤(1.1)*/ /*(1.1) Pid call quicksort(data,i,j) */ if(m==0) { if(MyID==id) QuickSort(data,start,end); return; } /*由第id号处理器划分数据,并将后一部分数据发送到处理器id+exp2(m-1),对应于算法步骤(1.2,1.3)*/ /*(1.2) Pid: r=patrition(data,i,j)*/ if(MyID==id) { /*将当前的无序区R[1,n]划分成左右两个无序的子区R[1,i-1]和R[i,n](1≤i≤n)*/ r=Partition(data,start,end); MyLength=end-r; /*(1.3) Pid send data[r+1,m-1] to P(id+2m-1) */ /* {MyLength表示发送缓冲区地址;*/ /* 发送元素数目为1; */ /* MyID是消息标签 } */ /* 在下面添加一条语句发送长度 */ MPI_Send(&MyLength,1,MPI_INT,id+exp2(m-1),MyID,MPI_COMM_WORLD); /*若缓冲区不空,则第id+2m-1号处理器取数据的首址是data[r+1]*/ if(MyLength!=0) /* 在下面添加一条语句 */ MPI_Send(data+r+1,MyLength ,MPI_INT,id+exp2(m-1),MyID,MPI_COMM_WORLD); } /*处理器id+exp2(m-1)接受处理器id发送的消息*/ if(MyID==id+exp2(m-1)) { /* 在下面添加一条语句 */ MPI_Recv(&MyLength,1,MPI_INT,id,id,MPI_COMM_WORLD,&status); if(MyLength!=0) { tmp=(int *)malloc(MyLength*sizeof(int)); if(tmp==0) ErrMsg("Malloc memory error!"); /* 在下面添加一条语句 */ MPI_Recv(tmp,MyLength,MPI_INT,id,id,MPI_COMM_WORLD,&status); } } /*递归调用并行排序,对应于算法步骤(1.4,1.5)*/ /*用2^m-1个处理器对start--(r-1)的数据进行递归排序*/ j=r-1-start; MPI_Bcast(&j,1,MPI_INT,id,MPI_COMM_WORLD); /*(1.4) para_quicksort(data,i,r-1,m-1,id)*/ if(j>0) /* 在下面添加一条语句 */ para_QuickSort(data,start,r-1,m-1,id,MyID); /*用2^m-1个处理器对(r+1)--end的数据进行递归排序*/ j=MyLength; MPI_Bcast(&j,1,MPI_INT,id,MPI_COMM_WORLD); /*(1.5) para_quicksort(data,r+1,j,m-1,id+2m-1)*/ if(j>0) /* 在下面添加一条语句 */ para_QuickSort(tmp,0,MyLength-1,m-1,id+exp2(m-1),MyID); /*将排序好的数据由处理器id+exp2(m-1)发回id号处理器,对应于算法步骤(1.6)*/ /*(1.6) P(id+2m-1) send data[r+1,m-1] back to Pid */ if((MyID==id+exp2(m-1)) && (MyLength!=0)) MPI_Send(tmp,MyLength,MPI_INT,id,id+exp2(m-1),MPI_COMM_WORLD); if((MyID==id) && (MyLength!=0)) MPI_Recv(data+r+1,MyLength,MPI_INT,id+exp2(m-1),id+exp2(m-1),MPI_COMM_WORLD,&status ); } /* * 函数名: QuickSort * 功能:对起止位置为start和end的数组序列,进行串行快速排序。 * 输入:无序数组data[1,n] * 返回:有序数组data[1,n] */ QuickSort(int *data,int start,int end) { int r; int i; if(start { r=Partition(data,start,end); QuickSort(data,start,r-1); QuickSort(data,r+1,end); } return 0; } /* * 函数名: Partition * 功能:对起止位置为start和end的数组序列,将其分成两个非空子序列,* 其中前一个子序列中的任意元素小于后个子序列的元素。 * 输入:无序数组data[1,n] * 返回: 两个非空子序列的分界下标 */ int Partition(int *data,int start,int end) { int pivo; int i, j; int tmp; pivo=data[end]; i=start-1; /*i(活动指针)*/ for(j=start;j if(data[j]<=pivo) { i++; /*i表示比pivo小的元素的个数*/ tmp=data[i]; data[i]=data[j]; data[j]=tmp; } tmp=data[i+1]; data[i+1]=data[end]; data[end]=tmp; /*以pivo为分界,data[i+1]=pivo*/ return i+1; } /* * 函数名: exp2 * 功能:求2的num次幂 * 输入:int型数据num * 返回: 2的num次幂 */ int exp2(int num) { int i; i=1; while(num>0) { num--; i=i*2; } return i; } /* * 函数名: log2 * 功能:求以2为底的num的对数 * 输入:int型数据num * 返回: 以2为底的num的对数 */ int log2(int num) { int i, j; i=1; j=2; while(j { j=j*2; i++; } if(j>num) i--; return i; } /* * 函数名: GetDataSize * 功能:读入待排序序列长度 */ int GetDataSize() { int i; while(TRUE) { printf("Input the Data Size :"); scanf("%d",&i); /*读出正确的i,返回;否则,继续要求输入*/ if((i>0) && (i<=65535)) break; ErrMsg("Wrong Data Size, must between [1..65535]"); } return i; } /*输出错误信息*/ ErrMsg(char *msg) { printf("Error: %s \n",msg); } 3.5 实验结果 3.6实验总结 通过这次实验,我熟悉快速排序的串行算法和并行算法,并了解了实现快速排序的并行流程图。但是在实际的操作过程中也遇到了不少问题。最后是在同学的帮助下完成的。 一、枚举排序算法说明: 枚举排序(Enumeration Sort)是一种最为简单的排序算法,通常也被叫做秩排序(Rank Sort)。 该算法的基本思想是:对每一个要排序的元素统计小于它的所有元素的个数,从而得到该元素在整个序列中的位置。其时间复杂度为o(n^2)。其伪代码为: 输入为:a[1], a[2] , ... , a[n] 输出为:b[1], b[2] , ..., b[n] for i =1 to n do 1)k =1 2)for j = 1 to n do if a > a[j] then k= k + 1 endif endfor 3)b[k] = a endfor 算法思想很简单,现将其主要代码总结如下: 1、数组自动生成,随机生成长度为n的数组: 1. 1: void array_builder(int *a, int n) 2. 3. 2: { 4. 5. 3: int i; 6. 7. 4: 8. 9. 5: srand((int)time(0)); 11. 6: 12. 13. 7: for(i = 0; i < n; i++) 14. 15. 8: a = (int)rand() % 100; 16. 17. 9: 18. 19. 10: return; 20. 21. 11: } 复制代码 2、取得每个元素在数组中的秩,即统计每个元素按小于它的其他所有元素的个数: 1. 1: int *get_array_elem_position(int *init_array, int array_length, int start, int size){ 2. 3. 2: 4. 5. 3: int i,j,k; 7. 4: int *position; 8. 9. 5: 10. 11. 6: position = (int *)my_malloc(sizeof(int) * size); 12. 13. 7: for(i = start; i < start+size; i++){ 14. 15. 8: k = 0; 16. 17. 9: for(j = 0; j < array_length; j++){ 18. 19. 10: if(init_array < init_array[j]) 20. 21. 11: k++; 22. 23. 12: if((init_array == init_array[j]) && i >j) 24. 25. 13: k++; 26. 27. 14: } 29. 15: 30. 31. 16: position[i-start] = k; 32. 33. 17: } 34. 35. 18: 36. 37. 19: return position; 38. 39. 20: } 复制代码 其中my_malloc函数的作用为动态分配大小为size的空间,如分配失败,则终止程序: 1. 1: void *my_malloc(int malloc_size){ 2. 3. 2: void *buffer; 4. 5. 3: 6. 7. 4: if((buffer = (void *)malloc((size_t)malloc_size)) == NULL){ 8. 9. 5: printf("malloc failure"); 10. 11. 6: exit(EXIT_FAILURE); 12. 13. 7: } 14. 15. 8: 16. 17. 9: return buffer; 18. 19. 10: } 复制代码 3、算法主函数: 1. 1: void serial(){ 2. 3. 2: 4. 5. 3: int i; 7. 4: int array_length = ARRAY_LENGTH; 8. 9. 5: 10. 11. 6: int *init_array; 12. 13. 7: int *sort_array; 14. 15. 8: int *position; 16. 17. 9: 18. 19. 10: // array_length = get_array_length(4); 20. 21. 11: 22. 23. 12: sort_array = (int *)my_malloc(sizeof(int) * array_length); 24. 25. 13: init_array = (int *)my_malloc(sizeof(int) * array_length); 27. 14: 28. 29. 15: array_builder(init_array, array_length); 30. 31. 16: 32. 33. 17: position = get_array_elem_position(init_array, array_length, 0, array_length); 34. 35. 18: 36. 37. 19: for(i = 0; i < array_length; i++) 38. 39. 20: sort_array[position] = init_array; 40. 41. 21: 42. 43. 22: printf("串行实行结果:\n"); 44. 45. 23: init_sort_array_print(init_array, sort_array, array_length); 47. 24: } 并行计算 实 验 报 告 学院名称计算机科学与技术学院专业计算机科学与技术 学生姓名 学号 年班级 2016年5 月20 日 一、实验内容 本次试验的主要内容为采用多线程的方法计算pi的值,熟悉linux下pthread 形式的多线程编程,对实验结果进行统计并分析以及加速比曲线分析,从而对并行计算有初步了解。 二、实验原理 本次实验利用中值积分定理计算pi的值 图1 中值定理计算pi 其中公式可以变换如下: 图2 积分计算pi公式的变形 当N足够大时,可以足够逼近pi,多线程的计算方法主要通过将for循环的计算过程分到几个线程中去,每次计算都要更新sum的值,为避免一个线程更新sum 值后,另一个线程仍读到旧的值,所以每个线程计算自己的部分,最后相加。三、程序流程图 程序主体部分流程图如下: 多线程执行函数流程图如下: 四、实验结果及分析 令线程数分别为1、2、5、10、20、30、40、50和100,并且对于每次实验重复十次求平均值。结果如下: 图5 时间随线程的变化 实验加速比曲线的计算公式类似于 结果如下: 图5 加速比曲线 实验结果与预期类似,当线程总数较少时,线程数的增多会对程序计算速度带来明显的提升,当线程总数增大到足够大时,由于物理节点的核心数是有限的,因此会给cpu带来较多的调度,线程的切换和最后结果的汇总带来的时间开销较大,所以线程数较大时,增加线程数不会带来明显的速度提升,甚至可能下降。 五、实验总结 本次试验的主要内容是多线程计算pi的实现,通过这次实验,我对并行计算有了进一步的理解。上学期的操作系统课程中,已经做过相似的题目,因此程序主体部分相似。不同的地方在于,首先本程序按照老师要求应在命令行提供参数,而非将数值写定在程序里,其次是程序不是在自己的电脑上运行,而是通过ssh和批处理脚本等登录到远程服务器提交任务执行。 在运行方面,因为对批处理任务不够熟悉,出现了提交任务无结果的情况,原因在于windows系统要采用换行的方式来表明结束。在实验过程中也遇到了其他问题,大多还是来自于经验的缺乏。 在分析实验结果方面,因为自己是第一次分析多线程程序的加速比,因此比较生疏,参考网上资料和ppt后分析得出结果。 从自己遇到的问题来看,自己对批处理的理解和认识还比较有限,经过本次实验,我对并行计算的理解有了进一步的提高,也意识到了自己存在的一些问题。 六、程序代码及部署 程序源代码见cpp文件 部署说明: 使用gcc编译即可,编译时加上-pthread参数,运行时任务提交到服务器上。 编译命令如下: gcc -pthread PI_3013216011.cpp -o pi pbs脚本(runPI.pbs)如下: #!/bin/bash #PBS -N pi #PBS -l nodes=1:ppn=8 #PBS -q AM016_queue #PBS -j oe cd $PBS_O_WORKDIR for ((i=1;i<=10;i++)) do ./pi num_threads N >> runPI.log 堆排序——C#实现 一算法描述 堆排序(Heap Sort)是利用一种被称作二叉堆的数据结构进行排序的排序算法。 二叉堆在内部维护一个数组,可被看成一棵近似的完全二叉树,树上每个节点对应数组中的一个元素。除最底层外,该树是满的。 二叉堆中,有两个与所维护数组相关的属性。Length表示数组的元素个数,而HeapSize则表示二叉堆中所维护的数组中的元素的个数(并不是数组中的所有元素都一定是二叉堆的有效元素)。因此,根据上述定义有: 0 = HeapSize = Length。 二叉堆可分为最大堆和最小堆两种类型。在最大堆中,二叉树上所有的节点都不大于其父节点,即 A[Parent(i)] = A[i]。最小堆正好相反:A[Parent(i)] = A[i]。 为维护一个二叉堆是最大(小)堆,我们调用一个叫做MaxHeapify (MinHeapify)的过程。以MaxHeapify,在调用MaxHeapify时,先假定根节点为Left(i)和Right(i)的二叉树都是最大堆,如果A[i]小于其子节点中元素,则交换A[i]和其子节点中的较大的元素。但这样一来,以被交换的子节点为根元素的二叉堆有可能又不满足最大堆性质,此时则递归调用MaxHeapify方法,直到所有的子级二叉堆都满足最大堆性质。如下图所示: 因为在调用MaxHeapify(MinHeapify)方法使根节点为A[i]的 二叉堆满足最大(小)堆性质时我们有其左右子堆均已满足最大(小)堆性质这个假设,所以如果我们在将一个待排序的数组构造成最大(小)堆时,需要自底向上地调用 MaxHeapify(MinHeapify)方法。 在利用最大堆进行排序时,我们先将待排序数组构造成一个最大堆,此时A[0](根节点)则为数组中的最大元素,将A[0]与A[n - 1]交换,则把A[0]放到了最终正确的排序位置。然后通过将HeapSize 减去1,将(交换后的)最后一个元素从堆中去掉。然后通过MaxHeapify方法将余下的堆改造成最大堆,然后重复以上的交换。重复这一动作,直到堆中元素只有2个。则最终得到的数组为按照升序排列的数组。 二算法实现 1 注意到在C#中数组的起始下标为0,因此,计算一个给定下标的节点的父节点和左右子节点时应该特别小心。 private static int Parrent(int i) return (i - 1) - 2; private static int Left(int i) return 2 * i + 1; private static int Right(int i) return 2 * i + 2; 2 算法的核心部分是MaxHeapify(MinHeapify)方法,根据算法描述中的说明,一下代码分别实现了对整数数组的最大堆化和最小堆化方法,以及一个泛型版本。 《计算方法》上机实验报告 班级:XXXXXX 小组成员:XXXXXXX XXXXXXX XXXXXXX XXXXXXX 任课教师:XXX 二〇一八年五月二十五日 前言 通过进行多次的上机实验,我们结合课本上的内容以及老师对我们的指导,能够较为熟练地掌握Newton 迭代法、Jacobi 迭代法、Gauss-Seidel 迭代法、Newton 插值法、Lagrange 插值法和Gauss 求积公式等六种算法的原理和使用方法,并参考课本例题进行了MATLAB 程序的编写。 以下为本次上机实验报告,按照实验内容共分为六部分。 实验一: 一、实验名称及题目: Newton 迭代法 例2.7(P38):应用Newton 迭代法求 在 附近的数值解 ,并使其满足 . 二、解题思路: 设'x 是0)(=x f 的根,选取0x 作为'x 初始近似值,过点())(,00x f x 做曲线)(x f y =的切线L ,L 的方程为))((')(000x x x f x f y -+=,求出L 与x 轴交点的横坐标) (') (0001x f x f x x - =,称1x 为'x 的一次近似值,过点))(,(11x f x 做曲线)(x f y =的切线,求该切线与x 轴的横坐标) (') (1112x f x f x x - =称2x 为'x 的二次近似值,重复以上过程,得'x 的近似值序列{}n x ,把 ) (') (1n n n n x f x f x x - =+称为'x 的1+n 次近似值,这种求解方法就是牛顿迭代法。 三、Matlab 程序代码: function newton_iteration(x0,tol) syms z %定义自变量 format long %定义精度 f=z*z*z-z-1; f1=diff(f);%求导 y=subs(f,z,x0); y1=subs(f1,z,x0);%向函数中代值 x1=x0-y/y1; k=1; while abs(x1-x0)>=tol x0=x1; y=subs(f,z,x0); y1=subs(f1,z,x0); x1=x0-y/y1;k=k+1; end x=double(x1) K 四、运行结果: 实验二: 数据结构课程设计设计说明书 内部堆排序算法的实现 学生姓名金少伟 学号1121024029 班级信管1101 成绩 指导教师曹阳 数学与计算机科学学院 2013年3月15日 课程设计任务书 2012—2013学年第二学期 课程设计名称:数据结构课程设计 课程设计题目:内部堆排序算法的实现 完成期限:自2013年3 月4日至2013年3 月15 日共 2 周 设计内容: 堆排序(heap sort)是直接选择排序法的改进,排序时,需要一个记录大小的辅助空间。n个关键字序列K1,K2,…,Kn称为堆,当且仅当该序列满足如下性质(简称为堆性质):ki≤K2i且ki≤K2i+1 或(2)Ki≥K2i且ki≥K2i+1(1≤i≤ n) 若将此序列所存储的向量R[1..n]看做是一棵完全二叉树的存储结构,则堆实质上是满足如下性质的完全二叉树:树中任一非叶结点的关键字均不大于(或不小于)其左右孩子(若存在)结点的关键字。(即如果按照线性存储该树,可得到一个不下降序列或不上升序列)。 本课程设计中主要完成以下内容: 1.设计堆排序算法并实现该算法。 2.对堆排序的时间复杂度及空间复杂度进行计算与探讨。 3.寻找改进堆排序的方法。 基本要求如下: 1.程序设计界面友好; 2.设计思想阐述清晰; 3.算法流程图正确; 4.软件测试方案合理、有效。指导教师:曹阳教研室负责人:申静 课程设计评阅 摘要 堆排序是直接选择排序法的改进。本课设以VC++6.0作为开发环境,C语言作为编程语言,编程实现了堆排序算法。程序运行正确,操作简单,易于为用户接受。 关键词:堆排序;C语言;时间复杂度 (此文档为word格式,下载后您可任意编辑修改!) 多核编程与并行计算实验报告 姓名: 日期:2014年 4月20日 实验一 // exa1.cpp : Defines the entry point for the console application. // #include"stdafx.h" #include 实验二 // exa2.cpp : Defines the entry point for the console application. // #include"stdafx.h" #include import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Random; public class GuiBing { public static void main(String[] args) throws Exception { int datalength=1000000; GuiBing gui=new GuiBing(); int[] array1=gui.createArray(datalength); int[] array2=gui.createArray(datalength); Thread.sleep(20000); long startTime = System.nanoTime();//纳秒精度 long begin_freeMemory=Runtime.getRuntime().freeMemory(); int[] final_array=gui.guibing(array1,array2); boolean result=gui.testResult(final_array); long end_freeMemory=Runtime.getRuntime().freeMemory(); System.out.println("result===="+result); long estimatedTime = System.nanoTime() - startTime; System.out.println("elapsed time(纳秒精 度):"+estimatedTime/100000000.0); System.out.println("allocated memory:"+(begin_freeMemory-end_freeMemory)/1000.0+" KB"); Thread.sleep(20000); } /** * 显示数组的内容 * @param array */ private static void dispalyData(int[] array) { for(int i=0;i 堆排序算法的基本思想及算法实现示例 堆排序 1、堆排序定义 n个关键字序列Kl,K2,…,Kn称为堆,当且仅当该序列满足如下性质(简称为堆性质): (1) ki≤K2i且ki≤K2i+1 或(2)Ki≥K2i且ki≥K2i+1(1≤i≤ ) 若将此序列所存储的向量R[1..n]看做是一棵完全二叉树的存储结构,则堆实质上是满足如下性质的完全二叉树:树中任一非叶结点的关键字均不大于(或不小于)其左右孩子(若存在)结点的关键字。 【例】关键字序列(10,15,56,25,30,70)和(70,56,30,25,15,10)分别满足堆性质(1)和(2),故它们均是堆,其对应的完全二叉树分别如小根堆示例和大根堆示例所示。 2、大根堆和小根堆 根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最小者的堆称为小根堆。 根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最大者,称为大根堆。 注意: ①堆中任一子树亦是堆。 ②以上讨论的堆实际上是二叉堆(Binary Heap),类似地可定义k叉堆。 3、堆排序特点 堆排序(HeapSort)是一树形选择排序。 堆排序的特点是:在排序过程中,将R[l..n]看成是一棵完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子结点之间的内在关系【参见二叉树的顺序存储结构】,在当前无序区中选择关键字最大(或最小)的记录。 4、堆排序与直接插入排序的区别 直接选择排序中,为了从R[1..n]中选出关键字最小的记录,必须进行n-1次比较,然后在R[2..n]中选出关键字最小的记录,又需要做n-2次比较。事实上,后面的n-2次比较中,有许多比较可能在前面的n-1次比较中已经做过,但由于前一趟排序时未保留这些比较结果,所以后一趟排序时又重复执行了这些比较操作。 堆排序可通过树形结构保存部分比较结果,可减少比较次数。5、堆排序 堆排序利用了大根堆(或小根堆)堆顶记录的关键字最大(或最小)这一特征,使得在当前无序区中选取最大(或最小)关键字的记录变得简单。 (1)用大根堆排序的基本思想 并行计算上机实验报告题目:多线程计算Pi值 学生姓名 学院名称计算机学院 专业计算机科学与技术时间 一. 实验目的 1、掌握集群任务提交方式; 2、掌握多线程编程。 二.实验内容 1、通过下图中的近似公式,使用多线程编程实现pi的计算; 2、通过控制变量N的数值以及线程的数量,观察程序的执行效率。 三.实现方法 1. 下载配置SSH客户端 2. 用多线程编写pi代码 3. 通过文件传输界面,将文件上传到集群上 4.将命令行目录切换至data,对.c文件进行编译 5.编写PBS脚本,提交作业 6.实验代码如下: #include #include 堆排序 堆排序是利用堆的性质进行的一种选择排序,下面先讨论一下堆。 1.堆 堆实际上是一棵完全二叉树,其任何一非叶节点满足性质: Key[i]<=key[2i+1]&&Key[i]<=key[2i+2]或者Key[i]>=Key[2i+1]&&key>=key[2i+2] 即任何一非叶节点的关键字不大于或者不小于其左右孩子节点的关键字。 堆分为大顶堆和小顶堆,满足Key[i]>=Key[2i+1]&&key>=key[2i+2]称为大顶堆,满足Key[i]<= key[2i+1]&&Key[i]<=key[2i+2]称为小顶堆。由上述性质可知大顶堆的堆顶的关键字肯定是所有关键字中最大的,小顶堆的堆顶的关键字是所有关键字中最小的。 2.堆排序的思想 利用大顶堆(小顶堆)堆顶记录的是最大关键字(最小关键字)这一特性,使得每次从无序中选择最大记录(最小记录)变得简单。 其基本思想为(大顶堆): 1)将初始待排序关键字序列(R1,R2....Rn)构建成大顶堆,此堆为初始的无序区; 2)将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,......Rn-1)和新的有序区(Rn),且满足R[1,2...n-1]<=R[n]; 3)由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,......Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2....Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。 操作过程如下: 1)初始化堆:将R[1..n]构造为堆; 2)将当前无序区的堆顶元素R[1]同该区间的最后一个记录交换,然后将新的无序区调整为新的堆。 因此对于堆排序,最重要的两个操作就是构造初始堆和调整堆,其实构造初始堆事实上也是调整堆的过程,只不过构造初始堆是对所有的非叶节点都进行调整。 下面举例说明: 给定一个整形数组a[]={16,7,3,20,17,8},对其进行堆排序。 首先根据该数组元素构建一个完全二叉树,得到 算法分析与设计实验报告第四次附加实验 while (a[--j]>x); if (i>=j) { break; } Swap(a[i],a[j]); } a[p] = a[j]; //将基准元素放在合适的位置 a[j] = x; return j; } //通过RandomizedPartition函数来产生随机的划分 template vclass Type> int RandomizedPartition(Type a[], int p, int r) { int i = Random(p,r); Swap(a[i],a[p]); return Partition(a,p,r); } 较小个数排序序列的结果: 测试结果 较大个数排序序列的结果: 实验心得 快速排序在之前的数据结构中也是学过的,在几大排序算法中,快速排序和归并排序尤其是 重中之重,之前的快速排序都是给定确定的轴值,所以存在一些极端的情况使得时间复杂度 很高,排序的效果并不是很好,现在学习的一种利用随机化的快速排序算法,通过随机的确 定轴值,从而可以期望划分是较对称 的,减少了出现极端情况的次数,使得排序的效率挺高了很多, 化算法想呼应,而且关键的是对于随机生成函数,通过这一次的 学习终于弄明白是怎么回事了,不错。 与后面的随机实 验和自己的 实验得分助教签名 附录: 完整代码(分治法) //随机后标记元素后的快速排序 #i nclude 华中科技大学 课程名称并行处理 实验名称矩阵乘法的实现及加速比分析考生姓名李佩佩 考生学号 M201372734 系、年级计算机软件与理论2013级类别硕士研究生 考试日期 2014年1月3日 一. 实验目的 1) 学会如何使用集群 2) 掌握怎么用并行或分布式的方式编程 3) 掌握如何以并行的角度分析一个特定的问题 二. 实验环境 1) 硬件环境:4核CPU、2GB内存计算机; 2) 软件环境:Windows XP、MPICH2、VS2010、Xmanager Enterprise3; 3) 集群登录方式:通过远程桌面连接211.69.198.2,用户名:pppusr,密码:AE2Q3P0。 三. 实验内容 1. 实验代码 编写四个.c文件,分别为DenseMulMatrixMPI.c、DenseMulMatrixSerial.c、SparseMulMatrixMPI.c和SparseMulMatrixSerial.c,用于比较并行和串行矩阵乘法的加速比,以及稀疏矩阵和稠密矩阵的加速比。这里需要说明一下,一开始的时候我是把串、并行放在一个程序中,那么就只有两个.c文件DenseMulMatrix.c 和SparseMulMatrix.c,把串行计算矩阵乘的部分放到了主进程中,即procsID=0的进程,但是结果发现执行完串行后,再执行并行就特别的慢。另外,对于稀疏矩阵的处理方面可能不太好,在生成稀疏矩阵的过程中非0元素位置的生成做到了随机化,但是在进行稀疏矩阵乘法时没有对矩阵压缩,所以跟稠密矩阵乘法在计算时间上没多大区别。 方阵A和B的初始值是利用rand()和srand()函数随机生成的。根据稀疏矩阵和稠密矩阵的定义,对于稀疏矩阵和稠密矩阵的初始化方法InitMatrix(int *M,int *N,int len)会有所不同。这里需要说明一下,一开始对于矩阵A和B的初始化是两次调用InitMatrix(int *M ,int len),生成A和B矩阵,但是随后我发现,由于两次调用方法InitMatrix的时间间隔非常短,又由于srand()函数的特点,导致生成的矩阵A和B完全一样;然后,我就在两次调用之间加入了语句“Sleep(1000);”,加入头文件“#include 归并排序算法实现(迭代和递归)\递归实现归并排序的原理如下: 递归分割: 递归到达底部后排序返回: 最终实现排序: #include } while(i 算法与数据结构程设计报告 一.设计题目: 堆排序的算法 二.运行环境: 1、硬件:计算机 2、软件:Microsoft Visual C++6.0 三.目的和意义: 目的:创建一个大堆,按从大到小顺序输出堆元素,实现堆排序。 意义:利用堆排序,即使在最坏情况下的时间复杂度也是O(nlog2n),相对于快速排序来说,时间复杂度小,这是堆排序的最大优点,可用于对若干元素进行排序,加快排序速度。 四.算法设计的基本思想: 堆排序算法设计基本思想: 堆排序利用了大根堆堆顶记录的关键字最大这一特征,使得在当前无序区中选取最大关键字的记录变得简单。先将初始文件R[1..n]建成一个大根堆,此堆为初始的无序区。再将关键字最大的记录R[1](即堆顶)和无序区的最后一个记录r[n]交换,由此得到新的无序区r[1..n-1]和有序区r[n],且满足r[1..n-1].keys≤r[n].key。由于交换后新的根R[1]可能违反堆性质,故应将当前无序区r[1..n-1]调整为堆。然后再次将r[1..n-1]中关键字最大的记录r[1]和该区间的最后一个记录R[n-1]交换,由此得到新的无序区r[1..n-2]和有序区r[n-1..n],且仍满足关系r[1..n-2].keys≤r[n-1..n].keys,同样要将r[1..n-2]调整为堆。直到无序区只有一个元素为止. . . 流程图3:打印数组函数print() 六.算法设计分析: 性能分析:堆排序的时间主要由建立初始堆和不断重复建堆这两部分的时间开销构成,它们均是通过调用Heapify实现的。其中:建立初始堆时,总共需进行的关键字比较次数≤ 4n =O(n) ;排序过程中进行n-1次重建堆所进行的总比较次数不超过下式:2*(└ log2(n-1) ┘+└ log2(n-2) ┘+…+ └ log22 ┘) < 2n └ log2n ┘=O(n log2n)因此堆排序总的时间复杂度是 O(n+ n log2n)= O(n log2n)。堆排序在最坏情况下的时间复杂度也是O(nlog2n),相对于快速排序来说,这是堆排序的最大优点。另外,堆排序仅需一个记录大小供交换用的辅助空间。由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录较少的文件,但对n较大的文件还是很有效的。堆排序是就地排序,辅助空间为O(1),它是不稳定的排序方法。 堆的描述:堆排序(HeapSort)是一树形选择排序。堆是对基于数组的树的重要应用场合之一。它是节点间具有层次次序关系的完全二叉树。其中,父节点值大于或等于其孩子节点值的,叫“最大堆(maximum heap)”;父节点值小于或等于孩子节点值的,叫“最小堆(minimum heap)”.在最大堆中,根中的元素值最大;在最小堆中,根中的元素值最小。堆排序的特点是:在排序过程中,将R[l..n]看成是一棵完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子结点之间的内在关系,在当前无序区中选择关键字最大(或最小)的记录。 从算法描述来看,堆排序需要两个过程,一是建立堆,二是堆顶与堆的最后一个元素交换位置。所以堆排序有两个函数组成。一是建堆的渗透函数,二是反复调用渗透函数实现排序的函数。 堆的存储特点:在这里,讨论作为顺序表存储的堆。它是按某种次序将一系列数据以完全二叉树形式存放的一种表。它要求堆中的节点的值都大于或等于其孩子节点的值。 按照这种存储方式,可知第i个元素的左右儿子分别是第2i和第2i+1个元素,而它的父亲节点是第i/2个元素。由于父亲节点和儿子节点具有这样的顺序关系,所以可以方便地由父亲节点找到儿子节点,反之亦然。 可见,这种存储方式大大节省了存储空间,高效快速。 堆的相关操作:作为抽象表结构,堆允许增加和删除表项。插入过程不用假定新表项占有一个特定的位置而只需维持堆顺序。但是删除操作总是删去表中的最大项(根)。堆可以用于那些客户程序想直接访问最大(小)元素的场合。作为表,堆并不提供Find操作,而对表的直接访问是只读的。所有的堆处理算法都有责任更新树和维持堆顺序。 1.建堆:数组具有对应的树表示形式。一般情况下,树并不满足堆的条件。通过重新排列元素,可以建立一棵“堆化”的树。 2.插入一个元素:新元素被加入到表中,随后树被更新以恢复堆次序。例如,下面的步骤将15加入到表中。 3.删除一个元素:删除总是发生在根节点处。用表中的最后一个元素来填补空缺位置,结果树被更新以恢复堆条件。 在堆实现时我们是采用数组来存储堆的完全二叉树表示,并且用一种有效的算法来保证对堆的所有操作不破坏堆的性质。这种表示的主要问题在于数组的大小需要事先确定,这使得对堆的大小有了一个初始的限定。在堆中数据增长到 实验报告 (2015 / 2016 学年第二学期) 课程名称 实验名称分治法实现快速排序与两路合并排序 实验时间年月日指导单位计算机学院计算机科学与技术系 指导教师 学生姓名班级学号 学院(系) 专业 实验报告 三、实验原理及内容 实验原理: 分治法:即分而治之。将问题分解为规模较小,相互独立,类型相同的问题进行求解。对于无序数组的有序排序也就是按某种方式将序列分成两个或多个子序列,分别进行排序,再将已排序的子序列合并成一个有序序列。 实验内容: 两路合并排序算法的基本思想是:将待排序元素序列一分为二,得到两个长度基本相等的子序列,其过程类似于对半搜索;然后将子序列分别排序,如果子序列较长,还可以继续细分,知道子序列长度不超过1为止。 以上的实现由下列代码执行: void SortableList::MergeSort() { MergeSort(0,n-1); } void SortableList::MergeSort(int left,int right) { if (left 多核编程与并行计算实验报告 姓名: 日期:2014年 4月20日 实验一 // exa1.cpp : Defines the entry point for the console application. // #include"stdafx.h" #include 实验二 // exa2.cpp : Defines the entry point for the console application. // #include"stdafx.h" #include 1 绪论 快速排序(quicksort)是分治(divide and conquer)法的一个典型例子。快速排序(Quicksort)是对冒泡排序的一种改进。由C. A. R. Hoare在1962 年提出。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。 快速排序算法具有良好的平均性能,因此它在实际中常常是首选的排序算法。本次任务主要以快速排序算法实现对任意数字序列的排序,并解决书本P59页 2-26问题: O n n 试说明如何修改快速排序算法,使它在最坏情况下的计算时间为(log) 所选编程语言为C语言。 2 快速排序算法 2.1快速排序算法简介 快速排序算法是基于分治策略的排序算法。即对于输入的子数组a[p:r],按以下三个步骤进行排序。 (1)分解:以a[p]为基准元素将a[p:r]划分成3段a[p:q-1],a[q]和a[q+1:r],使a[p:q-1]中任何一个元素小于等于a[q],而a[q+1:r]中任何一个元素大于等于a[q]。下标q在划分过程中确定。 (2)递归求解:通过递归调用快速排序算法分别对a[p:q-1]和a[q+1:r]进行排序。 (3)合并:由于对a[p:q-1]和a[q+1:r]的排序是就地进行的,所以在a[p:q-1]和a[q+1:r]都已排好的序后,不需要执行任何计算,a[p:r]就已排好序。 2.2 图1 快速排序算法流程图 2.3快速排序算法的算法实现 第一趟处理整个待排序列,选取其中的一个记录,通常选取第一个记录,以该记录的关键字值为基准,通过一趟快速排序将待排序列分割成独立的两个部分,前一部分记录的关键字比基准记录的关键字小,后一部分记录的关键字比基准记录的关键字大,基准记录得到了它在整个序列中的最终位置并被存放好,这个过程称为一趟快速排序。第二趟即分别对分割成两部分的子序列再进行快速排序,这样两部分子序列中的基准记录也得到了最终在序列中的位置并被存放好,又分别分割出独立的两个子序列。这是一个递归的过程,不断进行下去,直至每个待排子序列中都只有一个记录是为止,此时整个待排序列已排好序,排序算法结束。 快速排序的过程: (1)初始化。取第一个记录作为基准,设置两个整型指针i,j,分别指向将要与基准记录进行比较的左侧记录位置和右侧记录位置。最开始从右侧比较,当发生交换操作后,再从左侧比较。 (2)用基准记录与右侧记录进行比较。即与指针j指向的记录进行比较,如果右侧记录的关键字值大,则继续与右侧前一个记录进行比较,即j减1后,再用基准元素与j所指向的记录比较,若右侧的记录小,则将基准记录与j所指向的记录进行交换。 (3)用基准记录与左侧记录进行比较。即与指针i指向的记录进行比较,如果左侧记录的关键字值小,则继续与左侧后一个记录进行比较,即i加1后,再用基准记录与i指向的记录比较,若左侧的记录大,则将基准记录与i指向的记录比较。 (4)右侧比较与左侧比较交替重复进行,直到指针i与j指向同一位置,即指向基准记录最终的位置。 可实现的快速排序算法如下: void QuickSort(int a[],int p,int r) { i f(p 深圳大学 实验报告 课程名称:并行计算 实验名称:矩阵乘法的OpenMP实现及性能分析姓名: 学号: 班级: 实验日期:2011年10月21日、11月4日 一. 实验目的 1) 用OpenMP 实现最基本的数值算法“矩阵乘法” 2) 掌握for 编译制导语句 3) 对并行程序进行简单的性能 二. 实验环境 1) 硬件环境:32核CPU 、32G 存计算机; 2) 软件环境:Linux 、Win2003、GCC 、MPICH 、VS2008; 4) Windows 登录方式:通过远程桌面连接192.168.150.197,用户名和初始密码都是自己的学号。 三. 实验容 1. 用OpenMP 编写两个n 阶的方阵a 和b 的相乘程序,结果存放在方阵c 中,其中乘法用for 编译制导语句实现并行化操作,并调节for 编译制导中schedule 的参数,使得执行时间最短,写出代码。 方阵a 和b 的初始值如下: ????????? ? ??????????-++++=12,...,2,1,..2,...,5,4,31,...,4,3,2,...,3,2,1n n n n n n n a ???????? ? ???????????= 1,...,1,1,1..1,...,1,1,11,...,1,1,11,..., 1,1,1b 输入: 方阵的阶n 、并行域的线程数 输出: c 中所有元素之和、程序的执行时间 提示: a,b,c 的元素定义为int 型,c 中所有元素之各定义为long long 型。 Windows 计时: 用并行计算1
堆 排 序 算 法
计算方法上机实验报告
内部堆排序算法的实现课程设计说明书
多核编程与并行计算实验报告 (1)
简单的归并排序算法例子
堆排序算法的基本思想及算法实现示例
并行计算第一次实验报告
堆排序算法分析(C语言版)
分治算法实验(用分治法实现快速排序算法)
并行处理实验报告:用MPI实现的矩阵乘法的加速比分析
归并排序算法实现 (迭代和递归)
堆排序
分治法实现快速排序与两路合并排序
多核编程与并行计算实验报告 (1)
快速排序算法(论文)
并行计算-实验二-矩阵乘法的OpenMP实现及性能分析