ch02.算法分析

2.0.引言

“计算任何事情不要超过一次”

在这里插入图片描述

2.1.数学基础

四个定义:

  • T(N)的增长率小于等于f(N)的增长率.f(N)是T(N)的上界. T(N) <=f(N) --> T(N)=O(f(N))
  • T(N)的增长率大于等于g(N)的增长率. g(N)是T(N)的下界. T(N)>=g(N) --> T(N)=omega(g(N))
  • T(N)的增长率等于h(N). T(N)=h(N) --> T(N)=theta(h(N))
  • T(N)的增长率小于p(N)的增长率. T(N)<p(N) --> T(N)=o(p(N))

可以通过求极限来确定:lim_{n->inf}{T(N)/f(N)}.

记忆:小o是小于,大O是小于等于,Omega是大于等于,theta是等于.

三个法则:

  • 如果T1(N)=O(f(N))且T2(N)=O(g(N)),那么有:
    (a).T1(N)+T2(N) = max{O(f(N)), O(g(N))}
    (b).T1(N)*T2(N) =O(f(N))* O(g(N))
  • 如果T(N)是一个k次多项式,则T(N)=theta(N^k)
  • 对任意常数k,log^k(N)=O(N)

2.2.运行时间计算

  • FOR循环:一次for循环的运行时间最多是该for循环内语句(包括测试)的运行时间乘以迭代得次数.
  • 嵌套的FOR循环:从里向外分析,最里面一条语句的总运行时间为该语句运行时间乘以该组所有for循环大小的乘积.

eg:

for(i=0; i<N; i++)
    for(j=0; j<N; j++)
    {
        k++;
    }

1*N*N --> O(N^2)

  • 顺序语句:将各个语句的运行时间求和即可。这意味着其中最大值就是所得的运行时间,原因见法则1.(a)

eg:

for(i=0; i<N; i++)
    k++;
	
for(i=0; i<N; i++)
    for(j=0; j<N; j++)
    {
        k++;
    }

O(N)+O(N^ 2) = O(N^2)

  • IF/ELSE语句:一个if/else运行时间从不超过判断再加上S1和S2中运行时间长者的总运行时间。
if(condition)
    S1
else
    S2
  • 分析策略:由内向外展开,函数调用也一样,先从被调用函数开始.
  • 递归:可以看做一个for循环。
    eg1:
long int Factorial(int N)
    {
	    if(N<=1)  return 1;
		else return N*Factorial(N-1);
	}

2*N --> O(N)

eg2:

long int Fib(int N)
    {
	    if(N<=1)  return 1;
		else return Fib(N-1) + Fib(N-2);
	}

令T(N)为函数Fib(N)的运行时间,T(N)=T(N-1) + T(N-2) +2,,又因为Fib(N) = Fib(N-1)+Fib(N-2),由数学归纳法可得,T(N)>=Fib(N)>=(3/2)^N (N>4时),可以看出这个函数运行时间是指数型增长.

所以:
T(0)=T(1)=1
T(N)>=Fib(N)>=(3/2)^N (N>4)

2.3.运行时间中的对数

除分治算法外,可将对数最出现的规律概括为一般法则:

如果一个算法用常数时间O(1)将问题的大小削减为其一部分(通常是1/2),那么该算法就是O(logN)。另一方面,如果使用常数时间只是把问题减少一个常数(如将问题减1),那么这种算法就是O(N)的。
如:二分查找法
在这里插入图片描述
对数的底不重要,只是相差一个常数:
在这里插入图片描述

O(nlogn):

在这里插入图片描述

O(sqrt(n)):

在这里插入图片描述

2.4.demo

#include <iostream>
#include <algorithm>
using namespace std;


//求最大子序列之和的问题,复杂度由内向外,O(1*N*N*N) = O(N^3)
int MaxSubsequenceSum1(const int A[], int N)
{
	int ThisSum, MaxSum, i, j, k;

	MaxSum = 0;
	for(i=0;i<N;i++)//循环大小为N
		for (j=i;j<N;j++)//循环大小为N-i,计算时使用N
		{
			ThisSum = 0;
			for (k=i;k<=j;k++)//循环大小为j-i+1,计算时使用N
			{
				ThisSum += A[k];
			}
			if (ThisSum>MaxSum)//这两行循环为两层,最大复杂度O(N^2),根据法则忽略
			{
				MaxSum = ThisSum;
			}
		}
	return MaxSum;
}



