一文搞定时间复杂度和空间复杂度
寫在前面:博主是一位普普通通的19屆雙非軟工在讀生,平時最大的愛好就是聽聽歌,逛逛B站。博主很喜歡的一句話花開堪折直須折,莫待無花空折枝:博主的理解是頭一次為人,就應該做自己想做的事,做自己不后悔的事,做自己以后不會留有遺憾的事,做自己覺得有意義的事,不浪費這大好的青春年華。博主寫博客目的是記錄所學到的知識并方便自己復習,在記錄知識的同時獲得部分瀏覽量,得到更多人的認可,滿足小小的成就感,同時在寫博客的途中結交更多志同道合的朋友,讓自己在技術的路上并不孤單。
目錄:
1.算法的時間復雜度
???? ?? 算法時間復雜度簡介
???? ?? 算法的時間復雜度預估值
???? ?? 大O記法
???? ?? 時間復雜度案例分析
2.算法的空間復雜度
???? ??空間復雜度概述
1.算法的時間復雜度
1.1算法時間復雜度簡介
判斷一個算法所編程序運行時間的多少,并不是將程序編寫出來,而是通過在計算機上運行所消耗的時間來度量。原因很簡單,一方面,解決一個問題的算法可能有很多種,一一實現的工作量無疑是巨大的,得不償失;另一方面,不同計算機的軟、硬件環境不同,即便使用同一臺計算機,不同時間段其系統環境也不相同,程序的運行時間很可能會受影響,嚴重時甚至會導致誤判。
實際場景中,我們更喜歡用一個估值來表示算法所編程序的運行時間。所謂估值,即估計的、并不準確的值。注意,雖然估值無法準確的表示算法所編程序的運行時間,但它的得來并非憑空揣測,需要經過縝密的計算后才能得出,也就是說:
表示一個算法所編程序運行時間的多少,用的并不是準確值(事實上也無法得出),而是根據合理方法得到的預估值。
1.2算法的時間復雜度預估值
那么,如何預估一個算法所編程序的運行時間呢?很簡單,先分別計算程序中每條語句的執行次數,然后用總的執行次數間接表示程序的運行時間。以一段簡單的 C 語言程序為例,預估出此段程序的運行時間:
for(int i = 0 ; i < n ; i++) //<- 從 0 到 n,執行 n+1 次 {a++; //<- 從 0 到 n-1,執行 n 次 }可以看到,這段程序中僅有 2 行代碼,其中:
for 循環從 i 的值為 0 一直逐增至 n(注意,循環退出的時候 i 值為 n),因此 for 循環語句執行了 n+1 次;而循環內部僅有一條語句,a++ 從 i 的值為 0 就開始執行,i 的值每增 1 該語句就執行一次,一直到 i 的值為 n-1,因此,a++ 語句一共執行了 n 次。因此,整段代碼中所有語句共執行了 (n+1)+n 次,即 2n+1 次。數據結構中,每條語句的執行次數,又被稱為該語句的頻度。整段代碼的總執行次數,即整段代碼的頻度。
再舉一個例子:
for(int i = 0 ; i < n ; i++) // n+1 { for(int j = 0 ; j < m ; j++) // n*(m+1){num++; // n*m} }讀者可結合注釋,計算此段程序的頻度為:(n+1)+n*(m+1)+nm,簡化后得 2nm+2n+1。值得一提的是,不同程序的運行時間,更多場景中比較的是在最壞條件下程序的運行時間。以上面這段程序為例,最壞條件即指的是當 n、m 都為無限大時此段程序的運行時間。
要知道,當 n、m 都無限大時,我們完全就可以認為 n==m。在此基礎上,2nm+2n+1 又可以簡化為 2n2+2*n+1,這就是此段程序在最壞情況下的運行時間,也就是此段程序的頻度。
如果比較以上 2 段程序的運行時間,即比較 2n+1 和 2n2+2n+1 的大小,顯然當 n 無限大時,前者要遠遠小于后者(如圖 2 所示)。
顯然,第 1 段程序的運行時間更短,運行更快。
思考一個問題,類似 2n+1、2n2+2n+1 這樣的頻度,還可以再簡化嗎?答案是肯定的。
以 2n+1 為例,當 n 無限大時,是否在 2n 的基礎上再做 +1 操作,并無關緊要,因為 2n 和 2n+1 當 n 無限大時,它們的值是無限接近的。甚至于我們還可以認為,當 n 無限大時,是否給 n 乘 2,也是無關緊要的,因為 n 是無限大,2*n 也是無限大。
再以無限大的思想來簡化 2n2+2n+1。當 n 無限大的:
首先,常數 1 是可以忽略不計的;
其次,對于指數級的 2n2 來說,是否在其基礎上加 2n,并無關緊要;
甚至于,對于是否給 n2 乘 2,也可以忽略。
因此,最終頻度 2n2+2n+1 可以簡化為 n2 。
也許很多讀者對于“使用無限大的思想”簡化頻度表達式,并不是很清楚。沒關系,這里給大家總結一下,在數據結構中,頻度表達式可以這樣簡化:
- 去掉頻度表達式中,所有的加法常數式子。例如 2n2+2n+1 簡化為 2n2+2n
- 如果表達式有多項含有無限大變量的式子,只保留一個擁有指數最高的變量的式子。例如 2n2+2n 簡化為 2n2;
- 如果最高項存在系數,且不為 1,直接去掉系數。例如 2n2 系數為 2,直接簡化為 n2 ;
事實上,對于一個算法(或者一段程序)來說,其最簡頻度往往就是最深層次的循環結構中某一條語句的執行次數。例如 2n+1 最簡為 n,實際上就是 a++ 語句的執行次數;同樣 2n2+2n+1 簡化為 n2,實際上就是最內層循環中 num++ 語句的執行次數。
1.3大O記法
得到最簡頻度的基礎上,為了避免人們隨意使用 a、b、c 等字符來表示運行時間,需要建立統一的規范。數據結構推出了大 O 記法(注意,是大寫的字母 O,不是數字 0)來表示算法(程序)的運行時間。發展至今,此方法已為大多數人所采納。
大 O 記法的表示方法也很簡單,格式如下:
O(頻度)其中,這里的頻度為最簡之后所得的頻度。
例如,用大 O 記法表示上面 2 段程序的運行時間,則上面第一段程序的時間復雜度為 O(n),第二段程序的時間復雜度為 O(n2)。
如下列舉了常用的幾種時間復雜度,以及它們之間的大小關系:
O(1)常數階 < O(logn)對數階 < O(n)線性階 < O(n2)平方階 < O(n3)(立方階) < O(2n) (指數階)
注意,這里僅介紹了以最壞情況下的頻度作為時間復雜度,而在某些實際場景中,還可以用最好情況下的頻度和最壞情況下的頻度的平均值來作為算法的時間復雜度。
1.4時間復雜度案例分析
1.對數階:
for(int i=0;i<n;i=i*3) x++;設循環體內部基本語句的頻度是f(n),那么就有3f(n)<=n
那么就有f(n)<=log3n
2 .根號階
x=n;//n>1 y=0; while(x>=(y+1)*(y+1)) y++;這個時間復雜度O(√n):
設基本語句y++執行次數為f(n),且x=n那么n>=(f(n)+1)2
f(n)<=√n-1舍去常數項得到最終結果
2.空間復雜度
2.1空間復雜度概述
與時間復雜度類似,一個算法的空間復雜度,也常用大 O 記法表示。
要知道每一個算法所編寫的程序,運行過程中都需要占用大小不等的存儲空間,例如:
1.程序代碼本身所占用的存儲空間;
2.程序中如果需要輸入輸出數據,也會占用一定的存儲空間;
3.程序在運行過程中,可能還需要 臨時申請更多的存儲空間。
首先,程序自身所占用的存儲空間取決于其包含的代碼量,如果要壓縮這部分存儲空間,就要求我們在實現功能的同時,盡可能編寫足夠短的代碼。
程序運行過程中輸入輸出的數據,往往由要解決的問題而定,即便所用算法不同,程序輸入輸出所占用的存儲空間也是相近的。
事實上,對算法的空間復雜度影響最大的,往往是程序運行過程中所申請的臨時存儲空間。不同的算法所編寫出的程序,其運行時申請的臨時存儲空間通常會有較大不同。
舉個例子:
int n; scanf("%d", &n); int a[10];通過分析不難看出,這段程序在運行時所申請的臨時空間,并不隨 n 的值而變化。而如果將第 3 行代碼改為:
int a[n];此時,程序運行所申請的臨時空間,和 n 值有直接的關聯。
所以,如果程序所占用的存儲空間和輸入值無關,則該程序的空間復雜度就為 O(1);反之,如果有關,則需要進一步判斷它們之間的關系:
1.如果隨著輸入值 n 的增大,程序申請的臨時空間成線性增長,則程序的空間復雜度用 O(n) 表示;
2.如果隨著輸入值 n 的增大,程序申請的臨時空間成 n2 關系增長,則程序的空間復雜度用 O(n2) 表示;
3.如果隨著輸入值 n 的增大,程序申請的臨時空間成 n3 關系增長,則程序的空間復雜度用 O(n3) 表示;
等等。
在多數場景中,一個好的算法往往更注重的是時間復雜度的比較,而空間復雜度只要在一個合理的范圍內就可以。
總結
以上是生活随笔為你收集整理的一文搞定时间复杂度和空间复杂度的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 二分查找(循序渐进由0到1掌握二分)
- 下一篇: 线性表易错点与线性表程序设计易错点