1. 程式人生 > >從零開始學習PYTHON3講義(七)條件分支和哥德巴赫猜想

從零開始學習PYTHON3講義(七)條件分支和哥德巴赫猜想


《從零開始PYTHON3》第七講

人生是由無數個選擇組成,每個選擇都有不同的限定條件。現在來說人生有點早是吧:)不過事實的確是這樣的。

程式也充滿著選擇,滿足不同的條件,則執行不同的運算。這些對不同運算的選擇,則被稱為分支,或者叫“條件分支”。

在Python中,最簡單的條件分支是這個樣子(虛擬碼):

if 條件:
    滿足條件時執行的分支
條件不滿足時,或者條件滿足、執行完分支之後,會繼續從這裡開始執行

用真實的程式碼來看個例子:

#當a的值大於3的時候,顯示a的值
if a > 3:
    print(a)

以上是if分支最簡單的形式,完整的if分支使用偽程式碼表示是這樣:

if 條件一:
    條件一滿足時執行
elif 條件二:
    條件二滿足(條件一不滿足)時執行
elif 條件三:
    條件三滿足(條件一、二不滿足)時執行
...
else:
    所有分支條件均不滿足時執行

在其它的程式語言中,if分支通常最多隻處理兩種分支條件,更多的分支要使用if語句的巢狀,或者switch等命令。在python中這些不同的處理方式都被合併到了if分支語句。其中主要的體現就是elif子句,elif實際是“else if"的縮寫,這非常像if的巢狀使用,但又更簡單易用。
if語句完整的語法包含很多個部分,elif只是其中的一個部分,所以叫“子句”。每個elif子句對應一個分支條件和吻合條件後的分支。在一個if分支結構中,elif子句可以有很多個,這樣就可以用於對應很多種不同的分支條件。但是最初的if和最後的else只能有一個。

除了虛擬碼,我們還可以用流程圖來描述if語句的執行走向,從而加深印象:

if-flowchart1
相信你已經有了概念了。下面是一個真實程式碼的例子:

#條件分支示例
#作者:Andrew

#輸入
wallet=float(input("請輸入錢包裡的現金總數:"))

if wallet > 1000.0:
    print("今晚去吃大餐.")
elif wallet > 500.0:
    print("簡單吃一頓,然後去迪廳.")
elif wallet > 100.0:
    print("去吃個快餐,然後看場電影.")
else:
    print("回家看看有沒有剩飯,然後看電視吧.")

上面程式中,首先請使用者輸入一個數字,代表錢包中的現金總數,然後使用float函式把輸入變換為浮點數。使用float型別是因為,表達的是現金的總數,當然可能包含小數部分。如果不使用float函式,輸入的資料預設是字串型別,這個我們前面已經講過了。

隨後根據現金的數額,顯示不同的提示。提示資訊僅供娛樂,這裡是為了說明if語句的基本使用方式。你主要需要理解的部分就是,這些不同資訊,是根據不同的分支條件決定的。

在if語句中,真正控制程式走向的正是程式中所給出的條件,通常以條件表示式的方式存在。表示式的運算結果只有“真”、“假”兩種形式。這是邏輯型別,或稱bool(布林)型別,屬於數字型別的子型別,我們在第五章中講過了。
我們當時還講過,採用bool型別的原因之一是因為現代數學體系上完備的概念和體系。今天我們就在這個基礎上再進一步講述邏輯運算,或稱bool(布林)運算。


邏輯運算(布林運算)

bool型別只有兩個可能值,所以常見的bool運算方式也並不多,最常用的就是下面三種:

  • not 否定操作,比如下面兩條語句,從邏輯上是相同的:

    if not 性別=="女":
    
    if 性別=="男":    
  • and 邏輯“與”操作:and操作符兩邊的條件,必須都為真,結果才是“真”,否則結果是“假”,例如:

    if 性別=="男" and 16<年齡<25:
    #當性別為男,同時年齡在16到25歲之間(不包含16歲和25歲)時,執行
  • or 邏輯“或”操作:or操作符兩邊的條件,只要有一個是“真”,則結果就是“真”,全部為“假”,結果才是假,例如:

    if 年齡<=16 or 年齡>=25:
        print("條件不符")
    #當年齡小於等於16歲,或者大於等於25歲,則顯示“條件不符“

