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;
}