11行虛擬碼給你講明白什麼是演算法

安全劍客發表於2020-09-13

導讀 演算法(algorithm)就是一個過程,是一種特殊的過程。它必須描述為一個有限步驟序列,且必須在有限時間內結束。每個步驟必須是良好定義的,達到人類可用一支筆和一張紙執行它的程度。

11行虛擬碼給你講明白什麼是演算法11行虛擬碼給你講明白什麼是演算法
演算法(algorithm)就是一個過程,是一種特殊的過程。它必須描述為一個有限步驟序列,且必須在有限時間內結束。每個步驟必須是良好定義的,達到人類可用一支筆和一張紙執行它的程度。

演算法基於我們提供給它的輸入做一些事情,並生成反映其所做工作的一些輸出。演算法1-1實現了我們前面描述的過程。

演算法1-1 一個簡單的股票跨度演算法

SimpleStockSpan(quotes)→spans

輸入: quotes,儲存n個股票報價的陣列
輸出: spans,儲存n個股票跨度的陣列

spans←CreateArray(n) 
for i←0 to n do 
    k←1 
    span_end ← FALSE 
    while i-k ≥ 0 and not span_end do 
       if quotes[i-k] ≤ quotes[i] then 
           k←k+1 
       else 
           span_end ← TRUE 
    spans[i] ← k 
return spans

演算法1-1展示瞭如何描述演算法。我們並不使用某種計算機語言,因為那樣會迫使我們處理與演算法邏輯無關的實現細節,我們使用的是某種虛擬碼(pseudocode)形式。

虛擬碼是一種介於真正的程式程式碼和非形式化描述之間的形式。它使用一種結構化格式,並採用一組具有特定含義的詞彙。但是,虛擬碼不是真正的計算機程式碼。它並不是為了被計算機執行,而是易於被人類理解。

順便提一下,程式也應能被人類理解,但並非所有程式都是如此——有很多正在執行的計算機程式寫得很糟糕,難以理解。

每個演算法都有一個名字,接受一些輸入,並生成一些輸出。在本書中,演算法的名字將採用駱駝拼寫法(CamelCase),輸入會寫在括號中,輸出用一個→指示。接下來的幾行將會對演算法的輸入和輸出進行描述。可以用演算法的名字緊接放在括號中的輸入來呼叫(call)演算法。

一旦演算法編寫好,就可以將其作為一個黑盒來處理,可以給它一些輸入,黑盒則會返回演算法的輸出。當用一種程式設計語言實現一個演算法時,它就是一個具名的計算機程式碼片段——函式(function)。在一個計算機程式中,我們呼叫實現演算法的函式。

某些演算法不生成輸出,當然也就不會顯式返回結果。取而代之的是,它們的行為影響上下文的某部分。例如,我們可能提供給演算法一個空間,供其寫入結果。在此情況下,在傳統意義上演算法並非返回輸出結果,但無論如何演算法是有輸出的,即它影響上下文發生的變化。

某些程式設計語言會區分顯式返回結果的具名程式程式碼片段——稱為函式(function),以及不返回結果但可能有其他副作用的具名程式程式碼片段——稱為過程(procedure)。這種差異來源於數學,數學上的函式是必須返回值的。對我們來說,當一個演算法編碼為實際程式時,既可以是一個函式也可以是一個過程。

我們的虛擬碼中使用一些用粗體表示的關鍵字,如果你對計算機和程式設計語言的工作方式有所瞭解,這些關鍵字的含義就是不言自明的了。

我們使用字元←表示賦值,用等號(=)表示相等比較。我們採用常用的五個符號(+,-,/,×,·)表示四種數學運算,後兩個符號都表示乘法,這兩個符號我們都會使用,基於美學考慮進行選擇。我們將不會使用任何關鍵字或符號對虛擬碼分塊,分塊是透過縮排來表示的。

在這個演算法中,我們使用了陣列(array)。陣列是一種儲存資料的結構,它允許我們按特定方式操縱其中的資料。我們儲存資料並允許在其儲存的資料上執行特定操作的結構稱為資料結構(data structure)。因此陣列是一種資料結構。

陣列之於計算機,就像物件序列之於人類。陣列是元素的有序序列,這些元素儲存在計算機記憶體中。為了獲得儲存元素所需的空間並建立一個儲存n個元素的陣列,可呼叫演算法1-1第1行中的CreateArray演算法。

如果你熟悉陣列,可能就會奇怪建立陣列怎麼還需要一個演算法。但實際情況的確如此。為了獲得儲存資料的一塊記憶體,你必須至少在計算機中搜尋可用記憶體並標記它為陣列所用。

CreateArray(n)呼叫做了所需的一切,它返回一個可容納n個元素的陣列,初始時其中沒有元素,只有儲存元素所需的空間。演算法負責呼叫CreateArray(n)來將實際資料填充到陣列中。

對陣列A,我們用A[i]表示其第i個元素,訪問該元素也是用該符號。一個元素在陣列中的位置,如A[i]中的i,被稱為索引(index)。一個n個元素的陣列A包含元素A[0],A[1],…,A[n-1]。