條件判斷本來挺好用,為什麼學這麼複雜的布林運算呢?我們來看一個例子你就會理解了:

假設某個男籃選秀,教練組提出了錄取的三個基本條件:

  • 男性
  • 年齡大於16歲,小於25歲
  • 身高超過2.1米

如果只使用邏輯判斷,不使用邏輯運算表示式,我們可能得到這樣的程式程式碼(虛擬碼):

if 性別=="男":
    if 16<年齡<25:   #注意這裡使用了連續判斷(if語句巢狀)
        if 身高>2.1:
            print("符合條件!")
        else:
            print("不符合條件!")
    else:
        print("不符合條件!")
else:
    print("不符合條件!")

對比如果使用邏輯運算的程式碼:

if 性別=="男" and 16<年齡<25 and 身高>2.1:
    print("條件符合!")
else:
    print("條件不符合!")

使用邏輯運算的程式碼乾淨、清晰,不易出錯。不過在初學的時候,你會感覺有點“燒腦” :)


迴圈中的分支

我們已經基本瞭解了分支語句的功能,上面舉的例子,基本都是通用程式中的分支處理。在常用的迴圈中,分支的處理又略有不同。

這些不同不是來自於分支語句本身,而往往是分支條件滿足之後,所要達到的效果。通常在迴圈語句塊中,我們常用到兩種特殊的處理:

  • 中斷迴圈的繼續,退出迴圈,從迴圈語句塊之後的第一條語句繼續執行程式的後續部分。這種情況下,使用break語句。
  • 繼續迴圈,但跳過本次迴圈的後續部分,從迴圈塊開始的部分執行下一次迴圈。這種情況下,使用continue語句。

      來看一個例子:
#迴圈顯示數字1-11,其中數字3、5跳過不顯示

i=0
#啟動一個無限迴圈
while True:
    i += 1
    #因題意,跳過數字3、5
    if i == 3 or i == 5:
        continue
    print(i)
    #因題意,超過數字11退出迴圈
    if i >= 11:
        break

因為我們的好習慣,大多需要解釋的內容,都已經在程式的給出註釋了。當然仍有幾點需要注意:

  • while True:語句,進入迴圈的條件和繼續迴圈的條件是True,這是一個立即數,也是常數。這使得迴圈成為一個永遠不停止的迴圈。
  • 當i的值是3或者5的時候,執行continue命令,這將跳過後面的顯示i值部分,從迴圈一開始重新執行。
  • 當i>=11的時候,break語句導致迴圈終止。
  • 注意i += 1這是我們在前面演示的時候,都放到迴圈塊最後部分的迴圈條件變數,當然這裡i已經不是迴圈的條件變量了,但仍然對於退出迴圈起著很關鍵的作用。

這裡放到迴圈一開始,是為了防止continue語句跳過迴圈剩餘部分的時候,把這一句也跳過去,從而導致i的值不再變化,最終導致迴圈無法停止的情況。

根據上面這段示例程式碼,我們出幾道思考題作為今天練習的一部分:

  • 如果沒有break語句,本程式會出現什麼情況?
  • 跟i == 3 or i == 5 對比
    (i == 3) or (i == 5) 功能是否一樣?哪個更好?
  • 本程式中, i >= 11 和
    i == 11功能是否一樣?
    哪個更好?
  • 本例中,如果使用i==11,跟換用for迴圈模式,然後使用range(12)含義一樣嗎?

挑戰

今天的挑戰內容是程式設計來證明《哥德巴赫猜想》,這個話題比較大,所以理所當然我們只是來證明簡化版的《哥德巴赫猜想》。

哥德巴赫是德國一位中學教師,也是一位著名的數學家,生於 1690 年,1725 年當選為俄國彼得堡科學院院士。1742年,哥德巴赫在教學中發現,每個不小於 6 的偶數都是兩個素數(只能被 1 和它本身整除的數)之和。如 6=3+3,12=5+7 等。
哥德巴赫 1742 年給尤拉的信中哥德巴赫提出了以下猜想:任一大於 2 的偶數都可寫成兩個質數之和。但是哥德巴赫自己無法證明它,於是就寫信請教赫赫有名的大數學家尤拉幫忙證明,但是一直到死,尤拉也無法證明。因現今數學界已經不使用“1 也是質數”這個約定,原初猜想的現代陳述為:任一大於 5 的偶數都可寫成兩個質數之和。

