通過字面意思可以知道
- 時間複雜度:就是說執行算法需要消耗的時間長短,越快越好。
- 空間複雜度:就是說執行當前算法需要消耗的存儲空間大小,也是越少越好
表示方法
我們一般用“大O符號表示法”來表示時間複雜度:T(n) = O(f(n))
n是影響複雜度變化的因子,f(n)是複雜度具體的算法。
時間和空間複雜度意義
評價一個算法的效率主要是看它的時間複雜度和空間複雜度情況。
可能有的開發者接觸時間複雜度和空間複雜度的優化不太多(尤其是客戶端),但在服務端的應用是比較廣泛的。在巨大併發量的情況下,小部分時間複雜度或空間複雜度上的優化都能帶來巨大的性能提升,是非常有必要了解的。
時間複雜度
常見的時間複雜度量級如下:
- 常數階O(1)
- 線性階O(n)
- 對數階O(logN)
- 線性對數階O(nlogN)
- 平方階O(n²)
- 立方階O(n³)
- K次方階O(n^k)
- 指數階(2^n)
1. 常數階O(1)
int a = 1;
int b = 2;
int c = 3;
大O符號表示法並不是用於來真實代表算法的執行時間的,它是用來表示代碼執行時間的增長變化趨勢的。
如上代碼即使成千上萬,其算法並未邊長,其時間複雜度仍爲O(1)。
- 線性階O(n)
for(i = 1; i <= n; i++) {// 執行n次
j = i;// 執行n次
}
n爲幾,則for內部代碼塊就需要運行多少次,所以它的時間複雜度其實是O(n)。
3. 對數階O(logN)
int i = 1;
while(i < n) {
i = i * 2;
}
可以看到每次循環的時候 i 都會乘2,那麼總共循環的次數就是log2n,因此這個代碼的時間複雜度爲O(log2n)。
這裏的底數對於研究程序運行效率不重要,寫代碼時要考慮的是數據規模n對程序運行效率的影響,常數部分則忽略,所以成了O(logn)
同樣的,如果不同時間複雜度的倍數關係爲常數,那也可以近似認爲兩者爲同一量級的時間複雜度。
4. 線性對數階O(nlogN)
for(m = 1; m < n; m++) {
i = 1;
while(i < n) {
i = i * 2;
}
}
線性對數階O(nlogN) 其實非常容易理解,將時間複雜度爲O(logn)的代碼循環N遍的話,那麼它的時間複雜度就是 n * O(logN),也就是了O(nlogN)。
5. 平方階O(n²)
for(x = 1; i <= n; x++){//執行你次
for(i = 1; i <= n; i++) {//執行 N*n次
j = i; //執行 N*n次
}
}
把 O(n) 的代碼再嵌套循環一遍,它的時間複雜度就是 O(n²) 了。
6.立方階O(n³)、K次方階O(n^k)
參考上面的O(n²) 去理解就好了,O(n³)相當於三層n循環,其它的類似。
時間複雜度分析
通常用T(n)表示代碼執行時間,n表示數據規模大小,f(n)表示代碼執行綜合次數。
在O(n)例子中執行次數爲2n,如果沒行執行時間t,則執行時間2nt,可以表示f(n)=2nt,
在O(n²)例子中執行次數爲2n,如果沒行執行時間t,則執行時間2nt,可以表示f(n)=(2n²+n)t。
代碼的執行時間 T(n)與每行代碼的執行次數 n 成正比,人們把這個規律總結成這麼一個公式:T(n) = O(f(n))。大O時間複雜度並不具體表示代碼真正的執行時間,而是表示代碼執行時間隨數據規模增長的變化趨勢。所以,也叫作漸進時間複雜度,簡稱時間複雜度。
當n變得越來越大時,公式中的低階,常量,係數三部分影響不了其增長趨勢,所以可以直接忽略他們,只記錄一個最大的量級就可以了,所以兩個例子實際他們的時間複雜度應該記爲:T(n)=O(n) ,T(n)=O(n*n)
空間複雜度
1. 空間 O(1)
如果算法執行所需要的臨時空間不隨着某個變量n的大小而變化,即此算法空間複雜度爲一個常量,可表示爲 O(1)。
int i = 1;
int j = 2;
++i;
j++;
int m = i + j;
2. 空間 O(n)
int[] arr = new int[n]
分配空間只和n有關
穩定性
假定在待排序的記錄序列中,存在多個具有相同的關鍵字的記錄,若經過排序,這些記錄的相對次序保持不變,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序後的序列中,r[i]仍在r[j]之前,則稱這種排序算法是穩定的;否則稱爲不穩定的。
需要注意的是,排序算法是否爲穩定的是由具體算法決定的,不穩定的算法在某種條件下可以變爲穩定的算法,而穩定的算法在某種條件下也可以變爲不穩定的算法。
如下冒泡排序中將條件修改爲if (arr[j] >= arr[j + 1]),則穩定性將從穩定變爲不穩定。
if (arr[j] > arr[j + 1]) {