複雜度
文章目錄
記錄小碼哥的戀上數據結構與算法(第一季) - 複雜度
什麼是算法
什麼是算法
算法是用於解決特定問題的一系列的執行步驟
eg:解決兩數相加的問題
// 計算a和b的和
public static int plue(int a, int b){
return a + b;
}
eg:解決 n個數字的和 的問題
// 計算1+2+3+...+n
public static int sum(int n){
int result = 0;
for(int i = 1; i <= n; i++){
result += i;
}
return result;
}
使用不同算法,解決同一個問題,效率可能相差非常大。
比如:求第n個斐波那契數 Fibonacci number
解決了什麼問題 比如兩數相加問題 、求和問題
能解決問題就是算法呢
解決同一個問題 方法非常多 效率可能相差非常大
如何評判一個算法的好壞
如果單從執行效率上進行評估,可能會想到這麼一種方案
- 比較不同算法對同一組輸入的執行處理時間
- 這種方案也叫做:事後統計法
事後統計法 寫代碼去測試 和硬件有關 和輸入有關
上述方案有比較明顯的缺點:
- 執行時間嚴重依賴硬件以及運行時各種不確定的環境因素
- 必須編寫相應的測算代碼
- 測試數據的選擇比較難保證公正性
一般從以下維度來評估算法的優劣:
- 正確性、可讀性、健壯性(對不合理輸入的反應能力和處理能力)
- 時間複雜度(time complexity) 估算程序指令的執行次數(執行時間)
- 空間複雜度(space complexity)估算所需佔用的存儲空間
算法首先要保證 正確性、可讀性、健壯性 對不合理輸入的反應能力和處理能力
時間、空間優化
由於現在硬件發展的較好,一般情況下我們更側重於時間複雜度。
時間換空間
時空
package cn.liuawen;
public class demo {
// 計算 1+2+3+...+n 的和
public static int sum1(int n) {
int result = 0;
for (int i = 1; i <= n; i++) {
result += i;
}
return result;
}
// 計算 1+2+3+...+n 的和
public static int sum2(int n) {
return (1 + n) * n / 2;
}
public static void main(String[] args) {
System.out.println("sum1(10):"+sum1(10));
System.out.println("sum2(10):"+sum2(10));
}
}
大O表示法 Big O
一般用大O表示法來描述複雜度 它表示的是數據規模n對應的複雜度
忽略常數、係數、低階
- 9 >> O(1)
- 2n + 3 >> O(n)
- n2 + 2n + 6 >> O(n2)
- 4n3 + 3n2 + 22n + 100 >> O(n3)
- 寫法上,n3 等價於 n^3’
注意:大O表示法僅僅是一種粗略的分析模型 是一種估算 能幫助我們短時間內瞭解一個算法的執行效率
對數階的細節
對數階一般省略底數
- log29 ∗ log9n = og2n
常數忽略掉 log2n/log29 = log9n
所以 O(log2n) 、O(log9n) 統稱爲 O(logn)
常見的複雜度
logn
可以藉助函數生成工具對比複雜度的大小
https://zh.numberempire.com/graphingcalculator.php
Leetcode
一個用於練習算法的網站 力扣 leetcode
目的是練習算法
我們來練習一個道斐波那契數列吧
https://leetcode-cn.com/problems/fibonacci-number/
斐波那契數列複雜度分析
https://leetcode-cn.com/problems/fibonacci-number/
實現
/* 0 1 2 3 4 5
* 0 1 1 2 3 5 8 13 ....
*/
// O(2^n)
public static int fib1(int n) {
if (n <= 1) return n;
return fib1(n - 1) + fib1(n - 2);
}
// O(n)
public static int fib2(int n) {
if (n <= 1) return n;
int first = 0;
int second = 1;
for (int i = 0; i < n - 1; i++) {
int sum = first + second;
first = second;
second = sum;
}
return second;
}
public static int fib3(int n) {
if (n <= 1) return n;
int first = 0;
int second = 1;
while (n-- > 1) {
second += first;
first = second - first;
}
return second;
}
public static int fib4(int n) {
double c = Math.sqrt(5);
return (int) ((Math.pow((1 + c) / 2, n) - Math.pow((1 - c) / 2, n)) / c);
}
斐波那契數列-遞歸
// O(2^n)
public static int fib1(int n) {
if (n <= 1) return n;
return fib1(n - 1) + fib1(n - 2);
}
複雜度分析:
呈現的是指數級增長的趨勢
效率很低很低。
斐波那契數列-循環
不開闢任何空間,只使用循環完成。
// O(n)
public static int fib2(int n) {
if (n <= 1) return n;
int first = 0;
int second = 1;
for (int i = 0; i < n - 1; i++) {
int sum = first + second;
first = second;
second = sum;
}
/*
// 也可以使用while循環
while (n-- > 1) {
second += first;
first = second - first;
}
*/
return second;
}
速度變快了,內存消耗還是很多…
開闢新的數組空間,用空間換時間。
public static int fib3(int n){
if(n <= 1) return n;
int[] fib = new int[n+1];
fib[0] = 0;
fib[1] = 1;
for(int i = 2; i < fib.length; i++){
fib[i] = fib[i-1] + fib[i-2];
}
return fib[n];
}
fib函數的時間複雜度分析
有時候算法之間的差距,往往比硬件方面的差距還要大
算法之間的差距 比硬件差多了
寫代碼要考慮 算法
多個數據規模的情況
時間複雜度:O(n + k)
public static void test(int n, int k){
for(int i = 0; i < n; i++){
System.out.println("test");
}
for (int i = 0; i < k; i++){
System.out.println("test");
}
}
算法的優化方向
- 用盡量少的存儲空間
- 用盡量少的執行步驟(執行時間)
- 根據情況,可以
- 空間換時間
- 時間換空間
更多知識
更多複雜度相關的知識,會在後續講解數據結構、算法的過程中穿插
最好、最壞複雜度
均攤複雜度
複雜度震盪
平均複雜度
…