編寫程式,輸入任意一個大於5的偶數,證明這個偶數符合哥德巴赫猜想,並顯示是哪兩個質數。

我們前面就講過,如果一個問題太複雜,我們難以實現。那就要對問題進行拆分,使得每個小的部分,都能夠清晰、容易的完成,最後把所有小程式“組裝”在一起。

現在我們就把今天的挑戰內容分拆一下,分解成幾個容易完成的小問題。

奇數、偶數判斷

輸入一個整數,判斷這個數字是奇數還是偶數?我們直接來用程式碼講解:

#輸入一個正整數n,判斷n是奇數還是偶數

#定義一個的函式,
#輸入引數n
#當n為偶數時返回True,否則返回False
def isEven(n):
    return not (n % 2)

#輸入
n=int(input("請輸入一個正整數:"))
#判斷
if isEven(n):
    print(n,"是偶數.")
else:
    print(n,"是奇數.")

我們在程式中定義了一個函式來判斷引數是奇數還是偶數。判斷的原理,是使用整數運算中的求餘數辦法,求引數除以2之後,是否有餘數。如果有餘數,則引數肯定是奇數;如果沒有餘數,剛好除盡了,則引數當然是偶數。

判斷的時候還使用了小技巧,就是not (n % 2)這一句。
有餘數的話,整數值表示為非0,當然這裡因為求除以2的餘數,所以這個值要麼是1,要麼是0,不可能是其它的值。前面我們已經講過了,1代表“真”,True。沒有餘數是0的話,0代表“假”,False。所以這個整數的結果,我們是可以直接當做bool值來使用的。
唯一要處理的,是我們的函式判斷如果是偶數才返回True,所以在取餘數運算的前面增加了not邏輯運算,也就是取反,來得到我們需要的bool值。也既:引數是偶數,返回真值True。

輸入整數之後,使用int()函式把輸入的字串內容轉換為整數數字。因為我們定義的函式返回實際是bool值,所以使用if分支來列印判斷的結果,而不是顯示返回值本身,那樣只能顯示出來“True”或者“False”。

使用者輸入是否滿足條件?

因為我們的程式對使用者的輸入值有約束條件,1、偶數,2、大於5,所以我們要對使用者輸入的數字先進行判斷是否條件吻合,如果不符合約束條件,要請使用者重新輸入。我們以前提過,為了簡化問題,在我們涉及的程式設計概念中,暫不考慮使用者輸入根本不是數字這種錯誤。

#接受一個大於5的偶數輸入
#不符合條件則迴圈重新輸入

#判斷是否為偶數
def isEven(n):
    return not (n % 2)
#判斷輸入數字是否符合條件
def isValid(n):
    if n <= 5 or not isEven(n):
        return False
    return True
#迴圈輸入,直到得到吻合條件的輸入
def inputNumber():
    while True:
        n=int(input("請輸入一個大於5的偶數:"))
        if isValid(n):
            break
        else:
            print("輸入不符合條件,請重新輸入!")
    return n

#呼叫輸入函式
print("輸入為:",inputNumber())

程式上來先是上一節定義的isEven函式,用來判斷輸入是否為偶數。

接著是新定義的函式isValid(n),用來判斷引數是否大於5,並且是偶數。判斷的方法使用or邏輯運算,用以在一個if分支判斷中,同時判斷兩個約束條件。
邏輯運算中的or跟後面的not有點容易混淆。區分的方法也很容易,not運算子是單運算元的,只對其後面的表示式有效,or則是對兩邊的兩個運算元有效。所以or後面一定要有一個運算元,這裡顯然只能是not的結果。而not操作符必須有其後面的唯一運算元。說了這麼多,都是為了解釋給“閱讀”程式的人,所以其實編寫程式的時候,寫成:if (n<=5) or (not isEven(n)):這樣更清楚,你說對嗎?

