完成了oj题的练习,现在就进入了下一环节——算法测试分析
算法概述
代码段时间复杂度分析
代码段执行时间测定
关于时间的测定,我们就需要用到C的时间函数clock(),该函数返回自程序启动起,处理器时钟所使用的时间。如果失败,则返回 -1 值。
下面是课件给出来的一个关于时间函数的代码:
double gettime(int restart) // 参数非零表示重新设置计时起点
{
const double c=1.0/CLOCKS_PER_SEC; // 毫秒转换至秒
static clock_t t = 0; // 静态局部变量,存储计时起点
if(restart||t==0) t = clock(); // 根据实参决定是否重新确定计时起点
return c*(clock()-t); // 从上一计时点到现在所经历的时间差
}
而在实际操作中,也给出了例子
double t;
gettime(1);
// 调用某函数
t = gettime(0); // 或 t = gettime();
等等,真的可以直接t = gettime();
吗?这还得让我后续测试一下才行。
函数中执行某运算的次数统计
这里我们可以拿冒泡排序来举例:
void Bubble(double *a, int length, unsigned long long *nComp,
unsigned long long *nAssign)
{ // 注意第3-4个参数的数据类型,指针型传递
*nComp = *nAssign = 0; // 非递归函数,可在此置0
for(int i=1; i<length; i++) // 不统计辅助操作
{
for(int j=0; j<length-i; j++)
{
(*nComp)++; // 仅统计元素间的操作(元素间比较)
if(a[j] > a[j+1])
{
(*nAssign) += 3;
swap(a[j], a[j+1]); // 元素间3次赋值
}
}
}
}
为什么要传指针呢?当然是为了返回多个数据。
课程任务
这里就直接照抄课程的文件了
对于所涉及的排序算法,本课程的重点在于实验分析或实验验证,即:不要求进行严密地理论分析,需要时直接引用相关的理论结果。
首先研究基本的冒泡排序算法、选择排序算法和快速排序算法(如参见表1提到的Sorts-C.zip文件)测试三种排序算法的时间。
其次,利用Score结构体数组讨论排序算法的稳定性。所谓排序的稳定性是指:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,若x[i]与x[j]相等,且x[i]在x[j]之前,而在排序后的序列中,x[i]仍在x[j]之前,则称这种排序算法是稳定的;否则称为不稳定的[3]。对于不稳定的排序算法只要举出一个实例即可。而对于稳定的排序算法,则不展开深入的理论研究(给出结论即可)。
最后,对double型数组的3个排序函数进行修改,在每个函数中增加2个无符号扩展的长整型指针形参(unsigned long long *),分别用于间接“返回”相关函数执行数组元素间的比较次数、数组元素间赋值次数(不统计辅助操作的次数,即不统计下标比较、下标增减等操作次数)。根据统计结果,对不同排序算法进行对比分析。
看...看起来好难!但是课程本身已经给了我们成品的测试程序,第一个和第二个任务都可以直接解决,好耶!
运行程序
既然已经有了成品,我们就运行一下看看吧。
效果大概是这样
整型数据(正态分布) | ||||||
冒泡排序 | 选择排序 | 快速排序 | ||||
1024 | 0.002000 | 0.001000 | 0.000000 | 冒泡排序: 正确 | 选择排序: 正确 | 快速排序: 正确 |
2048 | 0.000000 | 0.013000 | 0.000000 | 冒泡排序: 正确 | 选择排序: 正确 | 快速排序: 正确 |
4096 | 0.016000 | 0.031000 | 0.000000 | 冒泡排序: 正确 | 选择排序: 正确 | 快速排序: 正确 |
8192 | 0.140000 | 0.063000 | 0.000000 | 冒泡排序: 正确 | 选择排序: 正确 | 快速排序: 正确 |
16384 | 0.600000 | 0.275000 | 0.016000 | 冒泡排序: 正确 | 选择排序: 正确 | 快速排序: 正确 |
32768 | 2.632000 | 1.075000 | 0.047000 | 冒泡排序: 正确 | 选择排序: 正确 | 快速排序: 正确 |
65536 | 11.197000 | 4.319000 | 0.180000 | 冒泡排序: 正确 | 选择排序: 正确 | 快速排序: 正确 |
次数统计
既然前面已经有例子了,我们只要缝合上去就好了,好耶!
事实证明完全没有那么简单
冒泡排序
void D_Bubble(double *a, int size,unsigned long long *nComp,unsigned long long *nAssign)// 冒泡排序
{
*nComp = *nAssign = 0; // 非递归函数,可在此置0
double temp; // 定义一个局部变量,数据类型与形式数据类型相同
int i, j;
for(i=1; i<size; i++) // 共进行 size-1 轮比较和交换
{
for(j=0; j<size-i; j++)
{
(*nComp)++; // 仅统计元素间的操作(元素间比较)
if(a[j] > a[j+1]) // 相邻元素之间比较,必要时
{
temp = a[j]; // 交换 a[j] 与 a[j+1]
a[j] = a[j+1];
a[j+1] = temp;
(*nAssign) += 3; // 元素间3次赋值
}
}
}
}
比较排序
void D_Select(double *a, int size,unsigned long long *nComp,unsigned long long *nAssign)// 选择排序
{
*nComp = *nAssign = 0; // 非递归函数,可在此置0
double temp;
int i, j, k=0;
for(i=1; i<size; i++) // 循环size-1次
{
for(j=i; j<size; j++)
{
(*nComp)++; // 仅统计元素间的操作(元素间比较)
if(a[j] < a[k])
k = j;
} // 找出当前范围内"最小"元素的下标
if(k!=i-1) // 若"最小"元素不是a[i-1],则交换之
{
temp = a[k];
a[k] = a[i-1];
a[i-1] = temp;
(*nAssign) += 3; // 元素间3次赋值
}
k = i;
}
}
快速排序
void D_Qsort(double *a, int size,unsigned long long *nComp,unsigned long long *nAssign) // 快速排序
{
double pivot, temp;
int left=0, right=size-1; // 下标(整数)
if(size<=1) return;
pivot = a[right]; // 选择最后一个值为分界值
do
{
while(left<right && a[left]<=pivot)
{
left++;
(*nComp)++; //一次比较
} // 此处 "<=" 是让与分界值相等的元素暂时留在原地
while(left<right && a[right]>=pivot)
{
right--;
(*nComp)++; //一次比较
}// 此处 ">=" 是让与分界值相等的元素暂时留在原地
if(left < right)
{
temp=a[left]; a[left]=a[right]; a[right]=temp;
(*nAssign) += 3; // 元素间3次赋值
}
}while(left < right);
a[size-1] = a[left]; a[left] = pivot;
(*nAssign) += 2; // 找到分界点 left并赋值两次
D_Qsort(a, left,nComp,nAssign); // 递归调用(左侧部分)
D_Qsort(a+left+1, size-left-1,nComp,nAssign); // 递归调用(右侧部分)
}
啊咧,快速排序没有元素间的比较吗?真神奇啊。
这么看的话可能快排反而比较次数最多。然鹅就结果而言是比较少的
接下来就是写测试程序。
测试程序
void D_Test()
{
double *data0=NULL, *data=NULL; // 指针初始化为NULL非常重要!
char InitConf[][20] = {"完全逆序", "完全顺序", "均匀分布", "正态分布"}; // C-字符串数组(数据分布方式)
char algo[][20] = {"冒泡排序", "选择排序", "快速排序"}; // C-字符串数组(排序算法名称)
void (*f[])(double*, int,unsigned long long*,unsigned long long*) = {D_Bubble, D_Select, D_Qsort}; // 函数指针数组(元素为一系列函数的入口地址)
int i, j, n, m = sizeof(f)/sizeof(*f); // m为函数指针数组f的元素个数(此处有3个函数指针,分别指向3个排序函数的入口地址)
int flag[20]; // 这里认为:常量20足够大于变量 m。记录不同算法执行的正确性
//double t[20];
unsigned long long nc[20],na[20]; // 同上。记录不同算法的操作次数
unsigned long long nComp=0,nAssign=0;
for(j=2; j>=-1; j--) // 数据分布类型
{
printf("\n双精度浮点型数据(%s)\n", InitConf[j+1]);
for(i=0; i<m; i++)
printf("\t%s\t", algo[i]);
printf("\n");
for(n=1024; n<=65536; n*=2)
{
D_GetMemory(&data, &data0, n); // 申请分配堆空间
D_InitData(data0, n, j); // 设置原始数据
printf("%d", n);
for(i=0; i<m; i++)
{
if(n==65536 && j<=0 && i==m-1) // j<=0(完全顺序、完全逆序),i==m-1(快速排序,递归算法)
break;
D_ReSet(data, data0, n); // 恢复原始数据
//gettime(1); // 设置计时起点
nComp=0;
nAssign=0; //每次排序前初始化计数变量
f[i](data, n,&nComp,&nAssign); // 第 i 种排序算法
//t[i] = gettime(0);
nc[i]=nComp;
na[i]=nAssign; // 返回比较次数和赋值次数
flag[i] = D_Check(data, n); // 检验排序的正确性
}
for(i=0; i<m; i++)
{
if(n==65536 && j<=0 && i==m-1) // j<=0(完全顺序、完全逆序),i==m-1(快速排序,递归算法)
printf("\t");
else
{
printf("\t%llu", nc[i]);
printf("\t%llu", na[i]);
}
}
for(i=0; i<m; i++)
{
if(n==65536 && j<=0 && i==m-1) // j<=0(完全顺序、完全逆序),i==m-1(快速排序,递归算法)
printf("\t快速排序:由于递归层次太深,可能导致栈溢出,故跳过。");
else
printf("\t%s: %s", algo[i], (flag[i]?"正确":"错误"));
}
printf("\n");
D_FreeMemory(&data, &data0); // 释放堆空间资源,并使指针为空
}
}
}
新的问题又出现了
undefined reference to '__chkstk_ms'
查了一下,应该是mingw之间打架的问题,所以我换了一台电脑去build。
运行结果
大概是这样
双精度浮点型数据(正态分布) | |||||||||
冒泡排序 | 选择排序 | 快速排序 | |||||||
1024 | 523776 | 812901 | 523776 | 3051 | 10965 | 6671 | 冒泡排序: 正确 | 选择排序: 正确 | 快速排序: 正确 |
2048 | 2096128 | 3118740 | 2096128 | 6126 | 26537 | 14526 | 冒泡排序: 正确 | 选择排序: 正确 | 快速排序: 正确 |
4096 | 8386560 | 12431133 | 8386560 | 12243 | 67638 | 31535 | 冒泡排序: 正确 | 选择排序: 正确 | 快速排序: 正确 |
8192 | 33550336 | 50310393 | 33550336 | 24549 | 125374 | 69960 | 冒泡排序: 正确 | 选择排序: 正确 | 快速排序: 正确 |
16384 | 134209536 | 200332119 | 134209536 | 49131 | 270704 | 151097 | 冒泡排序: 正确 | 选择排序: 正确 | 快速排序: 正确 |
32768 | 536854528 | 804468672 | 536854528 | 98268 | 578908 | 326634 | 冒泡排序: 正确 | 选择排序: 正确 | 快速排序: 正确 |
65536 | 2147450880 | 3215171196 | 2147450880 | 196563 | 1321824 | 692429 | 冒泡排序: 正确 | 选择排序: 正确 | 快速排序: 正确 |
终于做完了,好耶O(∩_∩)O