//复杂度O(N ^ 2)
int MaxSubsequenceSum2(const int A[], int N)
{
	int ThisSum, MaxSum, i, j;
	MaxSum = 0;
	for (i=0;i<N;i++)
	{
		ThisSum = 0;
		for (j=i;j<N;j++)
		{
			ThisSum += A[j];
			if (ThisSum> MaxSum)
			{
				MaxSum = ThisSum;
			}
		}
	}
	return MaxSum;
}

int max3(int i, int j, int k)
{
	int tmp1 = i > j ? i : j;
	int tmp2 = j > k ? j : k;
	return tmp1 > tmp2 ? tmp1 : tmp2;
}
//分治策略(divide-and-conquer),递归解法
//时间复杂度分析见教材:
//T(1) = 1
//T(N) = 2T(N/2) + O(N) --> T(N) = O(NlogN)  线性级
static int MaxSubSum(const int A[], int Left, int Right)
{
	int maxLeftSum, maxRightSum, MaxSum;
	int maxLeftBorderSum, maxRightBorderSum;//第三种情况,前半部分包含最后一个元素,后半部分包含第一个元素
	int leftBorderSum, rightBorderSum;
	int center, i;


	if (Left == Right)			//递归出口,base case.当Left==Right时,那么只剩一个元素,并且当该元素非负时,它就是最大和子序列
		if (A[Left] > 0)
			return A[Left];
		else
			return 0;

	center = (Left + Right) / 2;
	maxLeftSum = MaxSubSum(A, Left, center);//递归调用,一直分
	maxRightSum = MaxSubSum(A, center + 1, Right);//递归调用,一直分

	//第三种情况,前半部分
	maxLeftBorderSum = 0; leftBorderSum = 0;
	for (i = center; i>= Left;i--)
	{
		leftBorderSum += A[i];
		if (leftBorderSum > maxLeftBorderSum)
		{
			maxLeftBorderSum = leftBorderSum;
		}
	}

	//第三种情况,后半部分
	maxRightBorderSum = 0; rightBorderSum = 0;
	for (i = center+1; i<= Right; i++)
	{
		rightBorderSum += A[i];
		if (rightBorderSum > maxRightBorderSum)
		{
			maxRightBorderSum = rightBorderSum;
		}
	}

	return max3(maxLeftSum, maxRightSum, (maxLeftBorderSum + maxRightBorderSum));

}

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


//这个和直接找到大于0的相加有什么区别呢,同样可以online啊
int MaxSubsequenceSum4(const int A[], int N)
{
	int ThisSum, MaxSum, j;
	ThisSum = MaxSum = 0;
	for (j=0; j<N; j++)
	{
		ThisSum += A[j];

		if (ThisSum > MaxSum)
			MaxSum = ThisSum;
		else if(ThisSum < 0)
			ThisSum = 0;
	}
	return MaxSum;
}


//二分法查找
int binarySearch(const int A[], int X, int N)
{
	int low, high, mid;
	low = 0; high = N - 1;
	while (low<=high)
	{
		mid = (low + high) / 2;
		if (A[mid] < X) low = mid + 1;
		else if (A[mid] > X) high = mid - 1;
		else return mid;
	}
	return -1;
}

//欧几里得算法
unsigned int Gcd(unsigned int M, unsigned N)
{
	unsigned int Rem;
	while (N > 0)
	{
		Rem = M % N;//连续取余,最后的非零余数则为最大公因数
		M = N;
		N = Rem;
	}
	return M;
}


bool isEven(long int X)
{
	if (X % 2 == 0) return true;
	else return false;
}

//递归求解
//偶数:  X^N = X^(N/2)*X^(N/2)
//奇数:  X^N = X^((N-1)/2)) *  X^((N-1)/2)) * X
//复杂度O(logN)
long int pow(long int X, unsigned int N)
{
	if (N == 0) return 1;//base case
	//if (N == 1) return X;//base case
	//Even偶数
	if (isEven(N)) return pow(X*X, N / 2);
	else return pow(X*X, N / 2)*X;
}




