1. 程式人生 > 實用技巧 >計算複雜性讀書筆記(三): 同構,自指,停機問題

計算複雜性讀書筆記(三): 同構,自指,停機問題

2019獨角獸企業重金招聘Python工程師標準>>> hot3.png

很久以前,舉國興盛各種仙術邪道,每個派別的高德大師會給弟子們傳授獨門心法,通俗地來講就是“怎麼快速地獲得幸福”,每次傳授完了之後,他們會例行地來一句:“千萬別跟別人說是我說的,不然就不靈了!”嗯,這種才叫無私奉獻嘛,而現在,大家拼命維護自己的各種版權,真是世風日下,嘖嘖

3.1. 如是我聞

“是聞”的意思是“聽說是醬紫的”,“如是我聞”的意思就是“就像我聽說的那樣”。大部分佛經都是以“如是我聞”開頭,意思是“就像我聽佛陀說的那樣”。碩士期間,浪費了一大部分時間整天沉浸在“如是我聞”的句子裡。話說回來,“如是我聞”是鳩摩羅什第一個翻譯的,人不但佛學得好,而且還是“譯神”(翻譯之神)。嚴復提出了後世膜拜不休的翻譯準則:信、達、雅。而他也正是從鳩摩羅什的譯經中學來的。所以讀佛經,就好像是在讀一篇遣詞造句超然天工的美文,更重要的是裡頭的禪意甚深。以至於,後來很多溼人在滿級之後,都開始寫禪偈子,然後很風騷地給自己加一個“居士”的稱號。大家可以翻翻中小學課本,看看有多少溼人都有一個“什麼什麼居士”的頭銜。對,沒錯!他們不是真的信佛,他們是在裝。

其實很多佛經都是後世人按自己的體悟,大開腦洞後寫的,然後在最前面加一句“如是我聞”——“我是聽佛陀說的”。這就叫,裝完之後再謙虛一下。

所以,我要表達的是,我接下來講的這些真的不是在裝,真的是聽別人說,或者看到別人這樣寫的,而我又覺得可以跟大家分享分享。

3.2. 再談同構

很多人開腦洞,談佛學和量子力學的共通性,大家感興趣可以網上搜搜。很多講的很好的人都是對佛學不瞭解,對量子力學也不瞭解的那些人。講得好就是要把明白的人講暈,把不明白的人講的更暈,然後,they did it

其中一個吹得還有那麼點意思的是曹天元的《上帝擲骰子嗎》,之前M同學推薦給我,我也找過來拜了一拜。裡面很風趣地討論了唯物派對人類意識的一些觀點,其中就包括同構

。說人類特有的意識僅僅是取決於某種“組合模式”,而不限定於特定的物質。然後M同學舉了個很有意思的例子:把大蘿蔔們按照人類大腦的結構進行組合,那麼這些蘿蔔就可以開始思考:“我為什麼是蘿蔔?”這個問題了。我覺得這個例子很有意思,就放在了這裡。下面再展開談談2.5提到的“同構”。

這些在談“歸約”之前談,我覺得或多或少對大家有點幫助。同構說的是,兩個不同的事物之間存在著高度的相似性,在談計算複雜性的背景下,就是兩個計算問題之間存在著高度的相似性。然後利用這種相似性,我們將一個問題A“規約”到另一個完全不一樣的問題B上去,那麼解決A就可以通過解決B來完成。

也許現在你還沒有什麼感覺,但是再仔細感受感受,兩個風馬牛不相及的問題竟然是相似的,這本身不是一件很有意思的事情嗎?就像《

GEB》的作者開腦洞後發現,邏輯、繪畫、音樂之間竟然也存在驚人的相似性。你難道不覺得“大自然暗示我們兩個完全不一樣的東西背後竟然是如此地一致”這件事兒是故意安排的嗎?

從計算機角度看來,這個稍微容易理解一點。兩個同構的問題,比如SATCircuit(就不在這裡介紹什麼是SATCircuit問題,不影響閱讀),它們最後都被編碼成了01串,說白了解決這兩個問題就是在對這些01串進行操作。對SAT01串的一個操作,我們用一系列對Circuit01串的操作來模擬,反之亦然,由此產生同構。

在計算機裡,雖然兩個問題可以互操作(比如上面講到的SATCircuit),但是它們還是兩個不同的問題,只是背後存在著相似性。互操作,只是因為它們在計算機中都被編碼成了01串,然後我們都是對01串進行操作。我再舉個例子,W老師上課給出了一個語言,名叫L語言,它只能進行下面三種操作:

X <- X+1

X <- X-1

IF X GOTO [A]

如上面表中所示,L語言只能進行累加、遞減和條件轉移操作,但是L語言已經被證明是圖靈完備的,也就是它可以模擬任何複雜的語言。如果有一臺機器是根據L語言造出來的,那麼用這臺機器來解決問題時,所有的問題到最後都是這三條指令的操作序列。而同構的兩個問題AB之間,就可以通過限定指令的操作方式來相互模擬了:即對A的一個操作,可以用對B的一系列操作來實現,反之同理。

回到現實世界,完全不一樣的東西背後存在驚人的相似性。那麼這種相似性本身是怎麼來的呢?它們可以通過互操作相互模擬嗎?很遺憾,現在還沒有人有任何實質性的實驗結論。不過小學時語文老師可能會教你一個邪招:通感。比如在文章裡插一句“這個美麗動聽的聲音聽上去真的是五顏六色啊”,然後作文肯定可以很順利地拿到不及格。

3.3. 把一個判定問題看成一種“語言”

上面提到的兩個同構問題AB可以相互模擬,你如果覺得不能理解,我用另外一種表達方法:A可以用B來描述。然後你會說,“不好意思,我更不懂了。”

很多教科書抄別人的說法,在談歸約的章節裡,把一個判定問題直接用一種語言(比如:L)來表達,也根本不講清楚來龍去脈。OK,如果你已經把這本教科書放到了廁紙筒裡,那請你今天就把這幾章用掉。

首先放三個問題:1)為什麼要把一個判定問題看成是一種語言?2)什麼是“語言”?3)怎麼把一個判定問題看成一種語言?

為什麼天朝的大部分教材很bull shit呢(注意限定詞是大部分,不是全部)?因為它們從來都不會去講“為什麼”;而為什麼洋人的教材個個都長得都跟腦白金超值禮品套裝盒一樣,因為他們苦口婆心反反覆覆就跟你在叨咕這個“為什麼”。我們的教育為什麼bull shit?因為我們從來都不會引導孩子去思考“為什麼”,導致我們長大後有一種慣性思維便是:“是這樣就成,咋來的無所謂”。我家樓上原來住著一個做鋼筋混凝土發了財的壕叔,有錢後娶了一個棒子媳婦兒。我老孃管事兒,問他:“幹嘛娶個韓國媳婦兒?”他想了想說:“就是那個漂亮啊!”直到娃出來之後,他才發現:呀,媳婦兒是經過後期處理的啊!

回到正題,我們先來回答第一個問題:為什麼要把一個判定問題看成是一種語言?對於這類的問題,一般只有一個原因就是:省事兒!就跟人們談P=?NP問題時只關注判定問題一樣。從語言的角度來談同構,或者後面要講的歸約,事兒就方便了。但是,把問題看成一種語言也好,關係也好,函式也罷,其實都一樣。從多個角度來描述或者解釋一個物件,往往會有驚訝的發現(這些發現往往也都是你感受到了某種“同構”的存在)。也有人不喜歡這麼做,比如有一個處女座的科學家,喜歡用函式來描述問題,於是談可計算性和計算複雜性的時候從頭到尾全是用的函式。雖然這是一本經典,不過摟了一眼後你還是會感嘆,處女座真可怕!

第二個問題:什麼是語言?這個問題太大,就算是搞語言學的人都無法給個完全的定義。我們既然是研究計算機的問題,那更多地是從數學的角度出發。對於這個問題,我們只要找到一種可以對語言建模的數學模型就可以了。一個最簡單的模型就是集合。我們假設用集合L來代表一種語言,並且規定L中的元素是在某種約束下的所有合法的句子,形式化如下:

定義7L是一種語言,且L={XΣ*|P(X)},其中,Σ是字元表,接收函式P(X)是一個布林函式,用於判定X是否可以被該語言接收。

在上面這個定義中,語言是一個集合,並且是合法的句子(更確切地,應該是字串,不引起歧義的情況下我們在這裡以及後文就用“句子”來表達合法的字串)的集合,你不用去糾結集合裡的是詞素,句子,短語還是篇章,只要這個字串XP(.)這個布林函式判定為true(“接收”)就成。P(.)對於一個語言系統來講起到了約束,限定的作用,或者更確切地說是語法或者文法。如果把一個語言的諸多約束或者語法看成是一個個函式,那麼P(.)可以看成是這些函式的複合。舉個栗子:

語言1L1 ={X{0,1}*|isPalindrome(X)}.

語言L1的接收函式是isPalindrome,用於判定輸入的串是否是迴文,而L1中的所有句子都限定在01串的範圍內,所以L1語言實際上是所有的01迴文串組成的集合。

但實際生活中的語言(或者稱之為自然語言),它們的語法系統肯定是在不斷地更新中,所以語言所對應的集合也一定在不斷地擴充或者縮減。更普遍地,兩種語言系統也會融合。比如,上次問倆哥們兒,週末準備去哪兒玩兒,他們說準備去XX家裡玩“轟趴”。我說,納尼,你們原來是這個傾向?!後來才瞭解到是我自己奧特了,“轟趴”就是家庭聚會的意思。但是假如他們說,我們準備去XX家裡進行一次家庭聚會。嗯,這就感覺,到時去XX家時會全體起立,奏國歌一樣。語言的改變更多地發生在年輕人的群體當中,靈活,親切,而所有的目的就是為了:方便。

有了上述對語言的簡單定義,那麼第三個問題就水到渠成。因為判定問題在第一篇筆記當中也已經用集合來定義,所以ofcourse可以把一個判定問題看成是一種語言。很多教科書直接很酷炫地拉出來,“我們現在把一個判定問題編碼成一種語言”。實際上真正要到這一步,我們要經過上面這麼多理解之後才行。我們把一個判定問題R看成一個語言LR,那麼首先要注意的是,這個LR其實是判定問題R的所有被特徵函式判定為“true”的輸入的集合。此外,要把RLR對應上,最重要的是要把R的特徵函式看成是LR的接收函式。另外,由於判定問題的特徵函式是固定的,問題域也是相對不變的,所以我們這裡所研究的這些個語言基本上都是靜態的。

我們以問題4判斷x是不是質數?來說明。這個判定問題形式化為R={xN|Prime(x)}。它本身也是一種語言L2

語言2L2 ={XN|Prime(X)}.

它的合法的句子就是所有質數(當然可以編碼成01串的形式,或者直接以阿拉伯數字的形式出現),而它的接收函式也就是Prime(x)。是不是so easy?

現在,我們回到這一節的開頭,如何用一個問題A來描述另一個問題B?前面我們說過,任何問題都對應一個判定問題。所以我們關注更具體的一個問題:如何用一個判定問題A來描述另一個判定問題B那到此為止,我們知道AB都對應一個語言,假設為LALB,所以我們要考察如何用語言LA來描述LB

什麼叫用一種語言來描述另一種語言?很簡單,對應到我們的自然語言,就是“翻譯”。當然,我們不需要做到鳩摩羅什的信、達、雅,對於“Oh,My GOD!”,你of course可以翻譯成“我勒個去!”用LA來描述LB,就是把LB翻譯成LA,所以最後翻譯出來的那個東西LC實際上是LA的一個子集,但是同時能描述LB的性質。換句話說,LCLB是同構的,但,是LA的子集。

上栗子,我們用語言1試著來描述語言2

怎麼在語言1中找一個子集,而這個子集又能描述語言2的性質呢?首先,語言2中都是自然數,而語言1中只是01串,所以不能直接將語言2作為語言1的子集。

那麼把語言2的元素都用二進位制形式表示?當然也不可以,對於最小的質數2,其二進位制形式是01,卻不是一個迴文。

也許你會問,語言1只是01迴文串的集合,它的表達能力可能根本就不足以描述出語言2啊?

