数据结构学习第二天 - clock()计算程序运行的时间+时间复杂度 + 最大子列和求解问题(时间复杂度从N^3 -> N^2 -> N*log^N -> N的优化)


clock()计算程序运行的时间

  • clock():捕捉从程序开始运行到clock()被调用所消耗的时间
  • 时间单位:clock tick,即“时钟打点”
  • 常数CLK_TCK(或者CLOCKS_PER_SEC):机器时钟每秒所走的时钟打点数

通用模型代码:
#include <stdio.h>
#include <time.h>

clock_t start, stop;
/*clock_t是clock()函数返回的变量类型*/
double duration;
/*记录被测函数运行时间,以秒为单位*/
int main(){
	/*不在测试范围内的准备工作写在clock()调用之前*/
	start = clock();
	MyFunction();
	stop = clock();
	duration = ((double)(stop-start))/CLK_TCK;
	/*计算运行时间*/
	/*其他不在测试范围的处理写在后面,例如输出duration的值*/
}

对应实例(2种代码方法)

对于多项式求解,有俩种方法:

  • 1、直接按照方程求解
    在这里插入图片描述
/*字母p:记录的是f(x)式子的值,然后每循环i次,就解出i个式子的和*/
double f1(int n, double a[], double x){
 	int i;
 	double p = a[0];
 	for(i=1; i<=n; i++){
 		p += (a[i] * pow(x,i));
 		/*里面的a[i] * pow(x,i)就是式子anX^n*/
	 }
	 return p;
}
  • 2、提取X进行求解
    在这里插入图片描述
/*从里到外开始计算,p记录的是括号里面的值(与x进行相乘的值)*/
double f2(int n, double a[], double x){
 	int i;
 	double p = a[n];
 	for(i=n; i>0; i--){
 		p = a[i-1] + x*p;
	 }
	 return p;
}

虽然现在我们还没有讲到时间复杂度,但是先计算一下他们的时间复杂度

第一个代码:
在for循环里面,每一次都需要循环i次的乘积(解释:pow计算阶乘就需要i-1次乘积,前面一个乘积,一共就是i次)
一共循环n次,也就是循环的次数为:1-n次的加和
也就是:(1+n)* n/2
那么就是o(n^2)

第二个代码:
也是一个循环,但是循环里面只有一次乘法,也就是循环次数为n次
那么就是o(n)

所以很明显:第二个代码简便于第一个代码


但是!我们这样也只是进行口头计算,我们用clock函数进行运算,看看结果吧!

  • 【注意】
    由于一次循环对计算机而言,速度可以忽略不计,所以我们将代码进行多次循环,再除以循环的次数,就可以得到对应的时间

代码如下:(为了使代码简洁 -> 一个是.h文件,一个是.cpp文件)

命名为clock的.h文件
# include <stdio.h>
# include <time.h>
# include <math.h>
clock_t start, stop;
double duration;
# define MAXN 100/*多项式最大项数,即多项式阶数+1(99阶)*/
# define MAXK 1E5 /*循环的次数*/ 

//方法1
double f1(int n, double a[], double x){
 	int i;
 	double p = a[0];
 	for(i=1; i<=n; i++){
 		p += (a[i] * pow(x,i));
	 }
	 return p;
}

//方法2
double f2(int n, double a[], double x){
 	int i;
 	double p = a[n];
 	for(i=n; i>0; i--){
 		p = a[i-1] + x*p;
	 }
	 return p;
}

//输出方法1
void print1(double a[]){
	start = clock();
	for(int i=0; i<MAXK; i++){
		f1(MAXN-1, a, 1.1);
	}	
	stop = clock();
	duration = ((double)(stop-start))/CLK_TCK/MAXN;
	printf("ticks1 = %f\n", (double)(stop-start));
	printf("duration1 = %6.2e\n",duration);
}

//输出方法2
void print2(double a[]){
	start = clock();
	//对代码循环MAXK次
	for(int i=0; i<MAXK; i++){
		f2(MAXN-1, a, 1.1);
	}	
	stop = clock();
	duration = ((double)(stop-start))/CLK_TCK/MAXN;
	printf("ticks2 = %f\n", (double)(stop-start));
	printf("duration2 = %6.2e\n",duration);
}
对应的.cpp文件
# include "clock.h"
int main(){
	int i;
	double a[MAXN];
	//对代码循环MAXK次
	for(i=0; i<MAXN; i++){
		a[i] = (double)i;
	}
	print1(a);
	print2(a);
	return 0;
}


程序的好坏判定

判断一个程序的好坏,一般都是俩种方法计算,一个是空间复杂度,另一种是时间复杂度。
空间复杂度过大会导致内存直接爆炸,无法得到结果
时间复杂度会导致时间运行过慢,无法输出完结果


时间复杂度

一般在时间复杂度计算的时候,我们关注俩种复杂度

  • 最坏情况复杂度
  • 平均复杂度

但是由于平均复杂度不好估计,所以我们一般使用最坏情况复杂度
在这里插入图片描述

