重要结论
如果 \(T_1(N) = 0(f(N))\) 且 \(T_2(N)=O(g(N))\) 那么
a. \(T_1(N) + T_2(N) = O(f(N) + g(N))\)
b. \(T_1(N) * T_2(N) = O(f(N)*g(N))\)
- 如果 T(N) 是一个 k 次多项式,则 \(T(N) = O(N^k)\)
3. 对于任意常数k,\(log^kN = O(N)\)
表示,对数增长的非常缓慢
运算时间计算
- 时间单元的计算
一次赋值
一个时间单元一次四则运算
一个时间单元初始化 i、测试 i<=N,和对 i 的自增运算隐含着开销。所有这些总的开销是初始化一个时间单元
2. 一般法则
法则1 - for循环 一个 for 循环运行时间至少是该 for 循环内部那些语句(包含测试)的运行时间乘以迭代的次数
法则2 - 嵌套的 for 循环 从里向外分析这些循环,在一组嵌套循环内部的一条语句总的运行时间为该语句的运行时间乘以该组所有的for循环的大小的乘积
法则3 - 顺序语句 将各个语句的运行时间求和即可,其中的最大值就是所得到的运行时间如果 O(N) + O(N^2) 则 总量是 O(N^2)
法则4 - if/else 语句 一个 if/else 语句的运行时间从不超过判断的运行时间再加上 S1 和 S2中运行时间长者的总的运行时间。
例子,关于最大子序列和的四种解法的时间运算
第一种
public static int maxSubSum1(int [] a)
{
int maxSum = 0; // O(1)
// 由 基于三重 嵌套 For循环中的 O(1) 语句组成
for(int i = 0; i < a.length; i++) // 循环大小为N
{
for(int j = i; j < a.length; j++) // 循环大小为 N - i,也可能N,假设最坏的打算
{
int thisSum = 0;
for(int k = i; k <= j; k++) // 循环大小为 j - i + 1,假设 N
thisSum += a[k];
// 所以 O(1 * N * N * N) = O(N3)
if(thisSum > maxSum)
maxSum = thisSum; // 这行 + 上一行总共开销 O(N2)
}
}
return maxSum;
}
算法运行时间为 \(O(N^3)\)
第二种
public static int maxSubSum2(int [] a)
{
int maxSum = 0; // O(1)
// 2重 for 循环,T = O(1 * N * N) = O(N2)
for(int i = 0; i < a.length; i++)
{
int thisSum = 0;
for(int j = i; j < a.length; j++)
{
thisSum += a[j];
if(thisSum > maxSum) // O(N2)
maxSum = thisSum;
}
}
return maxSum;
}
算法运行时间 \(O(N^2)\)
第三种
分而治之
private static int maxSumRec(int [] a, int left, int right)
{
if(left == right) // Base case
if(a[left] > 0)
return a[left]
else
return 0;
int center = (left + right) / 2;
// 下面两行求解大小为 N / 2 的子序列问题(假设 N为 偶数)
// 因此每行花费大概 T(N / 2)个时间单元,共花费 2T(N/2) 个时间单元
int maxLeftSum = maxSumRec(a, left, center);
int maxRightSum = maxSumRec(a, center + 1, right);
// 下面两个 For循环,以及四个赋值大概花费的时间为
// O(1) * 4 + O(N) * 2 ≈ O(N)
int maxLeftBorderSum = 0, leftBorderSum = 0;
for(int i = center; i >= left; i--)
{
leftBorderSum += a[i];
if(leftBorderSum > maxLeftBorderSum)
maxLeftBorderSum = leftBorderSum;
}
int maxRightBorderSum = 0, rightBorderSum = 0;
for(int i = center + 1; i <= right; i++)
{
rightBorderSum += a[i];
if(rightBorderSum > maxRightBorderSum)
maxRightBorderSum = rightBorderSum
}
return max3(maxLeftSum, maxRightSum, maxLeftBorderSum + maxRightBorderSum); // 常量 运行时间
}
public static int maxSubSum3(int []a)
{
return maxSumRc(a, 0, a.length - 1);
}
算法运行时间 2T(N/2) + O(N)
第四种
public static int maxSubSum4(int [] a)
{
int maxSum = 0, thisSum = 0;
for(int j = 0; j < a.length; j++) // O(N)
{
thisSum += a[j];
if(thisSum > maxSum) // O(logN)
maxSum = thisSum;
else if(thisSum < 0)
thisSum = 0;
}
return maxSum;
}
算法运行时间 O(Nlog N)
运行时间中的对数
一般法则
如果一个算法用常熟时间(O(1))将问题的大小削减为其一部分(通常是 1 / 2),那么该算法就是 O(log N)。另一方面,如果使用常数时间只是把问题减少一个常数的数量(如将问题减少1), 那么这种算法就是 O(N)的。
具有其特点的三个例子
- 折半查找
public sttatic>
int binarySearch(AnyType [] a, AnyType x)
{
int low = 0, high = a.length - 1;
while(low <= high)
{
int mid = (low + high) / 2;
// a[mid] < x 的话
if(a[mid].compareTo(x) < 0)
low = mid + 1;
else if (a[mid].compareTo(x) > 0)
high = mid - 1;
else
return mid; // found
}
return NOT_FOUND; // NOT_FOUND IS defined as -1;
}
- 欧几里得算法
public static long gcd(long m, long n)
{
while(n != 0)
{
long rem = m % n;
m = n;
n = rem;
}
return m;
}
两个整数的最大公因数(gcd) 是同时整除二者的最大整数
- 幂运算
public static long pow(long x, 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;
}