2018年10月9日java
時間複雜度通常採用大O標記法
, 即 , 其中T(n)表示代碼運行時間;n表示數據規模大小;f(n)表示每行代碼執行次數總和, 表示T(n)與f(n)的正比關係。大 時間複雜度實際上並不具體表示代碼的真正運行時間,而是表示代碼執行時間隨數據規模增加的變化趨勢。算法
在大 表示分析中,低階項
和常數項
均可以省略,只保留最高階項便可;如 在大 標記法中記爲 ,而對於形如 表示爲 。數組
關注循環次數多的代碼spa
public int accumulate(int n) {
int sum = 0;
int i = 1;
for (; i <= n; i++) {
sum += i;
}
return sum;
}
複製代碼
其中for循環內的代碼執行n次,而其他代碼執行1次,與n的大小無關,忽略常數項,該段代碼的時間複雜度爲 。code
加法法則cdn
總複雜度爲量級最大的那段代碼的複雜度,抽象爲公式爲:blog
若 ,, 那麼
排序
public int accumulate(int n) {
int sum1 = 0;
for (int i = 1; i <= 100; i++) {
sum1 += i;
}
int sum2 = 0;
for (int i = 1; i <= n; i++) {
sum2 += i;
}
int sum3 = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
sum3 += i * j;
}
}
return sum1 + sum2 + sum3;
}
複製代碼
其中sum1段的代碼循環執行了100次,與n無關。sum2段代碼的複雜度爲 ,sum3段的代碼複雜度爲 ;根據加法法則,咱們只去其中最大量級的複雜度,因此該段代碼的時間複雜度爲 。it
乘法法則io
嵌套代碼的複雜度等於嵌套內外代碼複雜度的乘積,抽象爲公式爲:
若 ,,那麼
public int accumulate(int n) {
int result = 0;
for (int i = 1; i <= n; i++) {
result += f(i);
}
return result;
}
private int f(int n) {
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
}
return sum;
}
複製代碼
其中accumulate方法與f方法的時間複雜度都爲 ,可是f嵌套在accumulate中,因此整段代碼的複雜度就爲:
度量級 | 大 O 表示 |
---|---|
常量階 | |
對數階 | |
線性階 | |
線性對數階 | |
平方階 | |
立方階 | |
指數階 | |
階乘階 |
常見的時間複雜度有常量階、對數階、線性階、線性對數階以及平方階,常量階、線性階與平方階在第二節中已經分析,再也不贅述;而一些高效的排序算法的時間複雜度就是線性對數階,如快速排序,歸併排序以及堆排序等。
對數階
咱們所熟知的二分查找的複雜度就是 ,如下經過一段代碼來分析對數階複雜度:
public int test(int n) {
int res = 1;
while (res <= n) {
res *= 2;
}
return res;
}
複製代碼
該段代碼是求 的解,更確切的說,是找出 在小於或等於n的範圍內最接近n的x的值;其中 ,即while循環體內代碼要執行 次,即其時間複雜度爲 。
若把循環體內代碼 res *= 2
改成 res *= 3
,不難分析出其時間複雜度就變爲 ;可是爲何全部對數階的時間複雜度都統一表示爲 ?
首先咱們先複習對數換底公式:
則
因此 ,由於爲常數項,因此該項能夠忽略,所以;因此不管對數以哪一個數爲底,最後均可以轉化爲一個常數項與以2爲底的對數相乘,所以在對數階時間複雜度的表示方法裏,就忽略對數的底,統一表示爲
此種表示形式的時間複雜度是由兩個數據規模來決定的
public int accumulate(int m, int n) {
int sum1 = 0;
for (int i = 1; i <= m; i++) {
sum1 += i;
}
int sum2 = 0;
for (int i = 1; i <= n; i++) {
sum2 += i;
}
return sum1 + sum2;
}
複製代碼
因爲咱們不能事先知曉m與n哪一個量級大,因此就不能簡單的利用加法規則取其最大量級,那麼像這種代碼的時間複雜度就爲 。
public int accumulate(int m, int n) {
int sum = 0;
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
sum += i * j;
}
}
return sum;
}
複製代碼
而相似上述代碼依然可使用乘法法則,其時間複雜度爲
如下將經過一段代碼來說述這幾個時間複雜度:
public class Test {
private int[] array = new int[5];
private int N = 0;
public void push(int item) {
if (N == array.length) {
resize(2 * array.length);
}
array[N++] = item;
}
private void resize(int size) {
int[] temp = new int[size];
for (int i = 0; i < N; i++) {
temp[i] = array[i];
}
array = temp;
}
}
複製代碼
上述代碼是用數組模擬一個棧的部分代碼,其中push
表示壓棧操做,resize
表示對數組進行擴容的操做;當壓入棧中的元素數量達到數組的容量時,就定義一個容量爲以前兩倍的新數組temp
,將舊數組array
中的元素複製到新數組中,而後將array
指向temp
。
最好時間複雜度:最理想的狀況下,當前棧中元素數量比數組的容量小,此時就直接執行代碼塊array[N++] = item;
,即此時的時間複雜度爲 。
最壞時間複雜度:最糟糕的狀況下,當前棧中元素數量與數組的容量相等,此時就要執行resize
方法進行擴容了,進入循環體,執行N
次複製操做,此時的時間複雜度爲 。
平均時間複雜度:
N
中狀況,且每種狀況的時間複雜度爲 ;當棧中元素與數組容量相等時,此時進行壓棧就只有一種狀況了,要進行擴容操做,這種狀況的時間複雜度爲 ;則總共有N+1
中狀況,對其取平均值:在大 標記法中,能夠省略係數與低階項,因此其平均時間複雜度爲
N+1
中狀況,每種狀況的發生機率爲 ,則其平均時間複雜度爲:均攤時間複雜度:是一種特殊的平均時間複雜度,根據上述代碼,每出現一次擴容操做時,即此時壓棧的時間複雜度爲 ,那麼後面的N
次壓棧操做的時間複雜度均爲 ,先後是連貫的,所以將 平攤到前N
次上,得出均攤時間複雜度爲 。