template<typename T>
T RandInt(T _min, T _max)
{
	T temp;
	if (_min > _max)
	{
		temp = _max;
		_max = _min;
		_min = temp;
	}
	return rand() % (_max - _min + 1) + _min;
	//return rand() / (double)RAND_MAX *(_max - _min + 1) + _min;
}


//复杂度O(N^3)
void randomReplacement1()
{
	int A[10] = { 1,2,3,4,5,6,7,8,9,10 };
	const int N = sizeof(A) / sizeof(int);
	for (int i = 0; i < N; i++)
	{
		A[i] = RandInt(1, N);
		for (int j = 0; j < i; j++)
		{
			while (A[j] == A[i])
			{
				A[i] = RandInt(1, N);
				j = 0;//特别注意这里,重新赋值后,之前的元素也需要重新检查一遍
			}
		}
	}

	cout << "randomReplacement1: A[] = " << endl;
	for (int i = 0; i < sizeof(A) / sizeof(int); i++)
	{
		cout << A[i] << " ";
	}

}


//复杂度O(N^2)
void randomReplacement2()
{
	int A[10] = { 1,2,3,4,5,6,7,8,9,10 };
	const int N = sizeof(A) / sizeof(int);
	int used[N] = { 0 };//再初始化一个数组进行判别
	for (int i = 0; i < N; i++)
	{
		A[i] = RandInt(1, N);
		while (used[A[i] - 1] == 1)// -1  将其生成数转换为下标,只要这个被使用了就继续生成
		{
			A[i] = RandInt(1, N);
		}
		used[A[i] - 1] = 1;
	}

	cout << "randomReplacement2: A[] = " << endl;
	for (int i = 0; i < sizeof(A) / sizeof(int); i++)
	{
		cout << A[i] << " ";
	}

}




void swap(int *px, int *py)
{
	int temp;
	temp = *px;
	*px = *py;
	*py = temp;
}
//复杂度O(N)
void randomReplacement3()
{
	int  A[10] = { 0 };
	const int N = sizeof(A) / sizeof(int);
	for (int i = 0; i < N; i++)
	{
		A[i] = i + 1;
	}
	//int sizeA = sizeof(A) / sizeof(int);
	//while (sizeA--)
	//{
	//	A[sizeA] = sizeA + 1;
	//}
	for (int i = 1; i < N; i++)
	{
		swap(&A[i], &A[RandInt(0, i)]);//这个真做到了只计算一遍
	}
	cout << "randomReplacement3: A[] = " << endl;
	for (int i = 0; i < N; i++)
	{
		cout << A[i] << " ";
	}
}



//Horner法则
//数组一直传递不过来,什么鬼!!!
long int Horner(const int A[],const int X)
{
	int N = sizeof(A) / sizeof(int);
	cout << "A[] = "<<endl;
	for (int i = 0; i<N;i++)
	{
		cout << A[i] << " ";
	}

	int poly = 0;
	for (int i = N; i >= 0; i--)
	{
		poly = X * poly + A[i];
	}
	return poly;
}


int main(int argc, char** argv)
{
	//int A[] = { -1,11,-4,13,-5,-2 };
	//int N = sizeof(A)/sizeof(int);
	//int MaxSum = MaxSubsequenceSum1(A, N);
	//int MaxSum = MaxSubsequenceSum2(A, N);
	//int MaxSum = MaxSubsequenceSum3(A, N);
	//int MaxSum = MaxSubsequenceSum4(A, N);

	//cout << "MaxSum = " << MaxSum << endl;



	/***************几个对数特点的例子*************/

	//对分查找,二分法
	//int B[] = { 1 ,2, 3, 4, 5, 6, 7, 8, 9, 10 };
	//int N = sizeof(B) / sizeof(int);
	//int searchIndx = binarySearch(B, 6, N);
	//cout << "searchIndx = " << searchIndx << endl;


	//欧几里得算法,求解两个证书的最大公因数
	//cout << "Gcd(1989,1590) = " << Gcd(1989, 1590) << endl;

	//幂运算
	//cout << "pow(2,10) = " << pow(2, 10) << endl;


	//习题2.7
	//randomReplacement1();
	//randomReplacement2();
	//randomReplacement3();


	//习题2.10
	//int A[] = {4,8,1,2};
	//int X = 3;
	//cout << "Horner(A, X )= " << Horner(A, X) << endl;

	 
	system("pause");
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值