這可能令你吃驚,因為其首元素是第0個,而尾元素是第n-1個,可能你的預期是第1個和第n個。但是,大多數計算機語言中的陣列都是如此,你最好現在就熟悉這種機制。這非常常見,當遍歷一個大小為n的陣列時,我們是從位置0遍歷到位置n-1。

在我們的演算法中,當我們說某個物件的取值是從數x到數y(假定x小於y)時,意思是從x到y(但不包含)的所有值,參見演算法第2行。

我們假定無論i的值是什麼,訪問第i個元素都花費相同的時間。因此訪問A[0]與訪問A[n-1]需要相同的時間。這是陣列的一個非常重要的特性:對元素的訪問是一致的,都花費常量時間。當我們透過索引訪問陣列元素時,陣列不需要搜尋此元素。

關於演算法描述中的符號表示,我們用小寫字母表示演算法中的變數。但當變數表示一個資料結構時,我們會使用大寫字母來令其突出,如陣列A。但這並非必要。當我們希望給變數起一個包含很多單詞的名字時,我們會使用下劃線(_),如a_connector。這是必要的,因為計算機不理解由一組空格分隔的單詞構成單個變數名的方式。

演算法1-1使用陣列儲存數值。陣列可以儲存任何型別的項,在我們的虛擬碼中每個陣列只能儲存單一型別的項。大多數程式設計語言中也都是如此。

例如,可以建立十進位制數陣列、分數陣列、表示人的項的陣列以及另一個表示地址的項的陣列,但不可以建立一個既包含十進位制數又包含表示人的項的陣列。至於“表示人的項”會是什麼,由程式設計所使用的語言所決定。所有程式設計語言都提供表示有意義的東西的方法。

一種特別有用的陣列是字元陣列。一個字元陣列表示一個字串(string),即一個字母序列、一個數序列、一個單詞序列、一個句子序列等。與所有陣列一樣,我們可以用索引單獨引用陣列中的單個字元。如果我們有一個字串s=“Hello,World”,則s[0]為字母“H”而s[11]為字母“d”。

總結一下,陣列就是一個儲存相同型別項的序列的資料結構。陣列支援兩種操作:

CreateArray(n)建立一個能儲存n個元素的陣列。陣列未初始化,即它不儲存任何實際元素,但儲存元素所需的空間已預留,可用來儲存元素。
正如我們已經看到的,對一個陣列A,A[i]訪問其第i個元素,而且訪問陣列中任何元素都花費相同時間。若i<0,則試圖訪問A[i]會產生錯誤。
我們回到演算法1-1。如前所述,演算法第2~10行是一個迴圈,即一個反覆執行的程式碼塊。如果我們有n天的報價的話,迴圈執行n次,每次計算一個跨度。變數i表示我們正在計算跨度的當前這一天。初始時,處於第0天這一最早的時間點。每次執行第2行程式碼時,就會推進迴圈到第1,2,…,n-1天。

我們使用變數(variable)k指示當前跨度的長度——在我們的虛擬碼中,變數就是一個引用某些資料的名字,那些資料的內容,或者更精確地說,變數的值(value),在演算法執行的過程中是可以改變的,變數這個術語因而得名。當我們開始計算一個跨度時,k的值總是1,我們是在第3行設定這個初值的。

我們還使用了一個指示變數(indicator variable)span_end。指示變數取值TRUE或FALSE,指出某事成立或不成立。當我們到達一個跨度的末端時,變數span_end的值將為真。

在開始計算每個跨度時,span_end為假,如第4行所示。第5~9行的內層迴圈計算跨度的長度。第5行告訴我們,只要跨度還未結束,就回退儘可能長的時間。我們能回退多遠由條件i-k≥0決定:回退到索引i-k指示的這一天檢查跨度是否結束,而索引不能為0,因為0對應第1天。

第6行檢查跨度是否結束。如果跨度未結束,則在第7行增加其長度。否則,我們注意到,第9行設定跨度結束,從而迴圈會在回到第5行後終止。

第2~10行的外層迴圈在第10行結束一次迴圈時,我們在此將k的值儲存到陣列spans的正確位置。在退出迴圈後的第11行,我們返回spans,它儲存著演算法的結果。

注意,初始時我們設定i=0和k=1。這意味著在最早的時刻第5行的條件必定為假。這是理所應當的,因為第0天的跨度只能為1。

此時此刻,記住我們曾說過的關於演算法、筆和紙的內容。理解一個演算法的最好方法就是去手動執行它。

在任何時候如果一個演算法看起來有些複雜,或者你不確定是否已完全理解它,就用紙和筆寫下執行它求解某個例子的過程。這種方法會節省你很多時間,雖然它看起來有點老套。如果對演算法1-1還有不明確的地方,馬上嘗試這種方法,當演算法已完全清晰後再回到這裡。

關於作者:帕諾斯·盧裡達斯(Panos Louridas),曼徹斯特大學軟體工程博士,現為雅典經濟與商業大學管理科學與技術系副教授。在加入高校之前,曾在投資銀行擔任高階軟體工程師。

原文來自:

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69955379/viewspace-2719169/,如需轉載,請註明出處,否則將追究法律責任。

相關文章