计算机程序设计实训记录(4)


完成了oj题的练习,现在就进入了下一环节——算法测试分析


算法概述

直接拿PPT的图了


代码段时间复杂度分析

代码段执行时间测定

关于时间的测定,我们就需要用到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


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM