演算法速度影響因素的本質
表面上,演算法速度的影響因素繁多,但事實上,如果我們窮根究底的話,也會在這個看似繁亂無序的世界裡找出一些本質的東西。
先考慮這麼乙個問題:
如果b地在a地正東方,乙個人要從a地去b地,那他可以有什麼方法來縮短所花的時間?
第一當然是交通工具。選擇汽車和步行自然不可能是相同的效果,這對應與下文的第一部分:函式儲存
第二是不走彎路,若他向東南方向走了,那他就在南北方向上有了向南的偏移,之後他就必須向東北方向走來抵消剛才的偏移,這對應與下文的部分:計算冗餘。
現在讓他的行進速度和其經驗相關,比如,翻過第一座山後由於有了經驗,今後翻山的速度會有很大的提公升,這對應於下文的第二部分:過程儲存
1 函式儲存
計算機能以最快速度實現的操作,我們將稱之為基本操作。比如簡單的加減、邏輯運算,儲存器讀取寫入,if...then語句等等。
顯然,計算機能進行的基本操作越多,演算法速度就越容易提公升。圖靈機可進行的基本操作是很少的,乙個簡單的加法都要計算上很多步,因此沒人會去試圖造台圖靈機作電腦用^_^。
對輸入為有限種可能的情況,我們可以建立乙個雜湊表,將輸入直接對映到記憶體位址上,而儲存的內容為相應輸入的輸出結果,這樣便產生了乙個執行時間幾乎和一次基本操作相當的演算法。這樣其實就是相當於把所要計算的函式直接儲存到了記憶體裡,使計算機又多了乙個基本操作。可以看出這樣對演算法的速度提公升會是驚人的(雖然是犧牲空間得到的)。這樣方法其實我們在作算術中就應用到了:小學生背乘法表是為了什麼?儲存一系列函式,等計算更複雜的乘法時便可以將它分解成這些函式,然後快速得到結果。相必沒人作乘法是先拆成加法,再乙個個加起來吧?
綜上所述,當演算法中出現對乙個函式的頻繁呼叫,而這個函式又是定義域有限且元素不多的,我們就可以把該函式儲存到記憶體裡,做成乙個基本操作,這樣就會對演算法效率有質的影響。
但基本操作少了也有它的好處,這樣各種操作容易各司其職,功能互不交叉,要用操作a完成的功能b肯定不能替代,比如說變數值加1操作和goto語句。這樣以來,要實現某個功能時就不需在各個可行的基本操作中進行孰優孰劣選擇了。所以想用函式儲存優化演算法是很困難的,如果有一天計算機可以自己生成演算法了,它是基本上不可能掌握上面所說的這種方法的,除非依賴人工智慧。它可以輕鬆掌握的方法或許應該是下面這個:
2 過程儲存
下文中將出現的「過程」不同於一般程式語言中「過程」的概念,它是指函式的一次特定呼叫,包括輸入值和被呼叫函式。呼叫乙個過程所返回的值即該輸入值經該函式計算後的結果。在一次計算的某個時刻,訪問任乙個變數都相當於呼叫了乙個過程,那是向前訪問過去的計算結果,而呼叫函式是向後將資料或控制權傳給未來的函式;反過來也成立,向過去只可能出現過程呼叫,而向未來只可能出現函式呼叫。
資料結構對演算法速度的影響是和電子計算機提供的兩個基本操作相關的,就是記憶體的讀/寫。和他們相關的其實還有建立中間變數之類一切和儲存/讀取相關的操作。
建立合適的資料結構,建立合適的中間變數都有可能大幅度提高演算法效率,那它們的本質是什麼呢?
乙個量的儲存便意味著今後可隨時以乙個單位時間的運算速度呼叫計算出該量的過程,這樣,如果乙個過程在一次計算中出現多次,那麼將其第一次執行所得結果儲存從而替代後來的多次運算無疑是高效的,這正是變數存讀問題的關鍵所在:將計算過的結果儲存,之後免除了計算過程,我們暫且稱之為過程儲存。
顯然,過程儲存是不同於函式儲存的。後者是演算法設計時就將函式「儲存」了,它是執行中「儲存」的;後者是通過增加基本操作來提高速度,而前者只是避免了重複計算,並未增加基本操作。
過程儲存和函式儲存一樣,也是適用於當演算法中出現對乙個函式的頻繁呼叫,而這個函式又是定義域有限且元素不多的時候。當然,它的效率會稍低。
過程儲存還有另乙個優美的描述,就是跨時間的資訊傳遞。我們不妨把儲存和讀取的概念從視野中完全抹掉,乙個量的儲存只是將其傳遞給了未來的乙個或多個函式,而讀取只是接受了過去某個過程傳來的輸入。這樣,過程的呼叫概念就更廣了,成為了跨時間的概念。
這樣我們不難理解中間變數和資料結構對演算法影響的實質了,前者充當了呼叫過去過程的橋梁,避免了同一過程的重複計算,而後者是前者更複雜的形式,而且與指標關係緊密,所以我們暫且只舉例說明。
例:某演算法的輸入中包含一字串s,演算法執行中將對s進行大量的查詢工作。
如果僅靠以上資訊而且不考慮空間複雜度的話,我們知道雜湊表是儲存s的最佳選擇。
s中出現的所有字元通過雜湊函式f對映到不同的記憶體位址,而記憶體中的資料則為相應各字母在字串中的位置。這樣要比不改變s的資料結構而每次查詢都用遍歷s的方法好的多。
我們來分析它和過程儲存的關係:建立雜湊表時,若在記憶體位址為a(值為f(k))的項中寫入了乙個陣列,它儲存了k出現在s中的所有位置。那麼,這就相當於向未來的函式提供了這樣乙個過程的呼叫:
遍歷s,記錄所有值等於k的元素在s中的位置並將其放入乙個陣列中,返回這個陣列。
當然,這個呼叫花費的時間是很短的,而遠非遍歷一遍s的時間。
資料結構和過程儲存的關係至此還是不甚清楚,再作進一步的分析。
資料結構就是資訊組織在計算機中的形式。這個概念其實也是很模糊的,什麼是資訊?什麼是組織形式?為什麼組織形式對演算法速度影響如此之大?
資訊是可以用函式來表示的。比如,乙個字串可用乙個以字元在其中的位置為自變數,字元為函式值的函式,即:
f:位置-->字元
資訊即若干量的值之間的關係。但在實際中將其以某種形式表示出來時,往往只能表示出該關係的乙個單向的形式,如上例中的字串存入乙個陣列後,該陣列就包含了字串的全部資訊,但它卻只能提供乙個單向的訪問,即由位置得到字元,若要由字元得到其對應的位址,哦,sorry,本陣列不提供該項服務,請自己您寫**遍歷查詢。遍歷自然意味著低效。
這是和計算機記憶體的先天不足有關的:它只能由位址找到對應內容,卻不能由內容找到對應位址。
為了提高演算法速度,當然應盡量避免演算法執行過程中出現這樣的情況。比如,如果演算法只需要由字元得到對應位址,就不能用 位置-->字元 的形式儲存了,要換成 字元-->位置,這就是雜湊表了。如果需要,還可以構造 位置<-->字元 的形式,那樣資料結構會更複雜。
可見,資訊的組織形式就是在記憶體中,資訊所包含的各量之間的可訪問關係。
有一點是不能忽略的,那就是資訊最初輸入計算機的形式未必就是演算法的最適結構,因此需先把這種結構轉換成最適合演算法的結構,這個轉換正是過程儲存的過程。
3 計算冗餘
下面我們只考慮數值計算中的情況。
且看這一段**:
a++a--
恐怕天下沒比這更弱智的**了吧?可它確實是代表了一類的影響因素,也就是我們要稱之為計算冗餘的東西。
看這個函式:
f(x)=5x-3x
我們可以馬上寫出兩種計算它的演算法,乙個按部就班的算(演算法a)而另乙個直接算2x的值(演算法b)。然而二者在執行時間上是有不小差異的,影響二者執行時間的因素,正是計算冗餘。
對演算法a,如果用乙個變數y來儲存結果的話,它是先被迴圈加x加5次,再被迴圈減x減3次,中間經過了先增大後減小的過程;而對演算法b,卻只是個增大的過程。它可以看成是a中增大x和減小x一對操作互相抵消的結果。
顯然,計算冗餘是由一對效果相反的操作同時出現在演算法中造成的不必要的計算。但是否演算法只要出現了相反操作就意味著還可以化簡呢?顯然不是,比如這個函式:
g(x,y)=1+x-y
直接計算它的演算法我們可以馬上寫出來,計算中必然會出現相反的操作,但是它已經是最簡單的形式了,我們不可能靠抵消相反操作來化簡它。
這二者的區別其實在於:前者計算中的一對相反操作(增大x和減小x)所進行的次數(5次和3次)是不決定於輸入變數的,是僅靠演算法本身就能確定的,它是演算法本身所具有的乙個屬性,故我們可以依據它來進行演算法的化簡;而對於後者,一對相反操作(加1和減1)的次數(y次和z次)是不決定於演算法本身的,它隨著輸入變數不同的值而改變,因而是不可化簡的。
我們可作如下總結:
乙個演算法中若出現了效果相反的操作且二者的次數都不是由輸入變數決定的,那這個演算法可以用抵消的方法化簡。
雖然是在數值計算的例項中推出來的,可這個思想並不只適用於數值計算,它應該是對任何資料型別都有效的。
影響上傳速度的因素
http上傳 客戶端 上傳資料 到伺服器。上傳速度決定於 1 客戶端,2 伺服器,3 路由 1 客戶端 1.1.是否保持長連線,對於多個小檔案的上傳,保持同乙個連線非常有效 1.2.連線數,過多或過少都不好 1.3.傳送資料的粒度大小 1.4.讀檔案的效率,最好非同步讀檔案,先將資料快取好,待傳送資...
影響網路速度的因素
關於網速的問題,我做了些總結,寫的雖有些繁瑣,但是為了讓各位工程師從不同的角度更全面的去考慮網速慢的因素,方便解決問題,還請各位仔細看一看,由於時間倉促和水平有限,可能有遺漏或不妥的地方,還請各位指教。我是從各種環境 網路環境,系統環境,軟體環境,硬體環境 自然環境等 來分析這個問題的,大家也可以從...
影響網路速度的因素
關於網速的問題,我做了些總結,寫的雖有些繁瑣,但是為了讓各位工程師從不同的角度更全面的去考慮網速慢的因素,方便解決問題,還請各位仔細看一看,由於時間倉促和水平有限,可能有遺漏或不妥的地方,還請各位指教。我是從各種環境 網路環境,系統環境,軟體環境,硬體環境 自然環境等 來分析這個問題的,大家也可以從...