内部排序算法效率比较,数据结构实验,C语言实现
注:完整代码可以到我的小程序 航筱北同学 上搜索查看。
1.直接插入排序
2.折半插入排序
3.希尔排序
4.简单选择排序
5.堆排序
6.冒泡排序
7.快速排序
8.归并排序
实验内容与要求
对我们所学过的各种排序算法,分别测试统计数据量为100、10000、1000000时各自在正序、逆序、随机序时所耗的时间,写出分析报告。可用图表说明你的分析内容。(确保算法正确的情况下可以不需要输出,以节约时间)
实验过程
1.首先,写出各个排序算法的C语言代码【详见后面的代码】;
2.声明一个存储待排序数据的数组,数组的RMaxSize定为1000000;
R[RMaxSize]应该放在main主函数的前面,不要放在main函数的里面,可能main函数里面可分配的数组空间有限。
#define RMaxSize 1000000
int R[RMaxSize];
3为了测算出排序时消耗的时间,我们还要引入time库,用到里面的库函数clock();
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <math.h>
#define RMaxSize 1000000
int R[RMaxSize];
clock_t start,stop; //colck_t是clock()函数返回的变量类型
4.可以通过类似下面的代码来测算各个排序方法排序时所消耗的时间。
start = clock();
//要记录运行时间的被测函数或某段过程将嵌入到这里
stop = clock();
duration = stop - start; // 将某段过程执行结束时的时间减去开始执行时的时间即得到该段过程的运行时间,以毫秒为单位,duration为以声明类型过的的变量
比如,要测试某次排序的耗时,可写如下代码
**5.为了对之前的8种排序算法的时间效率进行对比,我们需要把它们各自在不同数据量排序时的耗时记录下来。**所以,可以通过数组来存储,但是为了更加方便和直观的输出结果,决定采用二维数组来存储这些运行时间,并且,为了不那么变扭,决定把二维数组的第一行和第一列废弃掉,即durations[0][j]和durations[i][0]废弃。到时候输出的结果应该类似这样的表格格式,更加直观。当然,此时的数组内容已经初始化为-1,为了和0区分,-1代表未统计。
可以通过如下代码来设计这个表格,对数据进行格式化输出
void showResultTable() {
printf("\n 基本内部排序算法时间效率比较\n");
printf("\n%6s \t%6s %6s %6s %6s %6s %6s %6s %6s\n", "数据量\\耗时(毫秒)", "直接插入", "折半插入", "希尔排序", "简单选择", "堆排序法","冒泡排序","快速排序","归并排序");
printf("%10s\t %8d %8d %8d %8d %8d %8d %8d %8d\n","100个正序数",durations[1][1],durations[1][2],durations[1][3],durations[1][4],durations[1][5],durations[1][6],durations[1][7],durations[1][8]);
printf("%10s\t %8d %8d %8d %8d %8d %8d %8d %8d\n","100个逆序数",durations[2][1],durations[2][2],durations[2][3],durations[2][4],durations[2][5],durations[2][6],durations[2][7],durations[2][8]);
printf("%10s\t %8d %8d %8d %8d %8d %8d %8d %8d\n","100个随机数",durations[3][1],durations[3][2],durations[3][3],durations[3][4],durations[3][5],durations[3][6],durations[3][7],durations[3][8]);
printf("%10s\t %8d %8d %8d %8d %8d %8d %8d %8d\n","10000个正序数",durations[4][1],durations[4][2],durations[4][3],durations[4][4],durations[4][5],durations[4][6],durations[4][7],durations[4][8]);
printf("%10s\t %8d %8d %8d %8d %8d %8d %8d %8d\n","10000个逆序数",durations[5][1],durations[5][2],durations[5][3],durations[5][4],durations[5][5],durations[5][6],durations[5][7],durations[5][8]);
printf("%10s\t %8d %8d %8d %8d %8d %8d %8d %8d\n","10000个随机数",durations[6][1],durations[6][2],durations[6][3],durations[6][4],durations[6][5],durations[6][6],durations[6][7],durations[6][8]);
printf("%10s\t %8d %8d %8d %8d %8d %8d %8d %8d\n","1000000个正序数",durations[7][1],durations[7][2],durations[7][3],durations[7][4],durations[7][5],durations[7][6],durations[7][7],durations[7][8]);
printf("%10s\t %8d %8d %8d %8d %8d %8d %8d %8d\n","1000000个逆序数",durations[8][1],durations[8][2],durations[8][3],durations[8][4],durations[8][5],durations[8][6],durations[8][7],durations[8][8]);
printf("%10s\t %8d %8d %8d %8d %8d %8d %8d %8d\n","1000000个随机数",durations[9][1],durations[9][2],durations[9][3],durations[9][4],durations[9][5],durations[9][6],durations[9][7],durations[9][8]);
}
当然,也可以通过循环来输出,以下是循环来输出的表格形式的代码
6.为了产生不同数量级的待排序数据,我们可以写三个函数来获取数据,(如果你想要手动输入数据或者从文件读取数据到待排序数组来排序,可以参看后面一篇文章的代码)
void get_data_random(int R[],int n) ;
需要用到math.h库来使用rand函数;
用来获取n个随机数给待排序数组R[],为了方便,以下所有下标一律从1开始;用系统时间做种子来获取类似随机数,要不然获取的是伪随机数;并且为了把控随机数的范围,需要做一个取余操作,比如是100以内的随机数还是10000以内的随机数。
void get_data_PreOrder(int R[],int n);
用来产生n个正序数给待排序数组;
void get_data_ReverseOrder(int R[],int n);
用来产生n个逆序数给待排序数组;
代码如下:
/*以下各个函数的数组下标一律从1开始,R[0]不要*/
//产生n个(0~n)范围随机数的函数,以系统时间为种子产生随机数
void get_data_random(int R[],int n) {
int i;
srand( (unsigned)time( NULL ) );
for(i=1; i<=n; i++)
R[i]=rand()%n;
}
//产生n个正序数给待排序数组R[]的函数
void get_data_PreOrder(int R[],int n) {
int i;
for(i=1; i<=n; i++)
R[i]=i;
}
//产生n个逆序数给待排序数组R[]的函数
void get_data_ReverseOrder(int R[],int n) {
int i,j;
for(i=n,j=1; i>=1,j<=n; i--,j++)
R[j]=i;
}
7.如果需要将数据输出显示到屏幕,需要写一个函数,但是为了节约时间,虽然写了,但是到时候并没有去调用这个函数。
//输出n个数的函数,格式控制每10个一行
void put_data(int R[],int n) {
int i;
printf("\n");
for(i=1; i<=n; i++) {
printf("%6d",R[i]);
if(!(i%10))
printf("\n");
}
}
测试运行结果
为了节约时间,对于1000000个数据进行排序,由于有些算法实在太慢,所以跳过了1000000的测试,
依次输入;这里使用了system(“cls”)清除屏幕,便于使得输出界面干净。
最终结果
当然,运行时间会受很多方面诸如编译器、CPU等的影响,可能每次测试都有略微差异,但基本上可看出各个内部排序算法在不同数量级数据的效率差异。
1)当数量级为100个时,无论是正序、逆序还是随机,这8种内部排序算法耗时都不到1毫秒;
2)当数量级为10000个时,排序效率最高的是归并排序、希尔排序和堆排序,而对于10000个正序数,简单选择排序和快速排序效率低下。对于10000个逆序数或10000个随机数,此时冒泡排序效率最低下;
3)当数量级为1000000个时,待排数据正序逆序时希尔排序效率最高,待排数据随机时归并排序效率最高,堆排序次之;而其他排序算法 直接插入排序、折半插入排序、简单选择排序、冒泡排序、快速排序等由于耗时过久,一直卡在那儿,只能强行终止测算,未能得出大概的耗时。
主函数如下,部分代码折叠
各类内部排序算法代码C语言
//直接插入排序
void InsertSort(int R[],int n) { //待排关键字存储在R[]中,默认为整型,个数为n
int i,j,temp;
for (i=2; i<=n; ++i) {
temp=R[i] ;//将待插入关键字暂存于temp中
j=i-1;
/*下面这个循环完成了从待排关键字之前的关键字开始扫描,如果大于待排关键字,则后移一位*/
while (j>=0&&temp<R[j]) {
R[j+1]=R[j] ;
--j;
}
R[j+1]=temp; //找到插入位置, 将temp中暂存的待排关键字插入
}
}
//折半插入排序
void BInsertSort(int R[],int n) {
int i,j,low,high,mid,temp;
for(i=1; i<=n; i++) {
temp=R[i];
low=1;
high=i-1;
while(low<=high) {
mid=(low+high)/2;
if(temp>=R[mid])
low=mid+1;
else
high=mid-1;
}
for(j=i-1; j>=high+1; j--)
R[j+1]=R[j];
R[j+1]=temp;
}
}
//希尔排序
void ShellSort(int R[],int n) {
int i,j,d=n/2;
int temp;
while(d) {
for(i=d; i<n; i++) {
temp=R[i];
for(j=i-d; j>0 && R[j]>temp; j-=d)
R[j+d]=R[j];
R[j+d]=temp;
}
d/=2;
}
}
//冒泡排序
void BubbleSort(int R[],int n) { //默认待排序关键字为整型
int i,j, flag, temp;
for (i=n-1; i>=1; --i) {
flag=0;//变量flag用来标记本趟排序是否发生了交换
for(j=1; j<=i; ++j)
if (R[j-1]>R[j] ) {
temp=R[j] ;
R[j]=R[j-1] ;
R[j-1]=temp;
flag=1;//如果没发生交换,则flag的值为0;如果发生交换,则flag的值改为1
}
if(flag==0)//一趟排序过程中没有发生关键字交换,则证明序列有序,排序结束
return;
}
}
//快速排序
void QuickSort (int R[], int low, int high) { //对从R[low]到R [high]的关键字进行排序
int temp;
int i=low, j=high;
if (low<high) {
temp=R[low];
/*下面这个循环完成了一趟排序,即将数组中小于temp的关键字放在左边,大于temp的关键字放在右边*/
while(i<j) {
while (j>i&&R[j]>=temp) --j;//从右往左扫描, 找到一个小于temp的关键字
if(i<j) {
R[i]=R[j];//放在temp左边
++i;//i右移一位
}
while(i<j && R[i]<temp) ++i; //从左往右扫描,找到一个大于temp的关键字
if (i<j) {
R[j]=R[i] ;//放在temp右边
--j;//j左移一位
}
}
R[i]=temp;//将temp放在最终位置
QuickSort (R, low, i-1) ;//递归地对temp左边的关键字进行排序
QuickSort (R, i+1,high) ;//递归地对temp右边的关键字进行排序
}
}
//简单选择排序
void SelectSort (int R[],int n) {
int i,j,k;
int temp;
for (i=1; i<=n; ++i) {
k=i;
/*这个循环是算法的关键,它从无序序列中挑出一个最小的关键字*/
for (j=i+1; j<n; ++j)
if (R[k]>R[j])
k=j;
/*下面3句完成最小关键字与无序序列第一个关键字的交换*/
temp=R[i];
R[i]=R[k];
R[k]=temp;
}
}
/*本函数完成在数组R[1ow]到R[high]的范围内对在位置low上的结点进行调整-----用于堆排序*/
void Sift(int R[],int low,int high) { //这里 关键字的存储设定为从数组下标1开始
int i=low,j=2*i;//R[j]是R[i]的左孩子结点
int temp=R[i];
while(j<=high) {
if (j<high && R[j]<R[j+1])//若右孩子较大,则把j指向右孩子
++j;//j变为2*i+1
if (temp<R[j]) {
R[i]=R[j];//将R[j]调整到双亲结点的位置上
i=j;//修改i和j的值,以便继续向下调整
j=2*i;
} else
break;//调整结束
}
R[i]=temp;//被调整结点的值放入最终位置
}
/*堆排序函数*/
void HeapSort(int R[], int n) {
int i;
int temp;
for (i=n/2; i>0; i--)//建立初始堆
Sift (R,i,n);
for (i=n; i>1; i--) { //进行n-1次循环,完成堆排序
/*以下3句换出了根结点中的关键字,将其放入最终位置*/
temp=R[1];
R[1]=R[i];
R[i]=temp;
Sift(R,1,i-1);//在减少了1个关键字的无序序列中进行调整
}
}
void merge(int R[],int R1[],int low,int mid,int high) {
int i=low,j=mid+1,k=low;
while(i<=mid&&j<=high)
if(R[i]<=R[j]) R1[k++]=R[i++];
else R1[k++]=R[j++];
while(i<=mid) R1[k++]=R[i++];
while(j<=high) R1[k++]=R[j++];
}
void mergepass(int R[],int R1[],int len,int n) {
int i=1;
while(i+2*len-1<=n) {
merge(R,R1,i,i+len-1,i+2*len-1);
i+=2*len;
}
if(i+len-1<n) merge(R,R1,i,i+len-1,n);
else
while(i<=n) {
R1[i]=R[i];
i++;
}
}
//二路归并排序
void MergeSort(int R[],int n) {
int *R1,len=1;
R1=(int*)malloc(sizeof(int)*(n+1));
while(len<n) {
mergepass(R,R1,len,n);
len*=2;
mergepass(R1,R,len,n);
len*=2;
}
}