現在就需要你開腦洞了。想一想,這裡判定問題所對應的語言不像我們日常生活中的語言系統那麼複雜,它們中的句子往往僅具有一種非常簡單的“共有”性質。對於語言2,所有的句子都是質數,而Prime(.)就是它們唯一的共有性質,也就是說只要這個語言能表達出(或者界定出)質數就可以了。而現在,我們只要從語言1裡找一個子集,這個子集的元素在某一方面也具有“質數”這個共有性質。我於是找到下面這個語言:

語言3L3 ={X0*|Prime(length(X))isPalindrome(X)}.

L3中句子的字元都是“0”,所以肯定符合迴文形式,而我將L3進一步限定在串長是質數的範圍內。因此,L3即是L1的一個子集,而其元素的長度又能表達出質數這個性質。所以,L3可以作為語言1對語言2的描述。

你也許要反駁了,我的做法是犯規的,突然加上“串的長度”是不是作弊?這並沒有作弊,因為L3仍舊是L1語言(或者說子集)。我所做的是在L1語言的語法層多加了一種屬性“串的長度”來進一步限制了一下,但本身並沒有逾越L1語言的範疇。如果,外星人來訪問,而它們只用一進位制,那我們也只能用L3來描述質數了不是嘛。

什麼叫逾越L1語言的範疇?按照定義7,一種語言實際上分成兩個層次,一個就是語言層:包含所有的合法句子;另一個是語法層:是來限定或者描述這個語言的特性、範圍。從數學系統的角度來講,語法層更像是一種元語言,這種元語言定義出了更具體的語言。具體來說,如isPalindrome(.)這個接受函式本身也是一種語言(布林函式),不妨稱之為MetaL1,用它來定義出了更具體的語言L1。如果,L1越庖代俎要試圖去描述MetaL1OK,這時就已經逾越了L1本身的範疇了。更通俗地講,上帝創造了我們人類,而我們人類又反過來質問:上帝本身是不是我們創造的?這事兒嚴重了,上帝要生氣了。我們到下一節來簡單談談這個問題:自指

3.4.自指

從哲學的角度來看,讓人類產生煩惱的根本原因是人類意識到了“自己究竟是什麼玩意兒?”這個很嚴重的問題。不過我們這個筆記不會試圖去回答這個問題。只是這個現象本身和我們接下來要聊的東西很像:自指。

自指沒有一個明確的定義,因為這個現象在哲學,語言,數學,邏輯學甚至經濟學(阿羅悖論)裡都存在。我們接著3.3的茬,對於語言L1,它的語法(或者接收函式)本身需要一種語言MetaL1來描述和表達(不妨稱之為描述語法的元語言),如果L1中的句子試圖去描述MetaL1的話,那麼就產生了自指。我們說這些句子越庖代俎了,做了老子該做的事兒。不做自己本分的事兒,並且還做不好那就要出問題。我們在筆記二里舉了個例子,這裡簡化一下來說明我們日常生活的語言中出現的自指現象:

問題6:下面這句話是錯的嗎?

我這句話是錯的。

假設我們世界裡的話們(句子們)只有對錯之分,也就是所有的話有一個“對”或者“錯”的屬性。那麼哪一句話是對,哪一句話是錯,這個就是語言的規則系統或者語法系統規定的事兒。現在,“我這句話是錯的”本身是這個世界裡的一句話,但是它試圖代替語法系統來定義自己的對錯。如果語法規定“我這句話是錯的”是錯的話,那麼根據這句話本身的描述又應該是對的;如果是對的,那麼“我這句話是錯的”就沒有錯,那麼應該是錯的。現在,就粗事兒了,到底是對是錯?這個例子是對說謊者悖論的另一種表達。