对俩段算法代码进行拼接:

  • 相加:时间复杂度 = max(算法1时间复杂度,算法2时间复杂度)
  • 相乘:时间复杂度 = 算法1时间复杂度 ✖ 算法2时间复杂度

一个for循环的时间复杂度 == 循环次数 ✖ 循环体代码的复杂度

if-else结构的复杂度 == if的条件判断复杂度和两个分枝部分的复杂度,总体复杂度取三者中最大(max)


最大子列和问题

在这里插入图片描述

题目实例化:
1,2,3,-1,-2,-3
这一串数字的最大值是6


一个专业的程序员写代码的时候,遇到时间复杂度过大的程序,就需要想尽一切办法将程序进行优化,比如说,遇到o(n^2)的程序,就要有意识的转化为 o(n*log^n)

.cpp文件:
#include"max.h"
int main(){
	int A[5] = {-1,-3,4,6,-2};
	int N = 6;// n的值 == A[]的长度 
	int a = MaxSubseqSum1(A,N);
	int b = MaxSubseqSum2(A,N);
	int c = MaxSubseqSum4(A,N);
	printf("%d,%d,%d",a,b,c);
	return 0;
}
.h文件,命名为:max.h
# include<stdio.h>

/*算法1:暴力解法,T(N) = O(N^3)*/
/*每个子列都求出来,然后得到最大值*/ 
int MaxSubseqSum1(int A[], int N){
	int ThisSum, MaxSum = 0;
	int i, j, k;
	for(i = 0; i < N; i++){
		for(j = i; j < N; ++j){
			ThisSum = 0;
			for(k = i; k < j; k++)
				ThisSum += A[k];
			if(ThisSum>MaxSum){
				MaxSum = ThisSum;
			}
		}
	}
	return MaxSum;
} 

/*算法2:对算法1进行优化,T(N) = O(N^2)*/
/*每个子列都求出来,然后得到最大值*/ 
int MaxSubseqSum2(int A[], int N){
	int ThisSum, MaxSum = 0;
	int i, j;
	for(i = 0; i < N; i++){
		ThisSum = 0;
		for(j = i; j < N; ++j){	
			ThisSum += A[j];							
			if(ThisSum>MaxSum){
				MaxSum = ThisSum;
			}
		}
	}
	return MaxSum;
} 

/*算法4:一个循环,T(N) = O(N)*/
int MaxSubseqSum4(int A[], int N){
	int ThisSum, MaxSum;
	int i;
	ThisSum = MaxSum = 0;
	for(i = 0; i < N; i++){
		ThisSum += A[i];								
		if(ThisSum>MaxSum)
			MaxSum = ThisSum;
		else if(ThisSum < 0)
		ThisSum = 0;		
	}
	return MaxSum;
} 

第三种代码:分而治之【有点难的想到】
中间隔断,左右寻找(没有贴进我的程序里面,这个解法会思路即可)

int Max3( int A, int B, int C )
{ /* 返回3个整数中的最大值 */
    return A > B ? A > C ? A : C : B > C ? B : C;
}

int DivideAndConquer( int List[], int left, int right )
{ /* 分治法求List[left]到List[right]的最大子列和 */
    int MaxLeftSum, MaxRightSum; /* 存放左右子问题的解 */
    int MaxLeftBorderSum, MaxRightBorderSum; /*存放跨分界线的结果*/

    int LeftBorderSum, RightBorderSum;
    int center, i;

    if( left == right )  { /* 递归的终止条件,子列只有1个数字 */
        if( List[left] > 0 )  return List[left];
        else return 0;
    }

    /* 下面是"分"的过程 */
    center = ( left + right ) / 2; /* 找到中分点 */
    /* 递归求得两边子列的最大和 */
    MaxLeftSum = DivideAndConquer( List, left, center );
    MaxRightSum = DivideAndConquer( List, center+1, right );

    /* 下面求跨分界线的最大子列和 */
    MaxLeftBorderSum = 0; LeftBorderSum = 0;
    for( i=center; i>=left; i-- ) { /* 从中线向左扫描 */
        LeftBorderSum += List[i];
        if( LeftBorderSum > MaxLeftBorderSum )
            MaxLeftBorderSum = LeftBorderSum;
    } /* 左边扫描结束 */

    MaxRightBorderSum = 0; RightBorderSum = 0;
    for( i=center+1; i<=right; i++ ) { /* 从中线向右扫描 */
        RightBorderSum += List[i];
        if( RightBorderSum > MaxRightBorderSum )
            MaxRightBorderSum = RightBorderSum;
    } /* 右边扫描结束 */

    /* 下面返回"治"的结果 */
    return Max3( MaxLeftSum, MaxRightSum, MaxLeftBorderSum + MaxRightBorderSum );
}

int MaxSubseqSum3( int List[], int N )
{ /* 保持与前2种算法相同的函数接口 */
    return DivideAndConquer( List, 0, N-1 );
}

追加更新:
由于最大子列和求解问题的代码是要吃饭的时候赶出来的,所以呢,手写了一个代码分析,也就是代码运行的手写体,这样子更方便自己理解代码


算法1,2
在这里插入图片描述


算法4:
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Bessie_Lee_gogogo

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值