復雜度分析
1 為什麽要對算法進行復雜度分析?
數據結構與算法就是為了解決如何更省的存儲數據、如何更快的處理數據的問題,那麽就必須有一個準則對“更快”、“更省”進行判斷。
ok,可以,既然咱們要分析算法的執行時間和運行時占用的內存,那好辦呀,咱們就在Visual Studio上把代碼跑一遍就可以了。
這種測試方法當然是對的,但是對於算法的復雜度分析來說,卻有兩個缺陷:一是這種測試方法對測試環境依賴很嚴重,例如同一段代碼在i3和i9上運行,i9上跑的i3快;二是輸入數據的不確定性,輸入數據的規模和有序度都會影響算法的執行時間。
所以才有了空間、時間復雜度分析。
2 什麽是時間復雜度、空間復雜度?
2.1 時間復雜度:測試運行時間就是計算對運行時間有消耗的基本操作的執行次數,那麽我們可以假設一個前提,每一行代碼執行的時間(baseTime)是一樣的。例如,
1 long add(int n) 2 { 3 int i = 1; 4 int j = 1; 5 long sum = 0; 6 7 for ( , i < n, ++i) 8 { 9 for (, j < n, ++j)10 sum += i*j; 11 } 12 13 return sum; 14 }
外層循環每執行一次,內層循環執行n次,所以我們知道總共執行次數f(n) = 2n2+n+4,總共執行時間T(n) = f(n) * baseTime,也可以表示成T(n) = O(f(n)),‘O’表示總執行時間和執行語句數量f(n)成正比,叫做大O復雜度表示法。
大O復雜並不具體表示代碼真正的執行時間,而是代碼執行時間隨數據輸入規模增長的變化趨勢。記錄時我們只需關註最大量級即可,上述例子就可表示成O(n2)。
2.3 時間復雜度分析技巧
1) 只要算法中不存在循環語句、遞歸語句,即使有成千上萬行的代碼,其時間復雜度也是O(1);
2) 只關註循環次數最多的一段代碼;
3) 加法原則:總復雜度等於量級最大的那段代碼的復雜度;
4) 乘法原則:嵌套代碼的復雜度等於嵌套內外代碼復雜度的乘積。
2.4 常見的時間復雜度
O(1) < O(logn) < O(n) < O(nlogn) < O(n2) < O(n3) < O(2n) < O(n!) < O(nn)
2.5 空間復雜度
與時間復雜度類似,空間復雜度表示的是代碼執行過程中所需存儲空間隨數據輸入規模增長的變化趨勢,同樣可以用大O表示法表示。這裏所需的存儲空間是說除了輸入數據存儲空間外,算法運行還需額外的存儲空間。
1 ListNode* removeNthFromEnd(ListNode* head, int n) { 2 vector<ListNode*> nodeVector; //創建一個節點容器 3 ListNode* pWorkNode = head; 4 int i = 0; //鏈表長度 5 6 while (pWorkNode) //遍歷鏈表 7 { 8 nodeVector.push_back(pWorkNode); //將各個節點放入容器中 9 pWorkNode = pWorkNode->next; 10 ++i; 11 } 12 13 int j = i + 1 - n; //待刪除節點序號 14 if (1 == j) //如果刪除的是第一個節點 15 { 16 pWorkNode = head->next; 17 delete head; 18 head = pWorkNode; 19 } 20 else 21 { 22 nodeVector[j - 2]->next = nodeVector[j - 1]->next; 23 delete nodeVector[j - 1]; 24 } 25 26 return head; 27 }
該例子中在運行過程中創建了一個長度為n的容器,空間復雜度為O(n)。
3 最好情況時間復雜度、最壞情況時間復雜度和平均情況時間復雜度
1) 最好情況時間復雜度:最理想的情況下,執行這段代碼的時間復雜度。
2) 最壞情況時間復雜度:最糟糕的情況下,執行這段代碼的時間復雜度。
3) 平均情況時間復雜度:平均情況下的時間復雜度。
一般只有在同一塊代碼在不同的情況下,時間復雜度有量級的差距,才會使用這三種復雜度表示法來區分。
4 均攤時間復雜度
攤還分析法:將特殊耗時多的那次或幾次操作均攤到剩下的n次耗時少的操作上。大多數情況下,均攤時間復雜度都等於最好情況復雜度。
適用場景:對一個數據結構進行一組連續操作中,大部分情況下時間復雜度都很低,只有個別情況下時間復雜度比較高,而且這些操作之間存在前後連貫的時序關系,這個時候,我們就可以將這一組操作放在一起分析,看是否能將較高時間復雜度的那次操作的耗時,平攤到其他那些時間復雜度比較低的操作上。
復雜度分析