天天看點

《從問題到程式:用Python學程式設計和計算》——3.3 程式終止性

本節書摘來自華章計算機《從問題到程式:用python學程式設計和計算》一書中的第3章,第3.3節,作者:裘宗燕 更多章節内容可以通路雲栖社群“華章計算機”公衆号檢視。

本節讨論一個與循環和遞歸有關的重要問題:程式的終止性。一個程式的執行是否一定結束,這是一個很重要的問題。例如程式對所有可能的輸入是否都能結束(終止),或者函數對所有的參數是否都能終止并給出結果。如果一個程式對于某些輸入(或一個函數對于某些參數)不終止,遇到這些輸入或參數,程式就會無窮無盡地運作下去,既不給出結果,也不報告運作錯誤。這種情況通常也不符合使用者的需要。在很多實際應用中,這種情況是絕對不能允許的。請考慮一部汽車的刹車控制程式。

首先可以想到,隻要程式裡的每個基本語句都終止,直線型和分支型程式就必然終止。但循環程式的情況則不同,即使循環體中的語句保證終止,這個循環也可能不終止。如果在一個while循環的執行中,循環體裡語句的執行不能把循環條件變為假,這個循環就會無休止地繼續下去,循環之後的語句永遠也得不到執行的機會。遞歸函數(遞歸程式)也存在類似的問題,它也可能無窮無盡地遞歸調用下去 。

但另一方面,有些程式可能确實需要運作很長時間,等了很久也沒有得到結果,并不一定說明這個程式不能結束。

3.3.1 調和級數的部分和

看一個例子:由數學知識可知,下面調和級數的部分和增長非常慢:

《從問題到程式:用Python學程式設計和計算》——3.3 程式終止性

現在希望了解這個部分和的增長情況,考察該級數的前多少項之和能超過某個給定的數。可以定義下面的函數:

用一些參數值做試驗:

可以看到,程式輸出3個結果後很長時間都沒有進一步的動靜。在這種情況下,我們沒有辦法判定究竟是程式出了問題,永遠不會結束,還是過一會就能得到結果。為了了解程式運作的情況,可以考慮在循環裡加一個輸出資訊的語句:

這個語句要求每一百萬次循環輸出一次部分和。在執行harmony(20)時,可以看到輸出的值慢慢地增長。隻要等的時間足夠長就能得到結果。但如果調用harmony(25),考慮到浮點數的計算誤差等,我們還能得到結果嗎?請讀者考慮這個問題。

3.3.2 程式終止性不可判定

有關程式的終止性問題,被人們稱為計算機之父的英國數學家圖靈給出了一個理論結論(1934年):程式終止性問題是不可判定的。也就是說,我們不可能做出一個判斷程式終止性的軟體工具,使其對任何程式和程式的任何輸入,都能給出該程式将終止或不終止的結論。在實際中,有時判斷一個程式是否終止也很困難。看下面的簡單程式。

【例】本問題稱為collatz猜想(collatz conjecture)。德國數學家lothar collatz猜想下面函數對所有正整數n終止:

《從問題到程式:用Python學程式設計和計算》——3.3 程式終止性

如果這個函數對所有的正整數終止,那也就是說,對于任何正整數參數,本函數的值總是1,是以它就等價于總得到1的常函數。

很容易寫出python程式實作這個函數。下面是一個循環實作:

人們用大量的正整數試驗這個函數,發現它都終止,但至今無人能嚴格證明collatz猜想是正确的(即函數collatz終止),也沒人能證明這個猜想不正确,沒找到能使collatz函數不終止的輸入。人們通過試驗發現,采用一些參數,變量n可能經曆非常大的值或反複上下跳躍,但最終都能達到1,但卻沒有找到任何有價值的規律。

讀者可以基于上面程式做一些試驗,例如檢查它對具體的參數疊代多少次後才能達到1,或者考察在此過程中曾經達到的最大數值等。

雖然有上面的理論結論,我們不可能做出完成終止性判斷的系統(工具),但在實際工作中還是需要考慮這個問題,通過對具體程式的具體分析,設法确定自己寫出的程式一定能終止。這裡的基本問題就是檢查程式裡的循環或者遞歸,設法确認它們一定終止。隻要疊代器給出的序列有窮,相應的for循環一定終止。對于while循環,一種常用方法是設計一個基于循環中變量的整型表達式,證明該表達式的值随着一次次疊代嚴格下降,但其值一定不小于某個數(例如0)。如果這樣的表達式存在,該循環就一定終止。這裡不再深入,有關算法的專門書籍都有這方面讨論。

重看前面的程式,其中的大多數循環的終止性都比較容易判斷。另一些程式的終止性有數學上的保證,例如牛頓疊代法等。