冒泡排序、快速排序、基数排序、计数排序算法性能分析

来源:互联网 发布:域名一定要备案吗 编辑:程序博客网 时间:2024/05/21 17:23
  1. 实验要求
    1) 排序n个元素,元素为随机生成的1到65535之间的整数,n的取值为: 2^2,2^5,2^8,2^11,2^14,2^17 ;
    2) 算法:冒泡排序,快速排序,基数排序,计数排序;
    3) 对结果进行性能分析。
  2. 实验环境
    1) 编译环境:Dev-C++ 5.9.2
    2) 机器内存:8G
    3) 时钟主频:2.2GHz
  3. 实验过程
    1) 实现getnums函数(input.cpp源码在文件夹input中),随机产生2^17个1~65535间的整数,将整数写入文件input_numbers.txt中;
    2) 实现冒泡排序BubbleSort.cpp,其中产生不同规模下冒泡排序的结果result_n.txt和运行时间time.txt;
    3) 实现快速排序QuickSort.cpp,其中产生不同规模下快速排序的结果result_n.txt和运行时间time.txt;
    4) 实现基数排序MergeSort.cpp,其中产生不同规模下基数排序的结果result_n.txt和运行时间time.txt;
    5) 实现计数排序QuickSort.cpp,其中产生不同规模下计数排序的结果result_n.txt和运行时间time.txt;
    6) 进行结果分析。
  4. 实验关键代码截图
    1) getnums函数实现思路:产生2^17个1~65535间的随机数字写入input_numbers.txt中,数字间以换行间隔。
void getnums(){    FILE *fp;    int i,slen,j;    int temp;    srand((unsigned)time(NULL));    fp = fopen("input_numbers.txt", "w");    for (i = 0; i < NUM; i++)    {           temp = rand()*rand() % 65535 + 1;        fprintf(fp,"%d",temp);              fputc('\n',fp);    }    fclose(fp); }

2) 记录运行时间的方法(级别us):

#include "windows.h"static LARGE_INTEGER Freq;static LARGE_INTEGER start;static LARGE_INTEGER end;static double dt;//用于计时void count_start(){    QueryPerformanceFrequency(&Freq);    QueryPerformanceCounter(&start);}double count_stop(){    QueryPerformanceCounter(&end);    dt = (end.QuadPart - start.QuadPart)/(double)(Freq.QuadPart);    return dt;}

3) 冒泡排序过程
a)从输入数据input_numbers.txt中读取前num(问题规模)个数字存入numtemp[num]中(先读取一行数字字符串,再用函数atoi()将读的字符串转成整型数)

fp = fopen("..\\\\input\\input_numbers.txt","r");rewind(fp);memset(numtemp,0,num*sizeof(int));for(j = 1; j <= num; j++){       memset(strtemp[j],0,7*sizeof(char));    fscanf(fp,"%s",strtemp[j]);     numtemp[j] = atoi(strtemp[j]);      }fclose(fp);

b) 实现冒泡排序的过程(swap实现交换两个数字的功能,起始用上述记录时间的方法来记录运行时间)

count_start();  //开始排序for(i = num-1; i > 1; i--){        for(j = 1; j <= i; j++)        {//每次将小的数往前调整            if(numtemp[j] > numtemp[j+1])                swap(&numtemp[j], &numtemp[j+1]);        }}dtime[k] = count_stop();//结束排序

swap:

void swap(int *a,int *b){    int temp = *a;    *a = *b;    *b = temp;}

c) 将排序结果进行文件result_n.txt中

fp1 = fopen(name[k],"w");for(i = 1; i <= num; i++){   fprintf(fp1,"%d",numtemp[i]);   fputc('\n',fp1);}fclose(fp1);

d) 运行BubbleSort.cpp

4) 快速排序算法的实现:
a) 从input_numbers.txt中读取数据(方法同上)
b) 快速排序过程:

count_start();//开始排序  QuickSortRecur(numtemp,1,num);dtime[k] = count_stop();//结束排序
void QuickSortRecur(int a[],int p, int r){//递归过程    int q;    if(p < r)    {        q = Partition(a, p, r);//分区        QuickSortRecur(a, p, q-1);//分区左边排序        QuickSortRecur(a, q+1, r);//分区右边进行排序    }}

c) Partition过程:

int Partition(int a[],int p,int r){//以a[r]为标记界限,比a[r]小的放在前面,比a[r]大的放在后面,返回分界点    int i,j,temp;    temp = a[r];    i = p - 1;    for(j = p; j <= r-1; j++)    {        if(a[j] < temp)        {            i++;            swap(&a[i], &a[j]);        }    }    swap(&a[i+1], &a[r]);    return i+1;}

d) 将运行结果写入result_n.txt中。

5) 计数排序算法的实现:
a) 从input_numbers.txt中读取数据(方法同上)
b) 计数排序过程(排序结果返回给result):

count_start();  //开始排序result = _CountingSort(numtemp,num);//结束排序dtime[k] = count_stop();

_CountingSort()的实现如下:

int* _CountingSort(int a[],int num){//返回排好序的数组    int range = 65535;    int C[range + 1];    int temp[num];    int i,j;    for(i = 0; i <= range; i++)        C[i] = 0;    for(j = 1; j <= num; j++)    {        C[a[j]] = C[a[j]] + 1;    }    for(i = 0; i <= range; i++)    {        C[i] = C[i] + C[i - 1];    }    for(j = num; j > 0; j--)    {        temp[C[a[j]]] = a[j];        C[a[j]] = C[a[j]] - 1;    }    return temp;}

c) 运行结果

