1、複雜度分析(1)
- 複雜度分析相比於過後統計法,可以不依賴於運行環境和數據規模的大小,而計算程序的運行效率。
- 時間複雜度大O表示法 T(n) = O(f(n)) n 表示數據規模的大小;f(n) 表示每行代碼執行的次數總和。O表示代碼的執行時間 T(n) 與 f(n) 表達式成正比,因爲f(n)能夠忽略低階和常數項,因此計算的技巧:只計算循環最多、量級最大的那段代碼;嵌套代碼的複雜度等於內外複雜度的乘積
- 經常使用時間複雜度 O(1) 、 O(logn)、 O(nlogn)、O(m+n)、O(m*n)
- 空間複雜度分析: 常見的空間複雜度就是 O(1)、O(n)、O(n2)。咱們說空間複雜度的時候,是指除了本來的數據存儲空間外,算法運行還須要額外的存儲空間。
2、複雜度分析(2)
幾種時間複雜度分析算法
- 最好和最壞狀況時間複雜度,看兩種極端狀況的複雜度
- 平均時間複雜度, 用機率論,求平均狀況的複雜度。即各類狀況下出現的機率乘以各類狀況下的複雜度,再加起來。獲得 加權平均值。
- 使用場景: 多時候,咱們使用一個複雜度就能夠知足需求了。只有同一塊代碼在不一樣的狀況下,時間複雜度有量級的差距,咱們纔會使用這三種複雜度表示法來區分。
- 均攤時間複雜度:不一樣時間複雜度狀況的出現有必定的規律,看是否能將較高時間複雜度那次操做的耗時,平攤到其餘那些時間複雜度比較低的操做上。並且,在可以應用均攤時間複雜度分析的場合,通常均攤時間複雜度就等於最好狀況時間複雜度。
3、數組
- 定義: 線性表結構(邏輯上) + 連續的存儲空間(存儲方式上) + 存儲相同的數據類型。
數組支持隨機訪問,根據下標隨機訪問的時間複雜度爲 O(1)。由查找第i個數據的公式爲:
a[i]_address = base_address + i * data_type_size
二位數組的尋址公式爲,對於數組 m*n
a[i]_address = base_address + (i n + j) data_type_size
- 缺點: 插入、刪除須要移動數據,複雜度O(n)。
- 高級語言中的‘數組’每每是對數組進行封裝,或者使用非數組的方式去實現(JavaScript的數組實際上是對象),例如封裝了刪除、插入等操做的細節、支持動態擴容。
4、鏈表(上)
- 定義: 線性表結構(邏輯上) + 非連續的存儲空間(存儲方式上)
- 分類: 單鏈表、雙向鏈表、循環鏈表
- 循環鏈表是一種特殊的單向或雙向鏈表。
- 鏈表相比數組,隨機訪問更麻煩。數組的劣勢在於須要連續的存儲空間,擴容須要作數據遷移。鏈表更耗空間。
- 雙向鏈表對於單向鏈表的優點:在刪除特定指向的節點或者在特定指向的節點錢插入時,不用遍歷查找其前驅節點。
5、鏈表(下)
- 理解指針或引用的含義: 指針是變量(鏈表上的結點)的地址,它能夠訪問結點。而結點上又存儲着一個指針地址。因此指針地址既是結點上的一個數據,又能夠經過它訪問另外一個結點。
- 警戒指針丟失和內存泄漏:這一點要注意的是指針指向語句的順序
- 利用哨兵簡化實現難度: 增長無用的哨兵結點,使得操做更統一,不須要對鏈表爲空(對於插入)或只有一個 元素(對於刪除)的狀況做額外判斷。
- 重點留意邊界條件處理,4個邊界條件:
若是鏈表爲空時,代碼是否能正常工做?
若是鏈表只包含一個節點時,代碼是否能正常工做?
若是鏈表只包含兩個節點時,代碼是否能正常工做?
代碼邏輯在處理頭尾節點時是否能正常工做?
- 舉例畫圖, 釋放一些腦容量,留更多的給邏輯思考
6、棧
1、棧是一種「操做受限」的線性表,只容許在一端插入和刪除數據。可使用數組或鏈表來實現。理論上數組或鏈表能夠替代棧的功能,而棧是對特定場景的抽象,暴露的可操做接口少,比較可控。
2、複雜度分析數組
- 普通棧: 時間空間複雜度O(1)
- 可動態擴容的數組實現的棧: 入棧均攤時間複雜度就爲 O(1)
3、應用:函數調用棧、編譯器求和、檢查括號匹配數據結構
7、隊列
1、隊列和棧相似,是一種操做受限的線性表數據結構
2、隊列使用數組實現,隨着入隊和出隊操做整個隊列會後移,這是須要在入隊發現隊列滿了時作數據遷移。假設head 指針和 tail 指針指向第一個結點和最後一個結點,n表示隊列大小。 tail==n 時,會有數據搬移操做。
3、循環隊列: 隊空的判斷條件是 head == tail,當隊滿時,(tail+1)%n=head
4、應用:使用阻塞隊列,就是在隊列爲空的時候,從隊頭取數據會被阻塞。由於此時尚未數據可取,直到隊列中有了數據才能返回;若是隊列已經滿了,那麼插入數據的操做就會被阻塞,直到隊列中有空閒位置後再插入數據,而後再返回。能夠實現一個「生產者 - 消費者模型」。函數
8、遞歸
1、用遞歸來解決的問題要知足三個條件:優化
- 一個問題的解能夠分解爲幾個子問題的解
- 這個問題與分解以後的子問題,除了數據規模不一樣,求解思路徹底同樣
- 存在遞歸終止條件
2、編寫遞歸函數的思路:分析問題與子問題的關係,求出遞歸公式,找到遞歸的終止條件。編寫遞歸代碼的關鍵是,只要遇到遞歸,咱們就把它抽象成一個遞推公式,不用想一層層的調用關係,不要試圖用人腦去分解遞歸的每一個步驟。
3、遞歸的例子:斐波那契數列、 階乘
4、遞歸的優化指針
- 預防堆棧溢出,能夠計算遞歸的深度,限制遞歸層數。或者使用尾遞歸。
- 減小重複計算:因爲遞歸過程當中存在函數值重複計算的問題,咱們能夠將計算過的值存起來,當調用函數的參數相同時,就沒必要重複計算了
- 使用循環代替遞歸,不過複雜度高,不夠簡潔直觀