資料結構與演算法(二) 複雜度

2021-08-28 16:23:25 字數 4494 閱讀 3917

國慶假期第三天,昨天出去胡吃海喝,和朋友出去逛逛寺廟,然後今天早上又來實驗室,給貓貓鏟臭臭真的臭死我了,一見我就往我身上衝emmmmmmmmmm,今天就把上週就該寫的複雜度分析這塊寫下,因為這塊真的還蠻重要的

//我看的是極客時間上乙個老師講的,就是把他講的梳理一下

我覺得複雜度這塊主要就是這些,而且分析最多的也是時間複雜度

這個問題,我之前學的時候重來沒想過,寫專案的時間重來沒想過,好像生活中自己處理事情的時候也沒怎麼去想自己處理事情的方法所耗費的時間和資源

但是我之前刷acm題的時候,有幾次明明自己寫的演算法是正確的,但是**判定的結果就是錯誤的,要麼是時間超了,要麼是空間超了,同樣的一道題,不同人用的方法不一樣,就是結果雖然可能一樣,但是別人的演算法就是沒有超時,或者比自己的所耗費的時間少emmmmmm,這裡就是涉及到複雜度的問題了。

但是這塊複雜度又如何去算啊?

好像有方法,就是把**跑一遍,然後看下時間,看下消耗的空間,這樣好像也可以唉,但是仔細想下,這個方法很矛盾。

因為我不可能每次寫個演算法就都重新跑一遍,然後看下時間和空間emmmm這樣有點傻,但是意思可以的

這個方法有兩個很大的侷限性:

測試結果非常依賴測試環境

這個很明顯啊,我拿個高處理器的電腦去處理同一段**,和拿個低處理器的處理,肯定高處理器的在其他條件相同的情況下的時候,它處理的時間快啊

測試結果受資料規模的影響很大

就好比排序演算法一樣,資料規模大小很影響排序演算法的效能

說了這麼多,我們到底需要什麼樣的分析演算法的方式

大o表示法:稱乙個函式g(n)是o(f(n)),當且僅當存在常數c>0和n0>=1,對一切n>n0均有|g(n)|<=c|f(n)|成立,也稱函式g(n)以f(n)為界或者稱g(n)受限於f(n)。記作g(n)=o(f(n))。 定義:如果乙個問題的規模是n,解這一問題的某一演算法所需要的時間為t(n),它是n的某一函式。t(n)稱為這一演算法的「時間複雜度」。當輸入量n逐漸加大時,時間複雜度的極限情形稱為演算法的「漸近時間複雜度」。

因為我們要看**的執行效率,所以我們要看**的執行時間和佔的空間//佔的空間這裡簡單的小規模演算法先不考慮

所以,我們在乎的**執行的時間,但是我們又如何估算呢?

先看下這個**

int

text

(int n)

return sum;

}

我們看下上面這個**塊,一共就執行的就4個重要**,每段**塊就類似操作,儘管不一樣,但是按照現在cpu執行的那麼高速的樣子,其實每個**都只執行一點點時間,所以我們認為他們執行的時間都是一樣的,記成一time

現在上面的**,第1個標記,第2個標記,分別執行了乙個time,然後第3個標記和第4個標記的位置的,是分別執行乙個n*time的時間

所以,總的來說,這段**執行時間就是(2n+2)*time,隨著n的增大而增大

我們設**執行的總時間為t(n)

所以,t(n) = (2n+2)*time **執行時間和n成正比

所以,**執行時間和每行**的執行的次數成正比

綜上所述,大o表示法就出來了

t(n) = o(f(n))

上面公式的個個含義:

要注意的點:大o時間複雜度並不是**的具體執行時間,只是一種趨勢,一種變化的趨勢

當n特別特別大的時候,可以忽略不記常數項對這個的影響,我們上面**裡面,t(n) = (2n+2)time 展開時候發現t(n) = 2time+2ntime,當n為無窮大的時候

其實最後的結果就是:t(n) = o(n)

因為我們上面也看到了,大o就是一種變化趨勢,那些計算的常量什麼的其實都不重要,因為當n無窮大的時候,其他的常量,低價,係數什麼的其實都沒什麼作用,所以,我們分析一段**的時候,只需要看他迴圈次數最多的地方

int

text

(int n)

return sum;

}

我們繼續看這個例子,這段**就是只是關心下,執行次數最多的那給迴圈,其他的地方都無關緊要

先看下下面的**

int

text

(int n)

for(i =

0;i < n;i++

)//3

for(i =

0;i < n;i++)}

return sum1+sum2+sum3;

}

我們再進行分析

第一段**,就是執行了100次,所以這個就是個常量時間,和n無關

//就是能明顯知道執行次數是具體數字的時候,這個常量就不能省略

第二段**,時間複雜度就是o(n)

第三段**,時間複雜度就是o(n*n)

