這是在stackoverflow的關於 「演算法複雜度」 的經典問答。
將此文翻譯並分享下。
問: by arec barrwin
大o符號的簡明英語解釋
大o符號的簡明英語解釋是什麼? 用盡可能少的形式定義和簡單的數**算。
答: by cletus
注意,所有人幾乎都會混淆大o符號(有上界)和theta符號(有兩個邊界)。從我的經驗來看,這是實際上是典型的非學術性設定討論。對於造成的任何不明確、含糊的地方,我表示歉意。
我能給出的最簡單的大o符號的定義如下:
大o符號是一種演算法複雜度的相對表示。
在這個句子裡有幾個重要的、特意選出的詞:
· 相對:你只能用蘋果和蘋果比較。不能用一種乘法運算的演算法和整數分類的演算法比較。但是兩個算術運算(乙個乘法,乙個加法)演算法的對比是有意義的;
· 表示:大
o(最簡單的形式)減少了單變數演算法之間的對比。變數是基於觀察和假設而選出來的。例如,分類演算法是典型的基於對比運算(對比兩個節點,確定他們的相對順序)的對比。這裡假設對比是昂貴的。但如果對比是廉價的,而交換是昂貴的,會怎樣?它改變了對比;且
· 複雜度:如果我需要
1秒鐘將
10000
個元素分類,那麼我需要多久將
1百萬個元素分類?這個例子裡的複雜度是對另一些東西的相對測量。
當你讀完剩下的部分的時候,再回來讀一遍上面的內容。
我能想到的最好的大o符號的例子是做運算。選兩個數字(123456和789012)。我們在學校學到的最基本的算術運算子是:
· 加法
;· 減法
;· 乘法; 和
· 除法
.這些每乙個都是乙個運算或乙個問題。解決這些問題的方法被稱為演算法。
加法是很簡單的。將數字(右對齊)列出來,在一列寫下加法運算結果的最後一位。運算結果的『十位』上的數字要進製到下一位。
我們假設這些數字相加在這個演算法中是最昂貴的運算。顯然將兩個數字相加,我們必須將六位加到一起(可能進製到第7位)。如果我們將兩個100位的數字相加,我們要做100次加法。如果我們兩個10000位的數字,我們要做10000次加法。
看出這個模式了嗎?複雜度(運算的次數)是與較大數字的位數n成正比的。我們稱其為o(n)或者線性複雜度。
減法很相似(除了你可能會借位,不再是進製)。
乘法就不一樣了。把數字都列出來,取下面數字的第一位,上面數字的每一位依次與其相乘,剩下的每一位都是這樣。所以兩個6位的數字相乘,我們需要做36次乘法。最終結果可能需要多達10或11位。
如果我們有兩個100位的數字,我們需要做10000次乘法和200次加法。對於兩個一百萬位的數字,我們需要一萬億(1012)次的乘法和兩百萬次的加法。
演算法與n次方成正比,也就是o(n2)或二次複雜度。這正是個介紹另乙個重要概念的好時機:
我們只關心複雜度中最重要的部分。
聰明的人會發現,我們可以將運算的次數表示為n2 + 2n。但是從我們那個兩個一百萬位的數字的例子中,你可以看出第二個數(2n)不是很重要(因為在此例中僅佔所有運算次數的0.0002%)。
可能有人注意到我們在這裡假設的是最糟糕的情況。當我們乘以6位數的數字時,如果其中乙個是4位,另乙個是6位,那麼我們只需要做24次運算。我們仍然按最糟糕的情況計算n,例如當兩個數字都是6位數字時。因此大o符號是關於演算法的最糟糕的情況。
**本
我能想到的下乙個很好的例子是**本,一般我們叫它白頁或者其他類似的名字,但是每個國家的**本都不同。我所指的是那種按姓氏列排列,姓氏後面是姓名中的大寫字母或者名字,或許還有位址和**號碼。
現在如果你使用電腦在有1000000個名字的**本中查詢「john smith」的**號碼,你會怎麼做?忽略你可以猜測以s開頭的姓氏從哪一頁開始的事實(我們假設你不能這麼做),你會怎麼做?
一種典型的做法可能是開啟中間部分,取第500000個名字與「smith」對比。如果恰巧他正是「smith, john」,那麼我們的運氣真是太好了。但是更可能是「john smith」這個名字在此之前或之後。如果在這之後,我們再開啟後一半**本的中間部分,再重複一次上面的操作。如果在這之前,我們開啟前一半**本的中間部分,再重複一次上面的操作。以此類推。
這稱為二分搜尋法,而且無論你有沒有了解它,每天的程式設計工作都要用到它。
所以如果你想要在有一百萬個名字的**本中找到乙個名字,實際上你可以通過這種方法最多20次就能找到你想要找的任何乙個名字。在對比搜尋演算法中,這個對比次數就是我們的『n』。
· 對於一本只有
3個名字的**本,(最多)只需要
2次對比。
· 對於
7個名字的**本最多需要3次。
· 對於
15個名字的**本最多需要4次。
· …
· 對於
1000000
個名字的**本最多隻需要
20次。
這非常好,不是嗎?
在大o符號術語中,這是o(log n)或者對數複雜度。現在問題中的對數可能在自然對數、log10, log2或者以其他為底的對數中。是不是o(log n)不是太重要,比如o(2n2)和o(100n2)都是o(n2)。
從這點上值得這樣解釋,大o符號可以應用於確定演算法的這三種情況:
· 最好的情況:在搜尋**本時,最好的情況是在第一次對比時就找到了那個名字。這是
o(1)
或常量複雜度;
· 預期的情況:如我們在上面討論的是
o(log n);
且· 最糟糕的情況:這也是
o(log n)
。一般我們不太關心最好的情況。我們對預期的和最糟的情況更有興趣。有時候其中乙個或另乙個更為重要。
讓我們回到**本的例子上來。
你從第乙個姓名開始,與這個號碼對比。如果它恰巧就是這個號碼,那就太棒了,如果不是,再繼續對比下乙個。你不得不這麼做,因為**本是亂序的(以**號碼來看)。
所以要找到乙個姓名:
· 最好的情況:
o(1)
;· 預期的情況:
o(n)
(對於500000
);· 最糟糕的情況:
o(n)
(對於1000000)。
旅行推銷員
在電腦科學裡這是個非常有名的問題,值得一提。在這個問題中,你有n個城鎮。每乙個城鎮都通過一定距離的道路連線到另外乙個或多個其他城鎮。旅行推銷員的問題就是尋求一條走訪所有城鎮的最短的路。
聽起來很簡單?再想想。
· a → b → c
· a → c → b
· b → c → a
· b → a → c
· c → a → b
· c → b → a
實際上,最短道路的選擇要比這少,因為其中一些是一樣的(例如,a → b → c和c → b → a是一樣的,因為他們走的是同樣的路,只不過反過來了)。
實際上只有三種可能:
· 去
4個城鎮,(如果我沒記錯的話)有
12個可能。
· 5
個城鎮,有
60個可能。
· 6
個城鎮就成了
360個可能。
這個數**算的方法稱為階乘。基本上是這樣的:
· 5! = 5 × 4 × 3× 2 × 1 = 120
· 6! = 6 × 5 × 4× 3 × 2 × 1 = 720
· 7! = 7 × 6 × 5× 4 × 3 × 2 × 1 = 5040
· …
· 25! = 25 × 24× … × 2 × 1 = 15,511,210,043,330,985,984,000,000
· …
· 50! = 50 × 49× … × 2 × 1 = 3.04140932 × 1064
所以旅行推銷員問題的大o符號是o(n!)或者階乘或者組合複雜度。
當你有200個城鎮的時候,在整個宇宙中都不會足夠的時間讓傳統計算機解決這個問題。
想一想。
多項式時間
另外乙個我想很快提一下的是,任何有o(na)複雜度的演算法據說有多項式複雜度或者是可以用多項式時間解決的。
o(n)、 o(n2)等都是多項式時間。許多問題不能用多項式時間解決。世界上使用某些事物正因為此。公鑰密碼學是乙個主要的例子。找到乙個很大的數的兩個質因數在計算上是非常困難的。如果不這麼做,我們就不能使用公鑰密碼系統。
不管怎樣,這就是我的大o符號(修改的)解釋(希望是簡明)。
此文在cc-by-sa 3.0許可證下使用
簡明解釋演算法中的大O符號
大o符號是一種演算法複雜度的相對表示方式。這個句子裡有一些重要而嚴謹的用詞 我所能想到的大o符號最好的例子就是做算術。拿兩個數字 123456和789012 舉例。我們在學校裡學到的基本算術操作是 它們中每乙個都是一次操作或乙個問題。為它們求解的方法就被叫做演算法 algorithm 加法是最簡單的...
對volatile的簡明解釋
url volatile 關鍵字表示字段可能被多個併發執行執行緒修改。宣告為 volatile 的字段不受編譯器優化 假定由單個執行緒訪問 的限制。這樣可以確保該字段在任何時間呈現的都是最新的值。其實volatile是由於編譯器優化所造成的乙個bug而引入的關鍵字。int a 10 int b a ...
常用符號的解釋
1.吞吐量和頻寬 吞吐量是指對網路 裝置 埠 虛電路或其他設施,單位時間內成功地傳送資料的數量 以位元 位元組 分組等測量 通常用來表示系統的測試效能。頻寬的單位和吞吐量相同,但是頻寬用來指最大傳輸速率,因此由一段頻寬為10mbps的鏈路連線的一對節點可能只達到2mbps的吞吐量。2.rmse ro...