算法是一段執行的程序, 能夠理解成幾行代碼,或者一個方法; 算法的時間複雜度是指這段代碼須要消耗的時間資源;算法的空間複雜度是指這段代碼須要消耗的空間資源(空間資源一般是指佔用的內存)。java
一般咱們在討論一個算法時會說,這個算法時間複雜度是O(), 那個O(
)。而這個O(
)、O(
)就是大O複雜度表示法。這個
和
具體是怎麼來的呢,下面簡單舉個例子:算法
int cal(int n) {
int sum = 0; (1)
for(int i = 1;i <= n; ++i){ (2)
sum = sum + i; (3)
} (4)
return sum;
}
複製代碼
上面的代碼是計算 1, 2, 3… n 的和一個方法,咱們假設每一行代碼cpu的時間都是相同的,每一行代碼執行時間爲run_time, 那這段代碼的總執行時間 T(n) 是多少 ? 第1行爲run_time, 第2行和第3行代碼循環n次,時間爲2 * n * run_time。全部總時間:數組
T(n) = (2 * n + 1)* run_time
複製代碼
根據規律,能夠總結成一個公式:bash
T(n) = O (f(n))
複製代碼
因此, 上面的O()、O(
)例子中,
以及
。
中run_time是常數, 當n足夠大, 公式中的低階、常量、係數均可以忽略不計,全部這段計算 1, 2, 3… n總和的代碼時間複雜度是O(
)。性能
常見時間複雜度有:測試
下面在舉個空間複雜度的例子:spa
void print(int n){
int i = 0; (1)
int[] arr = new int[n]; (2)
int j = 0; (3)
}
複製代碼
第1行和第3行代碼中都是分別申請了一個空間存儲變量, 都有數據規模n無關,全部能夠忽略,第2行中申請了數量爲n的數組空間。全部整段代碼的空間複雜度是。.net
常見空間複雜度有: 3d
首先,對比幾個算法的好壞須要結合使用場景,好比數據量,內存,數據特徵等。一樣一段代碼,在不一樣機器上運行速度也是不一樣的。一般比較幾個算法的性能,就須要在同一個機器,同一批數據,同一個環境下去運行對比測試它們所須要的運行時間。code
可是,但開發者是使用或者選擇一個算法時,一般是沒辦法精準的對比算法的優劣,而且時間成本也不容許。全部咱們須要一個不用具體數據來測試, 就能夠粗略估算出算法的執行效率, 這就是算法的時間複雜度與空間複雜度做用啦。
什麼狀況下,一段代碼會出現有最好、最壞的狀況呢; 舉個例子, 好比在一個長度爲n的整數數組中去找一個數,找到就立馬返回數組的下表。在循環查找的過程當中,最好的狀況就是數組第一個就要找的,全部最好狀況時間複雜度爲; 若是數組最後一個是要找的, 這時就是最壞的狀況,最壞狀況時間複雜度爲
。
像上面一個例子,在一個長度爲n的整數數組中去找一個數, 有最好和最壞狀況,最好、最壞狀況時間複雜度沒法衡量一段代碼的執行效率,這時就須要用到平均狀況時間複雜度。平均=全部狀況總和/總次數。
那麼平均狀況時間複雜度怎麼算出來呢,要找的數要麼在數組裏,要麼不在,假設在與不在的機率是, 查找的數出如今0 ~ n-1 的位置機率是同樣的,爲
, 全部查找的數在數組裏機率爲
。總過程爲:
去掉常量,複雜度爲O(n)。
均攤時間複雜度就是一種特殊的平均時間複雜度,只能在特殊的場景才能是使用。很少說,舉個例子,會java的對容器arraylist不陌生吧,arraylist有個默認長度的數組,add方法裏,若是數組長度不夠,是須要擴容的。下面以一段add方法僞代碼爲例:(爲了代碼簡單易懂,以添加int爲例)
int arr[] = new int[10]; //初始默認長度爲10
int len = 10;
int index = 0;
void add(int element){
if(index >= len){ // 超出長度,數組須要擴容,並複製值到一個新數組裏
final int newLen = len * 2;
int new_arr[] = new int[newLen]; //申請2倍的數組
for (int i = 0; i < len; ++i){
new_arr[i] = arr[i];
}
arr = new_arr;
len = newLen;
}
arr[index] = element;
++index;
}
複製代碼
當不擴容時, 這個add方法時間複雜度就是, 當擴容時, if裏面複製到新數組須要遍歷一次,
int arr[] = new int[10]; //初始默認長度爲10
int len = 10;
int index = 0;
void add(int element){
if(index >= len){ // 超出長度,數組須要擴容,並複製值到一個新數組裏
final int newLen = len * 2;
int new_arr[] = new int[newLen]; //申請2倍的數組
for (int i = 0; i < len; ++i){
new_arr[i] = arr[i];
}
arr = new_arr;
len = newLen;
}
arr[index] = element;
++index;
}
複製代碼
當不擴容時, 這個add方法時間複雜度就是, 當擴容時, if裏面複製到新數組須要遍歷一次,
int arr[] = new int[10]; //初始默認長度爲10
int len = 10;
int index = 0;
void add(int element){
if(index >= len){ // 超出長度,數組須要擴容,並複製值到一個新數組裏
final int newLen = len * 2;
int new_arr[] = new int[newLen]; //申請2倍的數組
for (int i = 0; i < len; ++i){
new_arr[i] = arr[i];
}
arr = new_arr;
len = newLen;
}
arr[index] = element;
++index;
}
複製代碼
當不擴容時, 這個add方法時間複雜度就是O(1), 當擴容時, if裏面複製到新數組須要遍歷一次,數組默認長度假設是n, 擴容時的時間複雜度是O(n)。平均狀況時間複雜度是多少呢,若是像上一個例子用機率論去計算也是能夠,可是麻煩。咱們假設須要添加11個數,添加第11個數時須要擴容,循環10次把前10個數複製到新的數組中而後把第11個數添加進去。添加前10個數時間複雜度都是O(1), 若是把擴容時循環10次分攤到添加前10個數操做中,那麼添加前10個數操做都只是都運行了一行代碼而已,仍是常量級別的,全部這個add方法的平均時間複雜度就是O(1)。 經過分攤分析時間複雜度就叫作均攤時間複雜度。