再下面的inputNumber()函式,重點是使用了while迴圈,並且用True作while的條件,形成一個永遠的迴圈。在迴圈中,只要使用者輸入的數字不符合規定條件,就讓使用者重新輸入。只有當用戶輸入了滿足條件的數字的時候,才會退出迴圈,並由函式返回值返回使用者符合條件的輸入。

質數的判斷

質數是數學上的定義,指的是隻能被1和它本身整除的數字。因為要求整除,所以這個數字本身首先要是整數。

判斷質數很適合使用迴圈,假設我們需要對數字n判斷是否為質數。迴圈從2開始,一直迴圈到這個n-1。用n除以這個迴圈變數後,如果沒有餘數,表示整除了。那當然這個數字就不是質數。如果所有的迴圈結束,也沒有整除的現象,這個數字就是質數。來看程式程式碼:

#接受一個正整數輸入,判斷該數字是否為質數

def isPrime(n):
    #從2開始迴圈到n-1
    for i in range(2,n):
        #如果有可以被整除的(無餘數),
        #則數字不是質數
        if (n % i == 0):
            return False
    return True

#輸入
n=int(input("請輸入一個正整數:"))
#判斷是否為質數並顯示
if isPrime(n):
    print(n,"是質數")
else:
    print(n,"不是質數")

好了,至此我們所有用到的小功能都已經實現了,後續需要把所有程式碼拼裝到一起,成為一個完整的程式。拼裝工作我們當做今天的練習請你自己完成,一定要完成之後再看答案。

拼裝提示:在剛才的幾個小程式中,因為每個小程式都是一個完整的程式,都有輸入、顯示等功能,核心的功能當然已經完成了函式化。所以拼裝重要的工作是拼裝這些函式。主要的程式流程,則需要根據前面《哥德巴赫猜想》的題面來自己編寫。這個主流程的大致工作應當是:

  • 輸入數字,判斷數字是否合規,否則重新輸入
  • 假設輸入的數字是n,我們用i變數迴圈從3到n-1
  • 如果存在i和n-i兩個數字都是質數的情況,則猜想成立
  • 猜想成立把i和n-i都顯示出來就好了

我相信你一定能完成的,加油吧。


練習時間

  1. 迴圈中的分支一節中的思考題。

  2. 迴圈顯示數字1-11,其中數字3、5跳過不顯示,要求使用for迴圈實現。(我們前面已經有了while迴圈的例子,可以參考完成)

  3. 完成上一節中的《哥德巴赫猜想》完整程式。這裡有一個提示,在除錯程式的時候,不要輸入太大的數字,否則計算機可能需要執行上幾天甚至更多,這讓你完全無法驗證程式和找出程式中的問題。


本講小結

  • 本講重點講述了條件分支,但實際上邏輯運算及其各種應用是重點。因為分支的條件,是使用邏輯運算表達的。
  • 有邏輯處理能力是計算機區別於其它計算裝置(比如傳統計算器)的重要特徵。
  • 多項條件通過邏輯運算組合在一起,可以讓程式碼更簡潔。並且能完成很多複雜的工作。這個工作的難度,在於你如果想讓計算機執行的正確,你自己必須使用自己的大腦完全的模擬正確。

練習參考答案

  • 程式請參考原始碼:code2a.py 及 goldbach.py。

  • 如果沒有break語句,本程式會出現什麼情況?

    沒有break語句,本程式會陷入死迴圈,無法停止。

  • i == 3 or i == 5 對比(i == 3) or (i == 5) 功能是否一樣?哪個更好?

    功能都一樣,但後者更好,因為更直觀更容易理解。
    延伸一個解釋。加上小括號之後,比不加,程式碼速度回略微受一點影響。但這個影響非常小,可以忽略不計,所以看上去更清晰就成了優選。

  • 本程式中, i >= 11 和i == 11功能是否一樣?哪個更好?

    功能是一樣的,但i>=11容錯性更好。

  • 本例中,如果換用i==11,跟for迴圈中使用range(12)含義一樣嗎?

    功能可能一樣,但含義完全不一樣。i==11是本例中的結束條件,是相等的判斷。而range(12)表示生成的列表<12,使用了小於判斷。