目錄
1 -> 算法效率
1.1 -> 如何衡量一個算法的好壞?
1.2 -> 算法的復雜度
2 -> 時間復雜度
2.1 -> 時間復雜度的概念
2.2 -> 大O的漸進表示法
2.3 -> 常見時間復雜度計算
3 -> 空間復雜度
4 -> 常見復雜度對比
1 -> 算法效率
1.1 -> 如何衡量一個算法的好壞?
對于以下斐波那契數列:
#define _CRT_SECURE_NO_WARNINGS#include <iostream>
using namespace std;long long fib(int N)
{if (N < 3)return 1;return fib(N - 1) + fib(N - 2);
}int main()
{return 0;
}
用遞歸實現斐波那契數列,看上去代碼十分簡潔,但簡潔一定就是好算法嗎?如何衡量一個算法的好壞?
1.2 -> 算法的復雜度
算法在編寫成可執行程序后,運行時需要耗費時間資源和空間(內存)資源。因此衡量一個算法的好壞,一般是從時間和空間兩個維度來衡量的,即時間復雜度和空間復雜度。
時間復雜度主要衡量一個算法的運行快慢,而空間復雜度主要衡量一個算法運行所需要的額外空間。在計算機發展的早期,計算機存儲容量很小。所以對于空間復雜度很是在乎。但是經過計算機行業的迅速發展,計算機的存儲容量已經達到了很高的程度。所以我們如今已經不需要特別關注一個算法的空間復雜度。
2 -> 時間復雜度
2.1 -> 時間復雜度的概念
定義:在計算機科學中,算法的時間復雜度是一個函數,它定量描述了該算法的運行時間。一個算法執行所耗費的時間,從理論上來講,是不能算出來的,只有把程序放在機器上跑起來才能知道。但是我們需要每個算法都上機測試嗎?固然可以都上機測試,但是這很麻煩,所以才有了時間復雜度這個分析方法。一個算法所花費的時間與其中語句的執行次數成正比例,算法中的基本操作的執行次數,為算法的時間復雜度。
即:找到某條語句與問題規模N之間的數學表達式,就是算出了該算法的時間復雜度。
#define _CRT_SECURE_NO_WARNINGS#include <iostream>
using namespace std;// 請計算一下Func1中++count語句總共執行了多少次?
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--)++count;cout << count << endl;
}int main()
{return 0;
}
Func1執行的基本操作數:
-> N = 10 F(N) = 130-> N = 100 F(N) = 10210-> N = 1000 F(N) = 1002010
實際我們在計算時間復雜度時,并不一定要計算精確的執行次數,只需要大概執行次數,所以我們使用大O的漸進表示法。
2.2 -> 大O的漸進表示法
大O符號(Big O notation):是用于描述函數漸進行為的數學符號。
推導大O階方法:
- 在常數1取代運行時間中的所有加法常數;
- 在修改后的運行次數函數中,只保留最高階項;
- 如果最高階項存在且不為1,則去除與這個項目相乘的常數。得到的結果就是大O階。
使用大O的漸進表示法后,Func1的時間復雜度為:
-> N = 10 F(N) = 100
-> N = 100 F(N) = 10000-> N = 1000 F(N) = 1000000
通過上面我們會發現大O的漸進表示法去掉了那些對結果影響不大的項,簡潔明了的表示出了執行次數。?
另外有些算法的時間復雜度存在最好、平均和最壞情況:
- 最好情況:任意輸入規模的最小運行次數(下界)
- 平均情況:任意輸入規模的期望運行次數
- 最壞情況:任意輸入規模的最大運行次數(上界)
例如:在一個長度為N的數組中搜索一個數據x
- 最好情況:1次找到
- 平均情況:N / 2次找到
- 最壞情況:N次找到
在實際中一般情況關注的是算法的最壞運行情況,所以數組中的搜索數據時間復雜度為:
2.3 -> 常見時間復雜度計算
實例1:
// 計算Func2的時間復雜度?
void Func2(int N)
{int count = 0;for (int k = 0; k < 2 * N; ++k)++count;int M = 10;while (M--)++count;cout << count << endl;
}
實例2:
// 計算Func3的時間復雜度?
void Func3(int N, int M)
{int count = 0;for (int k = 0; k < M; ++k)++count;for (int k = 0; k < N; ++k)++count;cout << count << endl;
}
實例3:
// 計算Func4的時間復雜度?
void Func4(int N)
{int count = 0;for (int k = 0; k < 100; ++k)++count;cout << count << endl;
}
實例4:
// 計算strchr的時間復雜度?
const char* strchr(const char* str, int character);
實例5:
// 計算BubbleSort的時間復雜度?
void BubbleSort(int* a, int n)
{assert(a);for (size_t end = n; end > 0; --end){int exchange = 0;for (size_t i = 1; i < end; ++i){if (a[i - 1] > a[i]){Swap(&a[i - 1], &a[i]);exchange = 1;}}if (exchange == 0)break;}
}
實例6:
// 計算BinarySearch的時間復雜度?
int BinarySearch(int* a, int n, int x)
{assert(a);int begin = 0;int end = n - 1;// [begin, end]:begin和end是左閉右閉區間,因此有=號while (begin <= end){int mid = begin + ((end - begin) >> 1);if (a[mid] < x)begin = mid + 1;else if (a[mid] > x)end = mid - 1;elsereturn mid;}return -1;
}
實例7:
// 計算階乘遞歸Fac的時間復雜度?
long long Fac(size_t N)
{if (0 == N)return 1;return Fac(N - 1) * N;
}
實例8:
// 計算斐波那契遞歸fib的時間復雜度?
long long fib(size_t N)
{if (N < 3)return 1;return fib(N - 1) + fib(N - 2);
}
答案及分析:
1. 實例1基本操作執行了2N+10次,通過推導大O階方法知道,時間復雜度為 O(N)2. 實例2基本操作執行了M+N次,有兩個未知數M和N,時間復雜度為 O(N+M)3. 實例3基本操作執行了10次,通過推導大O階方法,時間復雜度為 O(1)4. 實例4基本操作執行最好1次,最壞N次,時間復雜度一般看最壞,時間復雜度為 O(N)5. 實例5基本操作執行最好N次,最壞執行了(N*(N+1)/2次,通過推導大O階方法+時間復雜度一般看最壞,時間復雜度為 O(N^2)6. 實例6基本操作執行最好1次,最壞O(logN)次,時間復雜度為 O(logN) ps:logN在算法分析中表示是底數為2,對數為N。有些地方會寫成lgN。7. 實例7通過計算分析發現基本操作遞歸了N次,時間復雜度為O(N)。8. 實例8通過計算分析發現基本操作遞歸了2^N次,時間復雜度為O(2^N)。
3 -> 空間復雜度
空間復雜度也是一個數學表達式,是對一個算法在運行過程中臨時占用存儲空間大小的量度。
空間復雜度不是程序占用了多少byte的空間,因為意義不大,所以空間復雜度算的是變量的個數。空間復雜度計算規則基本與時間復雜度類似,也是使用大O漸進表示法。
注意:函數運行時所需要的棧空間(存儲參數、局部變量、一些寄存器信息等)在編譯期間已經確定好了,因此空間復雜度主要通過函數在運行時顯式申請的額外空間來確定。
實例1:
// 計算BubbleSort的空間復雜度?
void BubbleSort(int* a, int n)
{assert(a);for (size_t end = n; end > 0; --end){int exchange = 0;for (size_t i = 1; i < end; ++i){if (a[i - 1] > a[i]){Swap(&a[i - 1], &a[i]);exchange = 1;}}if (exchange == 0)break;}
}
實例2:
// 計算fib的空間復雜度?
// 返回斐波那契數列的前n項
long long* fib(size_t n)
{if (n == 0)return NULL;long long* arr = (long long*)malloc((n + 1) * sizeof(long long));arr[0] = 0;arr[1] = 1;for (int i = 2; i <= n; ++i)arr[i] = arr[i - 1] + arr[i - 2];return arr;
}
實例3:
// 計算階乘遞歸Fac的空間復雜度?
long long Fac(size_t N)
{if (N == 0)return 1;return Fac(N - 1) * N;
}
答案及分析:
1. 實例1使用了常數個額外空間,所以空間復雜度為 O(1)2. 實例2動態開辟了N個空間,空間復雜度為 O(N)3. 實例3遞歸調用了N次,開辟了N個棧幀,每個棧幀使用了常數個空間。空間復雜度為O(N)
4 -> 常見復雜度對比
一般算法的常見復雜度:
5201314 | O(1) | 常數階 |
3n + 4 | O(n) | 線性階 |
3n ^ 2 + 4n + 5 | O(n ^ 2) | 平方階 |
3log(2)n + 4 | O(logn) | 對數階 |
2n +?3nlog(2)n + 4 | O(nlogn) | nlogn階 |
n ^ 3 + n ^ 2 + 3n + 4 | O(n ^ 3) | 立方階 |
2 ^ n | O(2 ^ n) | 指數階 |
感謝大佬們支持!!!
互三啦!!!