6) 基数排序算法的实现:
a) 从input_numbers.txt中读取数据(注:这里并不是将数字存入一维整型数组中,而是将每个数字按数位拆分(SplitDigit函数)后,存入二维数组numtemp[num][6]中,以便于后续对每一个十进制位使用计数排序进行排序):

fp = fopen("..\\\\input\\input_numbers.txt","r");rewind(fp); for(j = 1; j <= num; j++){//从输入数据中读取一行,并将该行数据拆分出每一位      memset(numtemp[j],0,6*sizeof(int));    memset(strtemp[j],0,7*sizeof(char));    fscanf(fp,"%s",strtemp[j]);     splitnum = SplitDigit(strtemp[j]);    for(i = 0; i < 5; i++)    {        numtemp[j][i] = *(splitnum + i);    }       }fclose(fp);

函数SplitDigit的实现是:将从文件中读取一行得到的字符串,从个位开始到十位、百位、千位、万位,每一位都减去数字0的ASCII码存入一维数组digit中,返回digit

int* SplitDigit(char str[7]){    int digit[5];    int i,j;    for(i = 0; i < 5; i++)        digit[i] = 0;    j = 4;    int len = strlen(str);    for(i = len - 1; i >= 0; i--)    {        if(j >= 0)        {            digit[j] = (int)str[i] - 48;            j--;        }       }    return digit;}

b) 基数排序过程:
i. 这里从个位的排序开始,调用3)中的计数排序算法(额外输入上一个位数排序之后各个元素在原数组中的下标index[],返回的是排序后的数组元素在原数组中的下标,而不是排好序后的数组,即每位排序不直接改变原数组,而是记录下来排序后的元素在原数组中的下标,最后再一次性调整);

index初值为:

for(i = 0; i <= num; i++)    {        index[i] = i;    }

ii. 取每一个数的第i位存入数组digitsort中
这里便是取每个数的个位数存入digitsort,使用上述的初始index进行计数排序,返回结果为按个位数从小到大排列的元素在原数组numtemp中的下标:

for(j = 1; j <= num; j++){        digitsort[j] = numtemp[j][4];}result = _CountingSort(digitsort,index,num);
int* _CountingSort(int a[],int index[], int num){    int range = 65535;    int C[range + 1];    int temp[num + 1];    int i,j;    for(i = 0; i <= range; i++)        C[i] = 0;    for(j = 1; j <= num; j++)    {        C[a[j]] = C[a[j]] + 1;    }    for(i = 0; i <= range; i++)    {        C[i] = C[i] + C[i - 1];    }    for(j = num; j > 0; j--)    {        temp[C[a[j]]] = index[j];      ///////        C[a[j]] = C[a[j]] - 1;    }    return temp;}

iii. 再依次按照十位、百位、千位、万位进行排序,排序前根据上一次返回的result进行index的调整(表明index是经前面几个位数的排序后各个元素在原数组的下标):

for(i = 3; i >= 0; i--)    {        for(j = 1; j <= num; j++)        {            index[j] = *(result + j);            digitsort[j] = numtemp[*(result+j)][i];        }        result = _CountingSort(digitsort,index,num);    }

iv. 排序完成之后,可以将numtemp中的元素合并成一维整型数组mergenum,再根据result中的下标,依次的从mergenum中取相应下标的数放入sortnum中,得到最终的结果即为sortnum:

mergenum = MergeDigit(numtemp,num);   for(i = 1; i <= num; i++){   sortnum[i] = mergenum[*(result + i)];}

函数MergeDigit的实现(按位数乘以位权重求和即可):

int* MergeDigit(int (*a)[6],int num){    int result[num + 1];    int i;    for(i = 1; i <= num; i++)    {        result[i] = a[i][0]*10000 + a[i][1]*1000 +                    a[i][2]*100 + a[i][3]*10 + a[i][4];    }       return result;}

c) 运行结果

  1. 实验结果、分析(结合相关数据图表分析)
    1) 冒泡排序结果分析(工具:Excel):
    这里写图片描述
    基本符合O(n2)的算法复杂度
    2) 快速排序结果分析(excel中没有nlgn的拟合,故用origin8进行)
    这里写图片描述
    基本符合O(nlgn)的算法复杂度
    3) 计数排序结果分析(当n比较小时,由于k很大为65535,导致运行时间并不线性,故将n在2^14和2^17间增加了四个点来进行分析)如下:
    这里写图片描述
    对结果的分析:
    这里写图片描述
    基本符合O(n+k)的算法复杂度
    4) 基数排序结果分析(由于用了计数排序,故当n远小于k时,运行时间并不成线性,故增加了几个点来进行分析)如下:
    这里写图片描述

这里写图片描述
基本符合O(n+k)的算法复杂度
6. 实验心得
1) 当问题规模n较小时,性能:快速排序>冒泡排序>计数排序>基数排序,当问题规模n较大时,性能:计数排序>快速排序>基数排序>冒泡排序;
2) 对四种排序的算法和性能有了更深入的了解,尤其是基数排序的实现模块思考了很久,学习了在不同问题规模下和处理不同类型的数据下,该如何选择最优的算法;
3) 对文件读写操作更为熟练;
4) 学会了使用excel和origin等数据分析工具进行性能的分析过程。

源代码下载:http://download.csdn.net/download/m0_37829610/10025689
转载请注明出处:
http://blog.csdn.net/m0_37829610/article/details/78268531

阅读全文
0 0