数据结构和算法之时间复杂度
文章目錄
- 前言
- 1 時間復雜度
- 1.1 分析方法
- 1.2 常見時間復雜度
- 2.3 最壞時間復雜度、最好時間復雜度、平均時間復雜度
- 2.空間復雜度
前言
? ? ? ?學習數據結構和算法,并不是為了死記硬背幾個知識點。我們的目的是建立時間復雜度、空間復雜度意識,寫出高質量的代碼,能夠設計基礎架構,提升編程技能,訓練邏輯思維,積攢人生經驗,以此獲得工作回報,實現你的價值,完善你的人生。
? ? ? ?掌握了數據結構與算法,你看待問題的深度,解決問題的角度就會完全不一樣。因為這樣的你,就像是站在巨人的肩膀上,拿著生存利器行走世界。數據結構與算法,會為你的編程之路,甚至人生之路打開一扇通往新世界的大門。
對數據結構和算法,要學習它的來歷、自身的特點、適合解決的問題和實際的應用場景。
最常用的、最基礎數據結構與算法:
10 個數據結構:數組、鏈表、棧、隊列、散列表、二叉樹、堆、跳表、圖、Trie 樹;
10 個算法:遞歸、排序、二分查找、搜索、哈希算法、貪心算法、分治算法、回溯算法、動態規劃、字符串匹配算法。
我們需要一個不用具體測試就可以粗略地估計算法的執行效率的方法。這就是時間、空間復雜度分析方法,即大 O 復雜度表示法。
1 時間復雜度
? ? ? ?所有代碼的執行時間 T(n) 與每行代碼的執行次數 n 成正比,因此我們用T(n) = O(f(n))來表示代碼執行時間,即大 O 時間復雜度表示法。
? ? ? ?大O 時間復雜度實際上并不具體表示代碼真正的執行時間,而是表示代碼執行時間隨數據規模增長的變化趨勢。n 很大時,你可以把它想象成 10000、100000。而公式中的低階、常量、系數三部分并不左右增長趨勢,所以都可以忽略。我們只需要記錄一個最大量級就可以了。
1.1 分析方法
其中第 2、3 行代碼都是常量級的執行時間,與 n 的大小無關,所以對于復雜度并沒有影響。循環執行次數最多的是第 4、5 行代碼,所以這塊代碼要重點分析。前面我們也講過,這兩行代碼被執行了 n 次,所以總的時間復雜度就是 O(n)。
? ? ? ?這個代碼分為三部分,分別是求 sum_1、sum_2、sum_3。我們可以分別分析每一部分的時間復雜度,然后把它們放到一塊兒,再取一個量級最大的作為整段代碼的復雜度。
? ? ? ?第一段的時間復雜度是多少呢?這段代碼循環執行了 100 次,所以是一個常量的執行時間,跟 n 的規模無關。這里再強調一下,即便這段代碼循環 10000 次、100000 次,只要是一個已知的數,跟 n 無關,照樣也是常量級的執行時間。當 n 無限大的時候,就可以忽略。盡管對代碼的執行時間會有很大影響,但是回到時間復雜度的概念來說,它表示的是一個算法執行效率與數據規模增長的變化趨勢,所以不管常量的執行時間多大,我們都可以忽略掉。因為它本身對增長趨勢并沒有影響。
? ? ? ?那第二段代碼和第三段代碼的時間復雜度是 O(n) 和 O(n2)。綜合這三段代碼的時間復雜度,我們取其中最大的量級。所以整段代碼的時間復雜度就為 O(n2)。也就是說:總的時間復雜度就等于量級最大的那段代碼的時間復雜度 。
嵌套代碼的復雜度等于嵌套內外代碼復雜度的乘積
int cal(int n) {int ret = 0; int i = 1;for (; i < n; ++i) {ret = ret + f(i);} } int f(int n) {int sum = 0;int i = 1;for (; i < n; ++i) {sum = sum + i;} return sum;}? ? ? ?我們單獨看 cal() 函數。假設 f() 只是一個普通的操作,那第 4~6 行的時間復雜度就是T1(n) = O(n)。但 f() 函數本身不是一個簡單的操作,它的時間復雜度是 T2(n) = O(n),所以,整個 cal() 函數的時間復雜度就是,T(n) = T1(n) * T2(n) = O(n*n) = O(n2)。
1.2 常見時間復雜度
常量級 O(1)
只要代碼的執行時間不隨 n 的增大而增長,這樣代碼的時間復雜度我們都記作 O(1)。或者說一般情況下,只要算法中不存在循環語句、遞歸語句,即使有成千上萬行的代碼,其時間復雜度也是Ο(1)。
對數階 O(logn)
對數階時間復雜度非常常見,同時也是最難分析的一種時間復雜度。
其實相當于求解 2x=n 中 x的值, 也就是x=log2n,所以這段代碼的時間復雜度就是 O(log2n)。
i=1;while (i <= n) {i = i * 3; }? ? ? ?很簡單就能看出來,這段代碼的時間復雜度為 O(log3n)。實際上,不管是以 2 為底、以 3 為底,還是以 10 為底,我們可以把所有對數階的時間復雜度都記為 O(logn)。為什么呢?我們知道,對數之間是可以互相轉換的,log3n 就等于 log32 * log2n,所以 O(log3n) = O(C * log2n),其中 C=log32 是一個常量。基于我們前面的一個理論:在采用大 O 標記復雜度的時候,可以忽略系數,即 O(Cf(n)) = O(f(n))。所以,O(log2n) 就等于 O(log3n)。因此,在對數階時間復雜度的表示方法里,我們忽略對數的“底”,統一表示為 O(logn)。
O(n)
O(nlogn)
像快排的時間復雜度就是O(nlogn)。
n2、n3、nk等k次方階
指數階O(2n) 、 階乘階O(n!)
這兩個屬于非多項式量級,我們把時間復雜度為非多項式量級的算法問題叫作 NP(Non-Deterministic Polynomial,非確定多項式)問題。非多項式時間復雜度的算法其實是非常低效的算法,一般很少遇到。
O(m+n)、O(m*n)
從下面代碼中可以看出,m 和 n 是表示兩個數據規模。我們無法事先評估 m 和 n 誰的量級大,所以我們在表示復雜度的時候,就不能簡單地利用加法法則,省略掉其中一個。所以下面代碼的時間復雜度就是 O(m+n)。
2.3 最壞時間復雜度、最好時間復雜度、平均時間復雜度
? ? ? ?顧名思義,最好情況時間復雜度就是,在最理想的情況下,執行這段代碼的時間復雜度。同理,最壞情況時間復雜度就是,在最糟糕的情況下,執行這段代碼的時間復雜度。
? ? ? ?而平均時間復雜度實際上在概率論中,應該叫加權平均時間復雜度或者期望時間復雜度。很多時候,我們使用一個復雜度就可以滿足需求了。只有同一塊代碼在不同的情況下,時間復雜度有量級的差距,我們才會使用這三種復雜度表示法來區分。
? ? ? ?均攤時間復雜度,對一個數據結構進行一組連續操作中,大部分情況下時間復雜度都很低,只有個別情況下時間復雜度比較高,而且這些操作之間存在前后連貫的時序關系,這個時候,我們就可以將這一組操作放在一塊兒分析,看是否能將較高時間復雜度那次操作的耗時平攤到其他那些時間復雜度比較低的操作上。而且,在能夠應用均攤時間復雜度分析的場合,一般均攤時間復雜度就等于最好情況時間復雜度。
2.空間復雜度
表示算法的存儲空間與數據規模之間的增長關系
void print(int n) {int i = 0;int[] a = new int[n];for (i; i <n; ++i) {a[i] = i * i;}for (i = n-1; i >= 0; --i) {print out a[i]} }? ? ? ?跟時間復雜度分析一樣,我們可以看到第 2 行代碼中,我們申請了一個空間存儲變量 i,但是它是常量階的,跟數據規模 n 沒有關系,所以我們可以忽略。第 3 行申請了一個大小為 n 的 int 類型數組,除此之外,剩下的代碼都沒有占用更多的空間,所以整段代碼的空間復雜度就是 O(n)。
? ? ? ?我們常見的空間復雜度就是 O(1)、O(n)、O(n2),像 O(logn)、O(nlogn) 這樣的對數階復雜度平時都用不到。
總結
以上是生活随笔為你收集整理的数据结构和算法之时间复杂度的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JVM监控工具有哪些
- 下一篇: (二)链表