1.计算时间复杂度
c09c7f12-ba75-4f45-9423-0194e6462a51
1.1 时间复杂度概念
如何衡量一个算法的好与坏 ?
public static long Fib(int N){
if(N < 3){
return 1;
}
return Fib(N-1) + Fib(N-2);
}
比如,如何判断 上面这个递归解斐波那契数列 算法的好坏 ?
判断一个算法好坏可以从两个方面:
-
时间效率
-
空间效率
时间效率和空间效率 也被称为时间复杂度和空间复杂度
什么是时间复杂度 ?
在定义中,时间复杂度是一个数学函数,这个函数描述一个算法的运行时间
如何计算一个算法的运行时间 ?
掐表数 这个算法编写成的程序 在机器上的运行时间 ?这个方法不可行
-
很麻烦 每计算一个算法的时间复杂度 都需要上机测试
-
由于机器配置的不同 程序执行的时间也就不同
所以 我们需要抛去这些因素,用一个统一的标准去计算一个算法执行的时间
一个算法执行的时间与执行程序语句的数次有关联 一个程序执行语句的次数越多 需要的时间也就越多
且计算语句执行的次数不需要上机测试 也与机器的配置无关
所以,计算一个算法的运行时间 只需要去计算程序语句执行的次数
练习:
// 计算func1函数执行了多少次语句?
public static void func1(int N) {
int count = 0;
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
count++;
}
}
for (int k = 0; k < 2 * N ; k++) {
count++;
}
int M = 10;
while ((M--) > 0) {
count++;
}
System.out.println(count);
}
F(N) = n*n + 2n + 10
1.2 大O渐进表示法
计算一个算法运行时间时 是否需要计算出程序语句的具体的执行次数 ?
结论是不需要 以下例证明:
// 计算func1函数执行了多少次语句?
public static void func1(int N) {
int count = 0;
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
count++;
}
}
for (int k = 0; k < 2 * N ; k++) {
count++;
}
int M = 10;
while ((M--) > 0) {
count++;
}
System.out.println(count);
}
语句具体执行次数:F(N) = n * n + 2 * n + 10
N = 10 ——》F(N) = 100 + 20 + 10 N = 100 ——》F(N) = 10000 + 200 + 10 N = 1000 ——》F(N) = 1000000 + 2000 + 10
通过观察发现,当N越大 等式后两项(2n+10) 对结果的影响越小
当N无限大时,等式后两项可以完全忽略不计 因为去除这两项之和 对最后结果没有影响
所以,我们在计算执行语句次数时 只需要去计算大概的语句执行次数
什么是大O渐进表示法 ? 它用来做什么 ?
大O渐进表示法是一个描述函数渐进行为的数学符号,它用来描述时间和空间复杂度
什么是描述函数渐进行为 ?
F(N) = N * N
数据个数N = 10 ——》执行语句次数 = 100
数据个数N = 100 ——》执行语句次数 = 10000
数据个数N = 1000 ——》执行语句次数 = 1000000
F(N) = N * N ——大O渐进表示——》 O(N * N)表示数据个数为N,执行语句次数为N*N
例子:
// 1. 计算func2的时间复杂度?
void func2(int N) {
int count = 0;
for (int k = 0; k < 2 * N ; k++) {
count++;
}
int M = 10;
while ((M--) > 0) {
count++;
}
System.out.println(count);
}
F(N) = 2 * N + 10 ——》func2的时间复杂度为 O(N)
当N无限大时,系数2和+10都可以忽略不计
// 2. 计算func4的时间复杂度?
void func4(int N) {
int count = 0;
for (int k = 0; k < 100; k++) {
count++;
}
System.out.println(count);
}
执行常数次 ——》func2的时间复杂度为 O(1)
注意:在大O的渐进表示法中,O(1)表示执行常数次语句
// 3. 计算bubbleSort的时间复杂度?
void bubbleSort(int[] array) {
for (int end = array.length; end > 0; end--) {
boolean sorted = true;
for (int i = 1; i < end; i++) {
if (array[i - 1] > array[i]) {
Swap(array, i - 1, i);
sorted = false;
}
}
if (sorted == true) {
break;
}
}
}
最好情况: array = {1, 2, 3, 4, 5};
内循环遍历一次 ——》时间复杂度为 O(N)
最坏情况: array = {5, 4, 3, 2, 1};
4,3,2,1,5 —— 执行(n-1)次
3,2,1,4,5 —— 执行(n-2)次
.....
2,1,3,4,5 —— 执行2次
1,2,3,4,5 —— 执行1次
观察发现这是个等差数列,根据等差数列求和公式 ((首项 + 尾项) * 项数)/2
((n-1) + 1) * (n-1) / 2 = 1/2n^2 - 1/2n ——》时间复杂度为O(N^2)
一般情况下 最后的结果都取最坏的情况
最后一定需要注意 判断嵌套循环的时间复杂度需要根据代码逻辑进行判断
在这个冒泡排序例子中 核心逻辑在内循环 计算内循环的执行次数就可以得到最终的结果
如果数据量很大 冒泡排序绝大部分的执行次数都是在内循环上 外循环的执行次数对结果不会有影响
// 4. 计算binarySearch的时间复杂度?
public static int binarySearch(int[] array, int value) {
int begin = 0;
int end = array.length - 1;
while (begin <= end) {
int mid = begin + ((end-begin) / 2);
if (array[mid] < value)
begin = mid + 1;
else if (array[mid] > value)
end = mid - 1;
else
return mid;
}
return -1;
}
最坏情况: 最后一次才找到,n个数据查找了x次:
// 5. 计算阶乘递归factorial的时间复杂度?
long factorial(int N) {
return N < 2 ? N : factorial(N-1) * N;
}
在大部分情况下 递归执行次数 = 递归次数 * 每个递归中语句执行次数
// 计算斐波那契递归fibonacci的时间复杂度?
int fibonacci(int N) {
return N < 2 ? N : fibonacci(N-1)+fibonacci(N-2);
}