然後我們要求這段總的**的時間複雜度的時候,還是比較下,選最大的那一段,因為還是那句話,把n當成無窮大時候,就可以比較了

所以,這段**的時間複雜度是o(n*n)

這個也好理解啊,冒泡就是典型的例子,但是我們還是看下下面的例子

intf(

int n)

return sum;

}intff(

int n)

return s;

}

這個**也是很簡單,就是乙個**裡面呼叫另乙個的函式

所以總的複雜度就是

t(n) = t(n1) * t(n2) = o(n*n)

下面舉例子說明

o(1)不是只是執行了一行**的意思,它只是表示時間複雜度

int i =

1;int j =2;

int sum = i + j;

這個整體有3行**,但是還是o(1)的時間複雜度

只要**的執行時間不隨著n的增大而增大,就是o(1),一般情況下,就是演算法中沒有迴圈,遞迴這些的

這塊高中數學要學好哇哇哇,對數這塊都忘完了

看下下面的**

int i =1;

while

(i <= n)

我們要求這塊的時間複雜度就發行很尷尬,不知道要求多少次

一步一步分析,當變數i從1開始取的時候,每次迴圈都是乘以2,當它大於n的時候,迴圈結束

然後我們分析下i的變數的取值 2 ,22 ,222 , 22…

也就是2的階乘

當2的x次方大於n的時候,就迴圈結束

所以 x = log2 n

所以,這個**的時間複雜度就是o(log2 n)

然後我們在記對數的時候,通常忽略係數

所以,複雜度統一為o(lngn)

再利用乘法法則,像什麼快排的時間複雜度就是o(nlong n)

這塊就是**裡面有兩個資料規模,不是乙個n了,所以這塊要看下

int

text

(int m ,

int n)

for(i =

0; i < n;i++

)return sum1 + sum2;

}

之前我們說的加法法則是看最大的那塊,但是我們現在不知道那塊最大,m,n都可以無窮大判定下,所以沒辦法比較,因此,這塊的時間複雜度就是o(m+n)

乘法法則類似的套路

我們看了時間複雜度這塊的概念和大o的表示方法,為什麼還有有這四種情況呢,因為傳入的資料可能會影響到最後的結果

很明顯的例子就是排序演算法

先看下乙個例子

就是乙個簡單的從陣列中找個數,看它在不在

int

find

(int

array,

int n,

int x)

}return pos;

}

這塊的分析也就尷尬了,因為我們發現這塊程式結束的條件就是找到這個數就結束,或者找不到返回-1,遍歷的過程次數不確定

當如果我們要找的數在第乙個的時候,找到了,直接break了,那麼時間複雜度就是o(1)

如果都找完了還是沒找到,這個時候複雜度就是o(n),所以,不同情況下,這段**的時間複雜度也是不同的

所以,引進了最好、最壞,平均,均攤情況時間複雜度的概念

在最最理想的情況下,執行這段**的時間複雜度,這塊很好理解,比如我演算法就是要找元素,第乙個就是,所以,執行一次就找到了

在最最最壞的情況下,執行這段**的複雜度,還是上面的例子,很簡單,就是我找陣列還是沒找到,這就是最壞的情況

在**中心所有情況的次數加權平均表示

在**執行的所有複雜度情況中絕大部分是低階別的複雜度,個別情況是高階別複雜度且發生具有時序關係時,可以將個別高階別複雜度均攤到低級別複雜度上。基本上均攤結果就等於低級別複雜度。

下次分析時間複雜度的時候,自己也要分析下各種情況

資料結構(二) 演算法複雜度

演算法中執行次數最多的那條語句就是基本語句,測定執行時間就是計算基本語句的執行次數 可以忽略加法常數 與最高此項相乘的常數並不重要 最高次項的指數越大,增長越快 判斷乙個演算法執行效率時,函式中的常數和其他次要項常常可以忽略,更應該關注主項 最高端項 的階數 某個演算法,隨著n增大,它會越來越優於另...

資料結構與演算法 時間複雜度與空間複雜度

解決乙個問題的方法可能有很多,但能稱得上演算法的,首先它必須能徹底解決這個問題 稱為準確性 且根據其編寫出的程式在任何情況下都不能崩潰 稱為健壯性 程式和演算法是完全不同的概念。演算法是解決某個問題的想法 思路 而程式是在根據演算法編寫出來的真正可以執行的 例如,要依次輸出一維陣列中的資料元素的值,...

資料結構 演算法複雜度

二 演算法的效能評價 三 真題解析 資料結構的第一章 緒論 包含的最後乙個重要內容是關於演算法的複雜度。這個考點一般會單獨出現在選擇題的前兩道,需要你熟練掌握演算法的基本概念 演算法時間複雜度和空間複雜度的分析判斷等。其次,還會出現在大題的程式設計部分,將演算法複雜度作為乙個限制條件,要求你給出滿足...