@
前言
新的學習階段又開始了,在更新完C語言后,博主將開始更新數據結構的知識了,說到數據結構想必大家都是知道其重要性吧.
嗯,廢話不多說,那我們現在就開始談談數據結構吧~
算法效率
什么是算法效率? 即判斷一個程序的相對好與壞的方法
.算法效率的測評主要有兩種:
- 第一種: 時間復雜度(又稱時間效率)
- 第二種: 空間復雜度(又稱空間效率)
而由於現在科技的飛速發展,計算機的存儲容量已經極大提高,空間復雜度
並不是重點了,我們今天需要着重講解的就是時間復雜度
時間復雜度
!!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!敲黑板!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !! !!! !!!!
重要的事情一定要多說幾遍,時間復雜度測的
!!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!不是時間!!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!!
!!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!不是時間!!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!!
!!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!不是時間!!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!!
一個程序的執行時間完全會受到很多因素的影響,比如同樣的程序,在不同的機器,執行的時間卻不一定相同,所以說如果我們要用 時間的長短來評價一個程序的好壞這明顯是不科學的,因此才提示時間復雜度的概念 : 算法中的基本操作執行次數
,而他的表示方法則是大O漸進表示法,即O(m) ,m是關於次數的表達式
樣例1
請問函數Func1
的時間復雜度是多少?
void Func1(int N)
{
int count = 0;
for (int i = 0; i < N ; ++ i)
{
for (int j = 0; j < N ; ++ j)
{
++count;
}
}
for (int k = 0; k < 2 * N ; ++ k)
{
++count;
}
int M = 10;
while (M--)
{
++count;
}
printf("%d\n", count);
}
根據程序分析,我們可以得知Func1的基本執行次數
是:
F(N) = N² + 2N + 10;
所以用大O漸進表示法就是O(N² + 2N + 10),對嗎???,先不回答,我們先看看下面:
N | F(N) | N² |
---|---|---|
1 | 13 | 1 |
10 | 130 | 100 |
100 | 10210 | 10000 |
1000 | 1002010 | 1000000 |
10000 | 100020010 | 100000000 |
我們發現,隨着N的逐漸增大,F(N)的值也逐漸增大,但是卻也與N²的值非常接近,因此我們便可以不看尾數,即我們再用大O漸進表示法時候是不需要精確計算運行次數,而只是求個大概.
-
**下面是總結的大O表示原則:**
**用常數1取代運行時間中的所有加法常數。**
**在修改后的運行次數函數中,只保留最高階項。**
**如果最高階項存在且不是1,則去除與這個項目相乘的常數。得到的結果就是大O階**
所以Func1
的時間復雜度是 O(N²)
,同時提醒一下哦,時間復雜度計算的是在最壞情況下的哦
樣例2
void Func2(int N)
{
int count = 0;
for (int k = 0; k < 2 * N ; ++ k)
{
++count;
}
int M = 10;
while (M--)
{
++count;
}
printf("%d\n", count);
}
經過分析可以得知,Func2的基本執行次數是:
F(N) = 2N + 10;
根據大O表示法原則第一條F(N)變成: F(n) = 2N + 1;
根據大O表示法原則第二條F(N)變成: F(n) = 2N;
根據大O表示法原則第三條F(N)變成: F(n) = N;
所以最終時間復雜度是 : O(N)
樣例3
void Func3(int N, int M)
{
int count = 0;
for (int k = 0; k < M; ++ k)
{
++count;
}
for (int k = 0; k < N ; ++ k)
{
++count;
}
printf("%d\n", count);
}
經過分析可以得知,Func3的基本執行次數是:
F(N) = M + N
根據大O漸進表示法原則,可以知道 時間復雜度為: O( M + N)
樣例4
void Func4(int N)
{
int count = 0;
for (int k = 0; k < 100; ++ k)
{
++count;
}
printf("%d\n", count);
}
經過分析可以得知,Func4的基本執行次數是:
F(N) = 100
根據大O漸進表示法原則,可以知道 時間復雜度為: O(1)
樣例5
void BubbleSort(int* a, int n)
{
assert(a);
for (size_t end = n; end > 0; --end)
{
int exchange = 0;
for (size_t i = 1; i < end; ++i)
{
if (a[i-1] > a[i])
{
Swap(&a[i-1], &a[i]);
exchange = 1;
}
}
if (exchange == 0)
break;
}
}
經過分析可以得知,BubbleSort的基本執行次數是:
F(N) = n-1 + n-2 + n-3 + n-4 + ... + 3 + 2 + 1;
可以知道那是一個等差數列求和,所以F(N) = n*(n-1) / 2 = (1/2)N² - (1/2)N
按照大O表示法可以知道 時間復雜度為 O(N²)
樣例6
int BinarySearch(int* a, int n, int x)
{
assert(a);
int begin = 0;
int end = n-1;
while (begin < end)
{
int mid = begin + ((end-begin)>>1);
if (a[mid] < x)
begin = mid+1;
else if (a[mid] > x)
end = mid;
else
return mid;
}
return -1;
}
請看下面隨着查找次數與數組長度關系:
查找次數 | 數組長度 |
---|---|
1 | N/2 |
2 | N/2/2 |
3 | N/2/2/2 |
... | ... |
x-2 | 3 |
x-1 | 2 |
x | 1 |
由此我們可以知道一個關系: 2^x = N,所以 x = ㏒₂(N), 即 F(N) = ㏒₂(N)
所以大O漸進表示法 時間復雜度為 O(㏒₂(N))
遞歸的時間復雜度求解
遞歸復雜度的計算公式為: 遞歸次數
* 每次遞歸里面的基本操作次數
樣例1
long long Factorial(size_t N)
{
return N < 2 ? N : Factorial(N-1)*N;
}
遞歸次數: N
每次遞歸基本操作次數: 1
**所以F(N) = N*1; **
大O的漸進表示法 時間復雜度為:O(N)
樣例2
long long Fibonacci(size_t N)
{
return N < 2 ? N : Fibonacci(N-1)+Fibonacci(N-2);
}
遞歸次數:
所以F(N) = 2^0 + 2^1 + 2^2 + ... 2^(n-2)
這是等比數列求和,所以F(N) = (1-2(n-2)) / (1-2) = 2^(n-2) - 1 = (1/4)*2^n - 1
所以大O表示法的 時間復雜度是 O(2^N)**
**
有人可能會說,這個是不准確的,的確,因為實際上這個次數是小於2^(n-2) - 1
的,為什么呢,請看圖:
即在中途后,次數就開始下降了,原因是有F(3)以下的數字先達到,就不再需要往下遞歸了.
但是即使這樣,我們想一想按照這樣真實的計算,最高次項是多少?? 其實還是2^N,所以時間復雜度仍然為O(2^N)
空間復雜度
!!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!!敲重點!!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!!
!!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! 空間復雜度 !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!!
計算的不是內存大小,而是程序每次執行,說臨時開辟的變量數量
並且時間復雜度是累積
的,但是空間復雜度不累計
,並且空間復雜度的表示也是大O表示
樣例1
void BubbleSort(int* a, int n)
{
assert(a);
for (size_t end = n; end > 0; --end)
{
int exchange = 0;
for (size_t i = 1; i < end; ++i)
{
if (a[i-1] > a[i])
{
Swap(&a[i-1], &a[i]);
exchange = 1;
}
}
if (exchange == 0)
break;
}
}
通過觀察可以看見,程序開辟的變量數量只有3個,即常數個,所以 空間復雜度為 O(1)
樣例2
long long* Fibonacci(size_t n)
{
if(n==0)
return NULL;
long long * fibArray =(long long *)malloc((n+1) * sizeof(long long));
fibArray[0] = 0,fibArray[1] = 1;
for (int i = 2; i <= n ; ++i)
{
fibArray[i ] = fibArray[ i - 1] + fibArray [i - 2];
}
return fibArray ;
}
這個程序可以看見動態開辟了n+1個空間,所以空間復雜度為O(N)
樣例3
long long Factorial(size_t N)
{
return N < 2 ? N : Factorial(N-1)*N;
}
由於是遞歸,遞歸的空間復雜度計算類似. 等於遞歸開辟的棧空間
*每次遞歸開辟的變量數量
此題每次遞歸沒有開辟變量,所以只計算遞歸次數
,可知道連續遞歸了N次,所以O(N
)
樣例4
long long Fibonacci(size_t N)
{
return N < 2 ? N : Fibonacci(N-1)+Fibonacci(N-2);
}
還記得我們說過的,空間復雜度不累計嗎????????????
這個題做多連續遞歸多少次? 答案n-1
,
因為要想知道F(N)必先知道F(N-1),要想知道F(N-1)必先知道F(N-2)......依次往下.
每次遞歸時候並沒有開辟變量數量,所以空間復雜度為O(n)