《数据结构与算法分析》Chap2:算法分析

按照阅读顺序记录笔记

算法是为求解一个问题需要遵循的、被清楚地指定的简单指令集和,确定某个算法是正确的,之后确定算法需要多少时间或空间就是最重要的了。

数学基础

时间复杂度:一般情况下,算法中基本操作重复执行的次数是问题规模n的某个函数,用T(n)表示,若有某个辅助函数f(n),使得当n趋近于无穷大时,T(n)/f(n)的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数。记作T(n)=O(f(n)),称O(f(n))为算法的渐进时间复杂度(O是数量级的符号 ),简称时间复杂度。

也就是当N趋于无穷,lim(n->∞) T(N)/f(N)的比值情况。

法则

  1. T(n,m) = T1(n) + T2(n) = O (max ( f(n), g(m) )
  2. T(n,m) = T1(n) * T2(m) = O (f(n) * g(m))
  3. 对于任意常数k, log^k(N) = O(N),对数的无论什么幂次也不比N快!
  4. 如果T(N)是k次多项式,去掉常数和低阶项仍等阶,T(N) 等阶于 N^k

复杂度和时间效率关系
复杂度与时间效率的关系:
1/n<c < log2n < n < n*log2n < n2 < n3 < 2n < 3n < n! (c是一个常量)
|--------------------------|--------------------------|-------------|
较好 一般 较差

要分析的问题

最坏时间:算法的时间复杂度不仅与语句频度有关,还与问题规模及输入实例中各元素的取值有关。一般不特别说明,讨论的时间复杂度均是最坏情况下的时间复杂度。这就保证了算法的运行时间不会比任何更长。

最坏时间规定了界限,平均情况提供不了,而且平均情况计算起来更难

运行时间计算

规定:不存在特定的时间单元,抛弃前导常数,抛弃低阶项,计算大O的运行时间。

  1. 找到执行次数最多的语句
  2. 计算语句执行次数的数量级
  3. 用大O来表示结果

一般法则

  1. for循环法则:
    运行时间 = for循环内语句运行时间* 迭代次数
for(int i=0;i<n;i++)
	sum++;
//语句内运行时间为1,迭代次数n,所以运行时间=O(N)
  1. 嵌套for循环法则(for循环法则的推论)
    一组嵌套循环内部语句的总运行时间= 该语句运行时间*所有for循环乘积大小
for(int i=0;i<n;i++)
	for(int j=0;j<n;j++)
		k++;
//语句运行时间为1,for乘积为n*n,所以运行时间=O(N^2)
for(int i=0;i<n;i++)
	for(int j=0;j<n;j*=2)
		k++;
//该语句运行时间1,for乘积n*logn,所以运行时间=O(NlogN)
  1. 顺序语句
    将各个语句求和即可,意味着最大值就是运行时间
for(int i=0;i<n;i++)
	sum++;
for(int i=0;i<n;i++)
	for(int j=0;j<n;j++)
		k++;
//运行时间是T(N) = n+ n^2=O(N^2)

分析的基本策略是inside to outside,如果有函数调用,那么首先分析函数调用。

最大子数列和问题

算法1

int MAX1(const int A[],int n)
{
    
    
	int sum,maxsum,i,j,k;
	for(int i=0;i<n;i++)
		for(int j=0;j<n;j++)
		{
    
    
			sum =0;
			for(k=i;k<=j;k++)
			{
    
    
				sum+= A[k];
			}
			if(sum>maxsum)
				maxsum = sum;
		}
	return maxsum;
}

运行时间复杂度为O(N^3),这种方法就比较笨b了,它尝试了每一种情况

int MAX2(const int A[],int n)
{
    
    
	int sum,maxsum,i,j;
	for(i=0;i<n;i++)
	{
    
    
		sum=0;
		for(j=i;j<n;j++)
		{
    
    
			sum+=A[j];
			if(sum>maxsum)
				maxsum= sum;
		}	
	}
	return maxsum;
}

这种方法时间复杂度为O(N^2),它只是在MAX1方法基础上减了一个for循环,本质并没有区别,但速度要快很多(数据大的话)

分治思想

int Max(int a,int b, int c)
{
    
    
	int t1 = a>b?a:b;
	int t2 = t1>c?t1:c;
	return t2;
}

static
int MaxSubSum(const int A[],int left,int right)
{
    
    
	int maxlsum,maxrsum;
	int maxlbs,maxrbs;
	int lbs,rbs;
	int center,i;
	
	if(left == right)
		if(A[left]>0)
			return A[left];
		else
			return 0;
	
	center = (left+right)/2;
	maxlsum = MaxSubSum(A,left,center);
	maxrsum = MaxSubSum(A,center+1,right);
	
	maxlbs=0; lbs=0;
	for(i=center;i>=left;i--)
	{
    
    
		lbs+=A[i];
		if(lbs>maxlbs)
			maxlbs=lbs;
		
	}
	maxrbs=0; rbs=0;
	for(i=center+1;i<=right;i++)
	{
    
    
		rbs+=A[i];
		if(rbs>maxrbs)
			maxrbs=rbs;
	}
	
	return Max(maxlsum,maxrsum,maxlbs+maxrbs);
}

int MAX3(const int A[],int n)
{
    
    
	return MaxSubSum(A,0,n-1);
}

时间复杂度:
Base case的时间复杂度为O(1);
两个for循环时间复杂度为O(N);
其他行复杂度都是常量级,omit
两个递归调用,求解大小为N/2的子序列问题(N为偶数假设),所以这部分花费2T(N/2)时间
总共花费:T(N)=2T(N/2)+O(N) ,用N简化O(N),则T(N)=2T(N/2)+N,由于T(1)=1,则T(2)、T(N)都可以推导出来,T(N)=Nlog N+ N =O(NlogN)

关于这个问题分治算法思想:分治算法思想
(其他讲的云里雾里)

算法4

int MAX4(const int A[],int n)
{
    
    
	int sum,maxsum, i;
	sum = maxsum =0;
	for(i=0;i<n;i++)
	{
    
    
		sum+=A[i];
		if(sum>maxsum)
			maxsum = sum;
		else if(sum<0)
			sum=0;
	}
	return maxsum;
}

这个算法很收膝盖,时间复杂度为O(N)

测试时间:

const int SIZE = 2000;
	clock_t starttime,endtime;
	srand((int)time(0));
	int arr[SIZE];
	for(int i=0;i<SIZE;i++)
	{
    
    
		arr[i]=rand()%SIZE;
	}
	
	//algorithm 1
	starttime = clock();
	cout<<MAX1(arr,SIZE)<<endl;
	endtime = clock();
	cout<<"Use time: "<<(double)(endtime - starttime)/CLOCKS_PER_SEC<<"s"<<endl;
	//algorithm 2
	starttime = clock();
	cout<<MAX2(arr,SIZE)<<endl;
	endtime = clock();
	cout<<"Use time: "<<(double)(endtime - starttime)/CLOCKS_PER_SEC<<"s"<<endl;
	//3
	starttime = clock();
	cout<<MAX3(arr,SIZE)<<endl;
	endtime = clock();
	cout<<"Use time: "<<(double)(endtime - starttime)/CLOCKS_PER_SEC<<"s"<<endl;
	//4
	starttime = clock();
	cout<<MAX4(arr,SIZE)<<endl;
	endtime = clock();
	cout<<"Use time: "<<(double)(endtime - starttime)/CLOCKS_PER_SEC<<"s"<<endl;

测试数据量2000:
在这里插入图片描述
测试数据量10000(alogorithm1 is dead)
在这里插入图片描述
测试数据量:100,000(algorithm2 is dead)
在这里插入图片描述
可以看到O(N)与O(NlogN)相差无几
来个100,000,000:
对不起,没了

运行时间中的对数

如果一个算法用常数时间O(1),将问题的大小削减一半1/2,那么该算法是O(log N)。
如果一个算法使用常数时间只是把问题减少一个常数,那这种算法是O(N)

对分查找(二分查找),循环次数最多为[log(N-1) ] +2,运行时间为O(log N)
插入操作需要O(N)时间

欧几里得算法(找最大公因数)
unsigned int 
Gcd(unsigned int M,unsigned int N)
{
	unsigned int rem;
	while(N>0)
	{
		rem = M%N;
		M=N;
		N=rem;
		}
	return M;}

可以证明,在两次迭代后余数最多为原来的一半,所以次数最多为2log N次=O(log N)

幂运算
long int
Pow(long int X,unsigned int N)
{
	if(N==0)
		return 1;
	if(N==1)
		return X;
	if( ISEVEN(N))
		return Pow(X*X,N/2);
	else
		return Pow(X*X,N/2)*X

可以通过X^8和 X^15等得出奇数幂和偶数幂与下一次分解计算的关系. 显然,乘法次数最多2log N次 = O(log N)

补:为什么是log N次呢,每次削减1/2,就好像128变为64、32、16、8、4、2、1,其实就是2^k=N,然后k就是次数,log N是以2为底(本书规定)

猜你喜欢

转载自blog.csdn.net/ZmJ6666/article/details/108597549