同一個問題可使用不一樣的算法解決,那麼不一樣的算法孰優孰劣如何區分呢?所以咱們須要一個表示方法來表明每一個程序的效率。算法
衡量一個程序好壞的標準,通常是運行時間與佔用內存兩個指標。數組
不過咱們在寫代碼的時候確定沒法去估量程序的執行時間,由於真實的執行時間受到多方面因素的影響,好比一樣一段程序,放在高配服務器上跑和放在低配服務器上跑徹底是兩個表現效果,好比遍歷一個數組的函數,執行時間徹底取決於調用函數傳入的數組大小。服務器
如何在不運行程序的狀況下,判斷出代碼的執行時間呢?顯然是不可能的。函數
不過咱們雖然沒法預估代碼的絕對執行時間,可是咱們能夠預估代碼基本的執行次數。優化
一段代碼的執行時間若是有變化,則必定是受到外部輸入的數據所影響,咱們將代碼中全部不變的因素,表示爲大O,將變化的因素做爲基數n,表示爲:O(n),大O的意思是忽略重要項之外的內容,咱們常以這種大O表示法來判斷比較各類算法的執行效率。spa
接下來我會介紹幾種經常使用的複雜度表示方法。code
PS:專業的解釋必然全篇都是數學證實,未免太過於複雜,讓學者更加迷茫,我這裏寫的並非教材,而是最直白的理解。blog
本節中例舉的各類時間複雜度以好到差依次排序。排序
先看下這個函數:內存
private static void test(int n) { int a = 2; int b = 3; int c = a+b; System.out.println(c); }
一共4行代碼,CPU要將a的值寫入內存,b的值寫入內存,a和b進行計算,將計算結果寫入c,最後將c輸出到控制檯。
儘管計算機內部要作這麼多事情,這段代碼的時間複雜度依然是O(1),緣由是這幾行代碼所作的操做是固定的,是不變的因素。
再看下這個函數:
private static void test(int n) { for (int i=0;i<100000;i++){ System.out.println(i); } }
循環10W次,可能你以爲功耗可能有點大,不過它的時間複雜度仍然是O(1)。
咱們能夠這麼固定的認爲:不管接收的參數怎麼變化,只要代碼執行次數是無變化的,則用1來表示。 凡是O(1)複雜度的代碼,一般表明着它是效率最優的方案。
廣泛性的說法是複雜度減半,就像紙張對摺。
示例代碼:
private static void test(int n) { for (int j=1;j<=n;j=j*2){ System.out.println(j); } }
這段代碼的執行效果並不是是一次折半,它是次次折半,以2爲底,不斷的進行冪運算,實際上只要有冪指數關係的,無論你的底數是幾,只要可以對原複雜度進行求冪逆運算咱們均可以稱之爲O(log n)
好比:
private static void test(int n) { for (int j=1;j<=n;j=j*3){ System.out.println(j); } }
在忽略係數、常數、底數以後,最後均可以表示爲O(log n),只不過咱們遇到的算法幾乎不會出現一些極端例外狀況,對數時間的所在地常見以二分查找爲表明。
咱們將test方法稍稍修改一下:
private static void test(int n) { for (int i=0;i<n;i++){ System.out.println(i); } }
修改以後此次不是執行10W次,而是執行n次,n是由參數傳入的一個未知值,在沒有真實運行的時候咱們沒法判斷這個n究竟是多少?由於它能夠是任意int型數字,你能夠這麼認爲:在理想的狀況下,它的複雜度是O(1),在惡劣的狀況下,它的複雜度是無限大。徹底取決於方法調用方。
直白的說,for循環就是循環n次,所以這段代碼的時間複雜度爲O(n),這種複雜度經常表現爲線性查找。
線性對數時間也就是線性時間嵌套對數時間:
private static void t(int n){ for (int i=0;i<n;i++){ test(n); } } private static void test(int n) { for (int j=1;j<=n;j=j*2){ System.out.println(j); } }
t這個方法的時間複雜度就是O(n log n)。
平方時間就是執行程序須要的步驟數是輸入參數的平方,最多見的是嵌套循環:
private static void test(int n) { for (int i=0;i<n;i++){ for (int j=n;j>0;j--){ System.out.println(j); } } }
比O(n^2)還要慢的天然有立方級O(n^3)
比O(n^3)更慢慢的還有指數級O(2^n)
慢到運行一次程序要繞地球三百圈的有O(n!)
正常狀況下咱們不會接觸到這些類型的算法。
所謂空間,就是程序運行佔用的內存空間,空間複雜度指的就是執行算法的空間成本。
這裏咱們拋一道題來作例子:在一個數組中找出有重複的值,如數組[3,8,13,7,15,8,6,6] 找出8和6。
解法:
private static void test(int[] arr) { for (int i=0;i<arr.length;i++){ for(int j=0;j<i;j++){ if(arr[j] == arr[i]){ System.out.println("找到了:"+arr[i]); } } } }
很顯然:時間複雜度爲O(n^2)。
那咱們還可使用一種更優的解法:
private static void test(int[] arr) { HashSet hashSet = new HashSet(); for (int i=0;i<arr.length;i++){ if(hashSet.contains(arr[i])){ System.out.println("找到了:"+arr[i]); } hashSet.add(arr[i]); } }
也許你會驚訝的發現,時間複雜度被優化成了O(n)。
雖然時間複雜度下降成了O(n),可是付出的代價是空間複雜度變成了O(n),由於新的解法使用了一個HashSet來存儲數據,存儲數據天然要佔用內存空間,而佔用的空間大小徹底取決於傳入數組大小。
咱們之因此說第二種解法更優,實際上是一種常規思想,由於現實中絕大部分狀況,時間複雜度顯然比空間複雜度更爲重要,咱們寧願多分配一些存儲空間做爲代價,來提高程序的執行速度。
總而言之,比較兩個算法優劣的指標有兩個,時間複雜度與空間複雜度,優先比較時間複雜度,時間複雜度相同的狀況下比較空間複雜度。
最後:感謝閱讀。