寫指數函數,只是無數解決方案的一種,還有其它方案。
用不一樣順序寫不一樣語句,也能獲得同樣的結果,不一樣的是「算法」,意思是:解決問題的具體步驟。
即便結果一致,有些算法會更好。
通常來講,所需步驟越少越好。不過有時候也會關心其餘因素,好比佔多少內存。node
「算法」一詞來自 阿爾●花拉子密,1000多年前的代數之父之一。
如何想出高效算法,是早在計算機出現以前就有的問題,誕生了專門研究計算的領域,而後發展成一門現代學科,計算機科學。程序員
選擇排序
記載最多的算法之一是「排序」。好比給名字,數字排序。
排序處處都是,找最便宜的機票,按最新的時間排郵件,按姓氏排聯繫人等等,這些都要排序。
計算機科學家花了數十年發明了各類排序算法,還起了酷酷的名字,「冒泡排序」, 「意麪排序」,「選擇排序」算法
好比:
一堆機票價格,都飛往目的地。把價格一組數據存放到一個數組結構。
先找到最小數,從最上面開始,而後和第一個比較,因此看最小的數是否變化,移動位置。重複循環比較,持續移動位置。
意味着,若是要排N個東西,要循環N次,每次循環中再循環N次,共N*N。編程
算法的輸入規模和運行步數之間的關係,叫算法的複雜度。
表示運行速度的量級。
計算機科學家們把算法複雜度叫:大O表示法(big O notation)。數組
選擇排序的算法複雜度O(n^2)效率不高。數據結構
歸併排序
第一件事是檢查數組大小是否>1,若是是,就把數組分紅兩半。再檢查數組大小是否>1,若是是,繼續分組,若是不是開始「歸併」。
從前兩個數組開始,讀第一個(也是惟一一個)值,而後開始比較,若是更小,排在以前。重複這個過程,按序排列。而後數組大小增長,再次歸併。
一樣,取前二個數組,比較第一個數,而後再比較第一個數組的第一個數,第二個數組的第二個數。而後合併成更大有序的數組。
直到排序完整。編程語言
歸併排序的算法複雜度O(n * logn),n是須要比較+合併的次數和數組大小成正比,logn
是合併步驟的次數。函數
圖搜索
「圖」是用線鏈接起來的一堆「節點」。能夠想成地圖,每一個節點是一個城市,線是公路。
一個城市到另一個城市,花的時間不一樣,能夠用成本(cost)或權重(weight)來代稱,表明要幾個星期。
假設想找「高庭」到「凜冬城」的最快路線。
最簡單的方法是嘗試每一條路,計算總成本。這是「蠻力辦法」。測試
假設用蠻力方法,來排序數組,嘗試每一種組合,看是否排好序。
這樣的時間複雜度是: O(n!)
,n
是節點數,n!
是階乘。比O(n^2)
還糟糕。人工智能
從「高庭」開始,此時成本爲0,把0標記在節點裏,其它城市標記成問號,由於不知道成本多少。Dijkstra 算法
老是從成本最低的節點開始,目前只知道一個節點「高庭」,因此從這裏開始,跑到全部相鄰節點,記錄成本,完成一輪算法。
可是還未到「凜冬城」,因此再跑一次Dijkstra 算法
。
下一個成本最低的節點,是「君臨城」,記錄相鄰節點的成本,到「三叉戟河」,然而想記錄的是,從「高庭」到這裏的成本,因此「三叉戟河」的總成本8+5=13。如今走另一條路到「奔流城」,成本高達25,總成本33,但「奔流城」中最低成本是10,因此無視新數字,保留以前的成本10。如今看了「君臨城」的每一條路還沒到「凜冬城」因此繼續。
下一個成本最低的節點,是「奔流城」,要10周。先看到「三叉戟河」成本,10+2=12,比以前的13好一點,因此更新「三叉戟河」爲12,「奔流城」到「派克城」成本是3。10+3=13,以前是14,因此更新「派克城」爲13。
「奔流城」出發的全部路徑都走了遍,還沒到終點,因此繼續。
下個成本最低的節點,是「三叉戟河」。從「三叉戟河」出發,惟一沒看過的路,通往「凜冬城」。成本是10加上「三叉戟河」的成本12,總成本是22。再看最後一條路,「派克城」到「凜冬城」,總成本是31。
因此最佳線路是:「高庭」 -> 「奔流城」 -> 「三叉戟河」 -> 「凜冬城」。
Dijkstra
的語錄:
「有效的程序員不該該浪費不少時間用於程序調試,他們應該一開始就不要把故障引入。」
「程序測試是代表存在故障的很是有效的方法,但對於證實沒有故障,調試是很無能爲力的。」
Dijkstra 算法
算法複雜度是:O(n^2)
通過改造的Dijkstra 算法
複雜度減低爲:O(n * logn + l) n是節點數,l是多少條線
算法處理的數據,存在內存裏的格式是什麼。
但願數據是結構化,方便讀取,所以計算機科學家發明了「數據結構」。
數組
數組Array,也叫列表(List),或向量(Vector)。有一些區別,在不一樣語言中基本相似。
數組的值一個個連續存在內存裏。能夠把多個值存在數據變量裏,爲了拿出數組中的某個值,要指定一個下標(index)
大多數編程語言中,數組下標都從0開始。
用方括號[]表明訪問數組。
j = [5, 3, 7, 21, 82, 4, 19] a = j[0] + j[5]
數組存在內存裏的方式,十分易懂。
爲了簡單,假設編譯器從內存地址1000開始存數組,數組有7個數字,像上圖同樣按順序存。
寫j[0]
,會去內存地址1000,加0個偏移,獲得地址1000,拿值: 5。
寫j[5]
,會去內存地址1000,加5個偏移,獲得地址1005,拿值: 4。
數組的用途普遍,因此幾乎全部的編程語言,都自帶了不少函數來處理數組。
字符串
數組的親戚是字符串String,其實就是字母,數字,標點符號等,組成的數組。
計算機怎麼存儲字符?
經過字符對應的ASCII表
寫代碼時,用引號括起來就好了: j = "STAN ROCKS"
雖然長的不像數組,但的確是數組。
注意:字符串在內存裏以0結尾,不是「字符0」,是二進制「0」,這叫字符「null」,表示字符串結尾。
這個字符很是重要,若是調用print()
函數,會從開始位置,逐個顯示到屏幕,可是得知道何時中止下來。不然會把內存裏全部東西都顯示出來。0告知函數什麼時候停下。
由於計算機常常處理字符串,因此有不少函數專門處理字符串。
矩陣
能夠用數組作一維列表,但有時想操做二維數據,好比電子表格,或屏幕上的像素。那麼須要 矩陣Matrix
。
能夠把矩陣當作數組的數組,內存中的表示:
j = [[10, 15, 12], [8, 7, 42], [18, 12, 7]]
爲了拿一個值,須要兩個下標,好比j[2][1]
,告知計算機在數組2裏,位置是1的元素。
結構體
把幾個有關係的變量存在一塊兒,會頗有用。多個變量打包在一塊兒叫 結構體(Struct)
多個不一樣類型數據,能夠放在一塊兒。
這些數據在內存裏,會自動打包在一塊兒,若是寫j[0]
,能拿到j[0]
裏的結構體,而後拿具體數據。
存結構體的數組,和其它數組同樣,建立時就有固定大小,不能動態增長大小。還有,數組在內存中,按順序存儲。在中間插入一個值很困難。但結構體能夠創造更復雜的數據結構,消除這些限制。
節點和鏈表
一個結構體,叫節點(Node)。它存一個變量,一個指針(pointer)
「指針」是一種特殊的變量,指向一個內存地址,所以得名。
用節點能夠作鏈表(linked list),鏈表是一種靈活數據結構,能存不少個節點(node), 靈活性是經過每一個節點指向,下一個節點實現的。
假設有三個節點,在內存地址1000,1002,1008。
內存地址隔開,多是建立時間不一樣,它們之間有其它數據,能夠看到第一個節點,值是7,指向地址1008,表明下一個節點,位於內存地址1008,來到下一個節點,值是112,指向地址1002,往下一個節點,地址1002的值是14的節點,這個節點,指回地址1000,也就是第一個節點,這個叫循環鏈表。
但鏈表也能夠是非循環的,最後一個指針是0,null
,表明鏈表的盡頭。
當程序員用鏈表時,不多看指針具體指向哪裏,而是用鏈表的抽象模型。
數組大小須要預先定好,鏈表大小能夠動態增減,能夠建立一個新節點,經過改變指針值,把新節點插入鏈表中。
鏈表也很容易從新排序,兩端縮減,分割,倒序等。
由於靈活,不少複雜的數據結構都用鏈表。
最出名的是 隊列(queue)和棧(stack)。
隊列和棧
「隊列」就像郵局排隊,誰先來就排在前面。先進先出(FIFO)。
有個指針,指向鏈表的第一個節點,第一個節點是Hank
,服務完Hank
以後,讀取Hank
的指針,把指針指向下一我的,這樣就把Hank
「出隊」(dequeue)了。
若是想加到隊列裏,「入隊」(enqueue)。
要遍歷整個鏈表到結尾,而後把結尾的指針,指向新人(Nick)。
只要稍做修改,就能用鏈表作棧,棧是後進先出(LIFO)。能夠把「棧」想成一堆鬆餅,作好一個新鬆餅,就堆在以前的上面,吃的時候,是從最上面開始吃的。棧出入叫「入棧(push)」,「出棧(pop)」。
樹
若是節點改一下,改爲2個指針,就能作成樹(Tree)
不少算法用了樹,這種數據結構,一樣程序員不多看指針的具體值,而是把樹抽象成:
其中有一個特例,節點就2個,叫「二叉樹(Binary Tree)」。
節點能夠用鏈表存全部的子節點。
「樹」的一個重要性質是(無論是現實仍是數據結構中):「根」到「葉」都是單向的。
若是數據隨意鏈接,包括循環,能夠用「圖」表示。這種結構,能夠用有多個指針的節點表示,所以沒有根,葉,子節點,父節點這些概念。能夠隨意指向節點。
不一樣數據結構使用於不一樣場景,選擇正確數據結構會讓工做簡單。
計算機科學之父,阿蘭●圖靈。
於1912年出生倫敦,從小就表現出驚人的數學和科學能力。
對計算機科學的建樹始於1935年,他開始解決德國數學家提出的問題:可斷定性問題,是否存在一種算法,輸入正式的邏輯語句,輸出準確的「是」或「否」答案?
美國數學家阿隆佐●丘奇於1935年首先提出解決方法,開發了一個叫「Lambda 算子」的數學表達系統,證實了這樣算法不存在。
雖然「Lambda 算子」能表示任何計算,但它使用的數學技巧難以理解和使用。
圖靈想出了本身辦法來解決「可斷定性問題」,提出了一種假想的計算機,如今叫「圖靈機」。
圖靈機提供了簡單又強大的數學計算模型,雖然用的數學不同,但圖靈機的計算能力和Lambda 算子同樣。同時由於圖靈機更簡單,因此在新興的計算機領域更受歡迎。
圖靈機是一臺理論計算設備,有無限長的紙帶,紙帶能夠存儲符號,能夠讀取和寫入紙帶上的符號,還有一個狀態變量,保存當前狀態,還有一組規則,描述機器作什麼。規則是根據:當前狀態+讀寫頭看到的符號,決定機器作什麼。結果多是在紙帶寫入一個符號,或改變狀態,或把讀寫頭移動一格,或執行這些動做的組合。
簡單例子:
讓圖靈機讀一個以0結尾的字符串,並計算1個出現次數,是否是偶數。若是是,在紙帶上寫一個1,若是不是,在紙帶上寫一個0。
首先要定義「圖靈機」的規則:
定義好了 起始狀態+規則,就像寫好了程序,如今能夠輸入了,假設把「1 1 0」放在紙帶上,有兩個1,是偶數。
規則只讓讀寫頭向右移動,其它部分可有可無,爲了簡單留空。
圖靈機開始運行:
若是有足夠時間和內存,能夠執行任何計算,它是一臺通用計算機。
只要有足夠的規則,狀態和紙帶,能夠創造任何東西。
因此,圖靈機是很強大的計算模型。
事實上,就可計算和不可計算而言,沒有計算機比圖靈機更強大,和圖靈機同樣強大的,叫「圖靈完備」。
每一個現代計算系統,好比筆記本電腦,智能手機,甚至微波爐和恆溫機內部的小電腦,都是「圖靈完備」。
停機問題
爲了回答可斷定性問題,把圖靈機用於一個有趣計算的問題:停機問題。
簡單說就是:給定圖靈機描述和輸入紙帶,是否有算法能夠肯定,機器會永遠算下去仍是會到某一點會停機?
有沒有辦法在不執行的狀況,弄清會不會停機呢?一些程序可能要運行好幾年,因此在運行前知道,會不會出結果頗有用。不然就要一直等啊等,憂慮到底會不會出結果。圖靈經過一個巧妙的邏輯矛盾,證實了停機問題是沒法解決的。
想象有一個假想圖靈機,輸入: 問題的描述 + 紙帶的數據,輸出「yes」表明會停機,輸出「no」表明不會停機。
推理:若是有個程序,H(halt的第一個字母)沒法判斷是否會「停機」,意味着「停機問題」沒法解決。
爲了找到這樣的程序,圖靈用H設計了另外一個圖靈機,若是H說程序會「停機」,那麼新機器會永遠運行(即不會停機),若是H的結果爲No,表明不會停機,那麼讓新機器輸出No,而後「停機」。
圖靈機1(H): 輸出yes->停機, 輸出no->不會停機
圖靈機2:H(yes) -> 不會停機,永遠運行, H(no)-> 機器停機,輸出no
實質上是一臺和H輸出相反的機器,若是程序不停機,就機器停機。若是程序停機,就機器永遠運行下去。
還須要在機器前面加一個分離器,讓機器只接收一個輸入,這個輸入既是程序,也是輸入。把這臺新機器叫「Bizzaro(DC漫畫的一名反派角色,他的能力和超人相反)」
若是把「Bizzaro」的描述,做爲自己的輸入會怎樣,意味着在問H,當「Bizzaro」的輸入是本身時,會怎樣。
但若是H說「Bizzaro」會停機,那麼「Bizzaro」會進入無限循環,所以不會停機。
若是H說「Bizzaro」不會停機,那麼「Bizzaro」會輸出No而後停機。
因此H不能正確斷定,停機問題,由於沒有答案,這是一個悖論,意味着「停機問題」不能用圖靈機解決。
圖靈證實了圖靈機能夠實現任何計算。
可是,「停機問題」證實了,不是全部問題都能用計算解決。
丘奇和圖靈證實了計算機的能力有極限。
不管多少時間或內存,有些問題,是計算機沒法解決的,計算是有極限的,起步了可計算性理論,稱之爲:「丘奇-圖靈論題」。
圖靈測試
當時是1936年,圖靈只有24歲,在1939年,二次世界大戰,圖靈的才能很快被投入戰爭,事實上,在戰爭開始前一年,已經在英國政府的密碼破譯研究。工做之一,是破解德國的通訊加密,特別是「英格瑪機(Enigma)」加密信息。
簡單說,英格瑪機會加密明文,若是輸入字母H-E-L-L-O
,機器輸出X-W-D-B-J
,這個過程叫「加密」。
文字不是隨便打亂的,加密由「英格瑪機」頂部的齒輪組合決定。每一個齒輪有26個可能位置,機器前面還有插板,能夠將兩個字母互換。總共有上十億種可能。
知道正確的齒輪和插頭的設置,輸入 X-W-D-B-J
就會輸出hello
,解密了這條信息。
有數十億的組合,根本無法手工嘗試全部組合。英格瑪機和操做員不是完美的,一個大缺陷是:字母加密後毫不會是本身。H加密後絕對不是H。
圖靈在前人的基礎上,設計了一個機電計算機,叫: Bombe
。利用了這個缺陷,它對加密消息嘗試多種組合,若是發現字母解密後和原先同樣,就知道英格瑪機不會這樣子作,這個組合會被跳過,接着試另外一個組合,Bombe
大幅度減小了搜索量,讓破譯人員把精力花在更有可能的組合,好比在解碼文本中找到經常使用的德語單詞。
德國人時不時會懷疑有人在破解,而後升級英格瑪機,好比加一個齒輪,創造更多可能組合,甚至還作了全新的加密機,整個戰爭期間,圖靈和同事都在努力破解加密,解密到德國情報,爲盟軍贏得了不少優點,有些史學家認爲他們把戰爭縮短好幾年,戰後,圖靈回到學術界,爲許多早期計算機工做做出貢獻。
好比曼切斯特1號,一個早期有影響力的存儲程序計算機。但他最有名的戰後貢獻是「人工智能」。這個領域很新,直到1956年纔有名字。
1950年,圖靈設想了將來的計算機,擁有和 人類同樣的智力,或至少難以區分。
圖靈提出若是計算機能欺騙人類相信它是人類,纔算是智能。這成了智能測試的基礎,現在叫「圖靈測試」
想象在和兩我的溝通,不用嘴或面對面,而是來回發消息,能夠問任何問題,而後會收到回答,但其中一個是計算機,若是分不出哪一個是人類,哪一個是計算機,那麼計算機就經過了圖靈測試。
這個測試的現代版叫:「公開全自動圖靈測試,用於區分計算機和人類」,簡稱,驗證碼,防止機器人發垃圾信息等。
圖靈於1954年服毒,年僅41歲。
因爲圖靈對計算機科學貢獻巨大,許多東西以他命名,其中最著名的是「圖靈獎」(計算機領域的最高獎項)。