我們之前定義語言的時候,把語言看成一個集合。如果僅僅從集合這個數學系統來解釋自指,就是說集合中的元素試圖去描述這個集合本身。假設有一個集合S={X|XX},S是這樣一類元素的集合,這些元素不屬於它們自身,那麼S是不是屬於S自己呢(S∊?S?如果S屬於自己,那麼它就不符合“XX”這個特徵函式;如果不屬於自己,那它應該在自己中,又和“不屬於自己”矛盾了。羅素髮現的這個悖論用很簡單的方式說明了數學系統並不完善。後來大家夥兒開腦洞,開出了一個更通俗的例子:理髮師悖論。構建一個完美的數學系統的夢最終被哥德爾打破,我們這裡掛上艾舍爾的一副經典作品《繪畫的手》以示對各種大師們的敬仰:

170903_PUiZ_1757911.jpg

解決由自指導致的問題,很簡單的方法就是加上“禁止你做老子該做的事兒”之類的限制。比如問題6,既然“對”和“錯”是語法層賦予句子的屬性,那麼具體的每一個句子中就不應該再出現對自身或者其它句子的“對”或者“錯”的描述。在數學系統當中也可以引入類似的限制,比如型別論所做的事情(它也號稱解決了第二次數學危機)。不過這些做法到頭來像是僅獲得了一些心理上的安慰,而並不能掩蓋一個事實:我們這個世界並不那麼完美。

3.5停機問題

由自指引發的矛盾,通常可以用來證明。在可計算性理論中,最基本的不可計算問題——停機問題,它的不可計算性就可以通過自指來證明。下面我們就以停機問題為例來看看自指怎麼用來證明。

在給出停機問題的定義和其不可計算性的證明前,我們先來介紹一個編碼函式:哥德爾編碼:

Gödel([X1,X2,…Xi,…,Xn]) =

171007_OGhk_1757911.jpg

給定一個有序的自然數列[X1,X2,…,Xn],其中X1Xnn個任意的自然數。哥德爾編碼函式Gödel()就是以任意的自然數列作為輸入,然後是一個累積連乘式,每一項是一個指數形式,以第i項為例:圖片,冪就是輸入的數列中第i個數Xi,底數是第i個質數Pi(比如第三個質數是P3= 5)。Gödel編碼可以將不同的數列對應到唯一的一個自然數。這個從另一方面也說明了自然數是無窮的。

有了哥德爾編碼,我們就可以將一個程式P編碼成一個唯一的自然數#P。假設程式是由一條條指令組成的,在32位機也好64位機也好,每一條指令假設都對應了一條二進位制編碼。如果我們將每一條指令的二進位制編碼轉譯成一個自然數,那麼每一條指令都對應唯一的一個自然數(不同的指令之間一定存在某一位不一樣)。而每一個自然數也可以通過求取其二進位制形式翻譯出這條指令。通過這種方式,程式P就可以看成是一個自然數列,第i項就對應了其第i條指令,那麼也就存在唯一的一個自然數與P對應,即

#P =Gödel(P)

從某個自然數解碼出這段程式也就很顯然了:

P =de-Gödel(#P)

這裡de-Gödel()Gödel的反函式。對於某一臺的機器,每一個自然數都會對應唯一的一段程式。不同的機器之間可能某一個自然數對應的程式不一樣,這取決於機器的指令系統。

在哥德爾編碼的基礎之上,我們就可以定義出停機問題:

定義7(停機問題):給定一個程式P,和謂詞Halt(#P, X)。在P輸入為X的情況下,如果P停機,Halt(#P,X) = true,否則Halt(#P, X) = false

這裡假設程式P的輸入只有一個自然數X。當然,程式可能有多個輸入引數,如果按照這些引數的輸入順序求取Gödel編碼,那麼也可以用唯一對應的一個自然數來作為輸入。

接下來我們證明停機問題不可計算。首先,什麼叫可計算,什麼叫不可計算?在可計算性理論中,所謂一個問題可計算,就是將它交給計算機來處理時,存在一個程式能夠把這個問題解出來。所以就算一個問題本身是有解的,或者有答案的,計算機也不一定能夠解出來。就像停機問題,從定義來看,對於一段程式肯定要麼停機要麼不停機,所以Halt()函式肯定有解(或者肯定是全函式),但是定理3表明計算機本身解決不了這個問題。

定理3:停機問題不可計算。

證明,

利用反證法,假設停機問題可計算,然後推出矛盾。對於某一段程式P,我令它的輸入是它自身的哥德爾編碼,即X=#P,那麼按照假設我們就有Halt(X,X)是可計算的(也就是程式一定會返回Halt(X,X)true還是false)。現在我們構造另一個程式P’:

while(Halt(X,X));

P’輸入仍舊是X,只是它呼叫了P來做while迴圈的條件判斷,如果Halt(X,X)true,那麼就永遠不會跳出while迴圈。令Y=#P’,那麼Halt(Y,X)=false。也就是

Halt(Y,X)=~Halt(X,X)

因為P並沒有指明是什麼具體的程式,所以我令P這個程式就是P’。所以也有下面的結論:

Halt(Y,Y)=~Halt(Y,Y)

這顯然是個矛盾。證畢。

這個證明簡單點說,就是一個不停機的程式試圖判斷自己停不停機。證明巧妙地利用了自指。後來很多不可計算問題都是基於停機問題不可解來證明的。大一的小盆友們剛上C語言課時,老師會介紹演算法和程式的區別,而這區別也就是演算法要求停機。以前有個學金融的友人準備考計算機二級證,問我為什麼演算法要求停機,我一下子覺得自己過去的人生受到了質疑,因為我從來沒想過這個問題!現在終於想明白了,因為演算法就是要去解決問題的嘛。

當然,自指還可以用於證明其他問題,比如數理邏輯中,經常定義出一種叫canonical解釋的模型來證明完備性啥的。對於自指的這些具體應用,這個筆記裡就不細說了。

3.3.接下來

先回顧一下到目前為止我們做了些什麼,後面又要去做些什麼,然後再繼續上路。

我們要研究計算問題的複雜性,這裡複雜性要明確的是交給機器來計算、或者用一個程式來執行有多少複雜,而不是人為的。人解決問題是會受環境因素影響的,比如我為什麼考上了現在的大學,而沒有考取清華?因為高考時坐在後面的一個胖子竟然在哼我最喜歡的Linkin Park?!但是問題交給機器來解決,除非把計算機放在煉丹爐裡,否則就一定會有一個自然存在的複雜程度。

那麼,接下來,要解決的計算問題是什麼呢?我們在第一篇筆記和第二篇筆記的開頭將計算問題限定在了一個範圍內,我們僅僅在那個範圍內進行考察。這裡面的計算問題是“實實在在”的計算問題,給定一個輸入,計算得到輸出。所以,不是證明(the procedure of proof),即,推匯出一個已經給定的結論。這些計算問題就像是W老師用來碾壓我們的他的四年級女兒的奧數習題。因此,研究計算問題,一定要清楚,我們其實是回到了我們的出發點,去看看那些我們從小就在解決的計算問題到底可以在多好的效能內被自動化地解決。

界定了計算問題之後,我們在之上定義了四類計算問題PCPFPNP。這四類是按照時間複雜性來分的類別。按照計算形式分類,這四類計算問題又屬於兩個大類:搜尋問題和判定問題。但發展到現在,計算複雜類家族中有上百種類別,之所以從多項式時間出發以及給出定義,是因為我們日常生活中大部分問題都是多項式時間內可解的,或者多項式時間內可驗證解答的。解決多項式時間的問題確實對我們在很多方面是有幫助的,所以一切從多項式時間開始。

多項式時間內可以解決的問題(P)是已經找到了一個多項式時間的演算法,或者證明出來存在這樣的演算法。然而,多項式時間內可以驗證解答的問題(NP)也是非常普遍的一類問題。但問題是,人們到現在還沒能夠找出或者證明NP類問題是否存在一個多項式時間的演算法。即使是“不存在”也難以證明。

我們現在找不到NP問題的多項式時間演算法,那麼我們先來看看有哪些問題是NP問題,它們之間是否存在關聯。如果存在關聯,那麼當我們找到一個NP問題的多項式時間演算法時,是不是也能找到其他問題的多項式演算法。於是,我們想到了同構,如果一些問題可以同構或者歸約到一個問題,那麼解決這個問題就可以解決所有的問題。關於同構,我們講了很多廢話,也竟然延伸到了自指,以至於你也許早就放棄讀我的筆記了。

不管怎樣,下面迴歸正途,在接下來的筆記裡談談多項式歸約。

轉載於:https://my.oschina.net/airship/blog/378824