PHP7資料結構與演算法分析:第一章--資料結構和演算法簡介

來,見證奇蹟發表於2018-01-27

enter image description here

第 1 章 資料結構和演算法簡介


當今世界是一個數字化的世界,科技已經影響了我們生活的方方面面,而這些科技背後執行的東西是程式!而程式本質是什麼?

程式 = 資料結構 + 演算法

是的,程式的本質就是資料結構和演算法的綜合體!

但是程式的目的是用來解決某個實際問題的!也就是說資料結構和演算法是用來解決實際問題的!那麼資料結構和演算法與實際問題有什麼關係呢?資料結構和演算法如何解決實際問題的呢?資料結構狗和演算法有哪些特徵和分類呢?

這些問題,我們將馬上來一一解答!

首先先讓我們從整體上對本章內容做一下了解。

本章內容如下:

  • 生活中的資料結構和演算法

  • 資料結構和演算法的重要性

  • 為什麼是PHP?

  • 認識ADT

  • 資料結構常見型別

  • 認識演算法

1.1 生活中的資料結構和演算法

生活中的資料結構?!

請你先不要吃驚!也許你認為資料結構只存在於計算機程式中特別抽象的東西!但是事實恰恰相反!資料結構實際上原產於“現實”!是的!這裡的“現實”指定就是現實世界,指的是真實的生活!

首先讓我們來看一個相信大家都經歷過的場景:排隊購票! 下面給出的圖片可以幫你回憶這種場景!

enter image description here

像這種一個一個有序排列的場景實際上就是一種資料結構--佇列!

下面再給你看一個真實的場景!計劃便籤!你想想我們做計劃的時候都是一張張貼上去,但是要完成計劃,都是從最上面開始!最先貼上去的反而最後完成!像這種後來反而先執行的場景就是一種典型的資料結構--棧!

enter image description here

ok!繼續看下面一個!是我國的地區分級表!最上面的是偉大的中國,中國下面是省級,省級下面是市級!當然還可以再細分!那麼像這種出現了層級關係的場景又是另外一種典型的資料結構--樹!

enter image description here

我們們最後再看一個,下面是對A的一個朋友圈的描述,假設每個圓圈都代表了一個人!那麼這些人之間,有的互相有關係,有些人之間並不認識。那麼像這種互相之間可能有多種關係的場景,又描述了另外一種典型的資料結構--圖!

enter image description here

再讓我們舉一個生活中演算法的例子!

就那我們手機上的電話簿來說!或者你微信上的聯絡人列表!現在假設你要查詢某個以T打頭的聯絡人,請問你,你會從頭開始一個一個查嗎?你還是一下跳到第一個以T開頭的位置開始找!我相信你會使用第二種方法!其實這個過程中,你既應用了一種資料結構--雜湊表,也使用了一種高效的演算法--二分查詢。

好了,你也許會舉出更多更多的例子出來!尤其是學完本套課程!你會發現身邊處處都是資料結構!處處都是演算法!

其實各種資料結構都是對生活特定場景的總結,然後起了個名字!演算法呢,也是對解決問題過程的描述!生活中,你身邊,到處都是他們的身影!

所以,資料結構和演算法,是一門貼近實際,又是解決實際問題的非常有用的一門學科!

通過上面的內容,我們明白了其實資料結構和演算法是基於生活的!瞭解這個是為了讓我們有一個更真實的認知!

但是資料結構和演算法到底有多重要呢?資料結構和演算法是如何定義的呢?如何實現呢?有哪些型別呢!下面就讓我們一個一個的解決這些問題!

1.2 資料結構和演算法的重要性

要了解資料結構和演算法的的重要性,先讓我們給它們明確的定義!

首先讓我們談談資料結構!

資料結構(data structure)就是計算機中儲存和組織資料的一種特定的形式,其目的是為了讓資料的關係更合理,處理更高效!

例外一種更加簡潔的定義方式是

資料結構 = 資料 + 結構

其實我認為下面的這種方式固然簡潔,但是也更直接說明了資料結構的本質!資料結構實際上包含了兩個主要的方面!一方面描述了資料的儲存,另一方面更加重要的是可以描述資料之間的關係!

想想我們的生活就知道了,排隊時每個人後面只有一個,這就是一對一的線性關係!公司中一個上級管理多個直接的下級,這是分層的一對多的樹形關係!朋友之間的關係很複雜,可以描述成多對多的圖形關係!當然班級中各個同學之間從學習這個層面上講,大家只是聚在一個班級中,相互還是獨立的,這個我們就能描述成集合關係!所繫其實按照資料之間的關係來分的話,資料結構可以分為下面幾種:

  • 集合關係:簡單的集中在一起;
  • 線性結構:一對一的關係;
  • 樹形結構:一對多的層級關係;
  • 圖形結構:多對多的關係。

通過這個分類,大家可以明白一個問題!結合實際問題,使用良好的資料結構來儲存和描述該實際問題可以幫助我們更加高效的解決它!其實這個也很好理解!現實生活中我們為了更加高效的管理和解決實際問題還要分類管理我們的資料呢!例如檔案要歸檔分類管理!例如圖書要分類按照編號排放!例如你通訊錄裡的好友名單是不是親人、好友、同事等等也要分類存放!所以,要把資料存入計算機一樣的道理!都是為了更加高效的管理和使用資料!

所以採用良好的資料結構,對於能否高效的解決實際問題非常的重要!結合實際生活,這個更好理解!

OK,讓我們再來談談演算法!

什麼是演算法呢?

演算法(algorithms)是針對特定的問題,經過精心設計的用來一步步解決該問題從而得到答案的過程。

聽起來是不是有些熟悉?如果把上面的定義拿到生活中,我們可以用另外的詞彙來代替演算法--方法!

是的,生活中,我們把解決某個問題的步驟或過程叫做方法!而在電腦科學中,解決問題的步驟我們就稱之為演算法!其實呢,一樣的道理!

這裡舉一個廣為人知的栗子來說明什麼是演算法:如何把大象關進冰箱?

  • 首先要準備一臺足夠大的冰箱;
  • 把冰箱門開啟;
  • 把大象裝進冰箱;
  • 把冰箱門關上。

以上我們描述的就是“把大象裝進冰箱”這個問題的解決步驟,我們稱之為“解決這個特定問題的方法”。那麼它是不是一個 “演算法”呢?是的!它也是一個演算法!你也許會說不是在電腦科學中,我們管解決問題的步驟才叫演算法嗎!對的,現在如果我告訴你我要把這個問題用計算機來解決!你還有疑問嗎?或許對於大多數人而言,還是感覺彷徨!因為這裡我們沒有使用任何的計算機語言!沒有使用C語言,java語言,甚至是我們這本教程裡使用的PHP語言!但是這並不妨礙我使用了一門“語言”把這個問題描述明白了!而對於演算法而言,它並不在意你是使用了計算機語言,還是自然語言,甚至是啞語!其實演算法,從其含義上來說,演算法是解決問題的步驟的思想的描述!而我們使用計算機語言來實現演算法是演算法的實現的過程僅僅是為了讓計算機能執行!所以演算法和演算法的實現是兩碼事!

但凡是方法就有好壞!演算法既也是方法!那麼一個優秀的演算法和一個常規的演算法到底差別有多大呢?舉一個我們都能感受到的例子:搜尋!

假設給你100條有序的記錄,要查詢一條指定的記錄,普通的演算法是從頭一條一條的比對,直到找到為止,最壞的情況下,你要找100次! 有一種二分查詢法,要找7次就找到了! 如果是40億條資料中找資料呢?第一種方法,最壞要找40億次!用二分查詢最多需要32次就找到了!吃驚嗎?這就是差距!普通的演算法可能需要幾年,幾十年才能搞定的東西,優秀的演算法可能幾秒鐘就搞定了!

那麼究竟資料結構和演算法是什麼關係呢?我很喜歡這樣一個比喻:

資料結構和演算法的關係就像米與巧婦!資料結構是米,是對事物本體的內容儲存!演算法是巧婦!她研究的是如何利用資料結構更好的解決實際問題!也就是把米做成更好吃的飯!米的質量越上乘,對巧婦的技藝要求就越低,同樣,巧婦的廚藝越精湛,食品的味道越佳!如果米的質量和巧婦的手藝都好,那麼吃到好美味的機會也就大大增加了!

任何的問題要想讓計算機解決,那麼首要的就要存入到計算機,所以一定會用到資料結構!選用合適的資料結構來儲存,會讓“米”的質量更好!要解決問題,就需要特定的步驟,就一定會用到演算法!好的演算法會讓事情事半功倍!

所以,合適的資料結構和好的演算法對於能夠順利甚至是高效的解決問題,都十分的重要!

1.3 為什麼是PHP

首先,PHP是非常流行的指令碼語言!數以萬計的網路程式使用它進行編寫!小到個人部落格大到各大平臺級別產品!既然如此流行,那麼就有必要從各個層面來提高我們的能力來更快的解決問題!尤其是PHP是一門後端伺服器語言,PHPer要處理大量的資料和邏輯,資料結構與演算法是基本能力之一!

第二,PHP7的釋出,從語言層面優化了效率,健壯性也得到很好的加強!顯然這門語言也想要發揮更大的力量!這也就意味著我們會更多的應用這門語言解決問題!所以我們十分有必要學習更好的使用這門語言!這就要求我們可以更好的掌握資料結構和演算法的相關知識!

再者,PHP本身提供了SPL,也就是標準PHP庫!SPL內建了大量的資料結構!這說明PHP作為一門伺服器語言,其實非常的注重資料結構和演算法!當然這屬於高階應用!為了能夠更好的應用PHP這些高階的技術,我們十分有必要掌握資料結構和演算法的原理!我們會在弄懂原理的基礎上學習PHP的SPL的應用,相信這些知識的學習會使你真正步入PHP高手的殿堂!

當然,還有很多重要的理由讓我們需要掌握PHP相關的資料結構與演算法知識!比如你第一門學習的語言就是PHP!比如你不是計算機專業出身,不會C或者JAVA語言卻又想學資料結構與演算法!比如雖然都是資料結構與演算法,但是語言本身有自己的特點,我們需要了解和結合PHP語言本身的特點來實現PHP化的資料結構和演算法!無論有什麼理由,資料結構與演算法都是我們必要掌握的知識!

所以,從PHP語言本身,還是從我們自身能力的提高而言,PHPer都應該好好的掌握PHP版的資料結構和演算法!

1.4 認識ADT

在瞭解ADT前,先讓我們對PHP內會的資料型別做個簡單的回顧!

PHP有8中內建的資料型別:布林,整型,浮點型,字串,陣列,物件,資源和null。其實PHP變數也有一些靜態語言的特性!但是,由於PHP是弱型別語言,在使用變數時,我們不用考慮對變數進行資料型別的初始化!當我們需要時,我們可以直接定義和使用變數!

目前我們已經瞭解了一些資料結構的相關知識!那麼,我們可以使用PHP內建的這些資料型別來表達這些資料結構嗎?有些或許可以,但是大多數的並不能直接來表達!你可以思考一下,PHP內建的這些資料型別雖然可以進行不限制資料型別的儲存,但是它們的基本功能僅僅是資料的儲存,並沒有資料之間的關係內容的具體表達!至少大多數不是!但是現實的問題是相對複雜的,一個資料裡面可能有多個子項,而且資料和資料之間存在的對應的關係,並且這種資料型別有屬於它們特定的操作方法!顯然,內建的資料型別就已經不能滿足要求了。這就用到了ADT,也就是抽象資料型別。

ADT:抽象資料型別(abstract data type)的簡稱, 是指一個數學模型和定義在該模型上的一組操作!

你會發現ADT不僅定義了資料的儲存還定義了這種型別資料的一系列操作!其實這個不難理解,你想想我們現實中的排隊購票, 所有人都依次排好, 對於當前的隊伍是不是有辦理完購票的人員離開和 新來的人排到隊尾,即出隊和入隊這兩個關鍵操作!那麼當前的隊伍就是一個典型的佇列資料結構,而入隊和出隊操作就是屬於這種特殊的資料結構的兩個關鍵操作!當前的佇列和這些關鍵操作綜合起來才是一個完整的佇列ADT即佇列抽象模型!其實簡單一點想就是:

對於特定的一種ADT,其包含了該種型別資料的資料結構和對應的演算法集合!

我們還需要理解的是,抽象資料型別ADT是一種邏輯上的概念!是我們對實際問題分析進行抽象總結出來的一種“自定義的資料型別”和一組操作!是的每個人都可以自定義ADT來使用!但是資料結構和演算法的知識體系中已經對我們經常遇到的問題進行了總結和分類,所以,經常使用的ADT型別有:

  • 集合
  • 連結串列
  • 佇列
  • 優先佇列

在接下來的章節中,我們將學習更多型別的ADT並使用PHP來實現對應的資料結構,讓我們一起加油!

1.5 資料結構常見型別

要了解資料結構的型別,先讓我們來了解幾個概念。

資料(data):是對客觀事物使用特定的符號表示!在電腦科學中凡是能輸入到計算機並能夠被計算機處理的所有符號組成的集合是資料!

例如我們要研究班級A內學生成績的分佈情況,那麼班級A裡面所有的學生資訊組成的集合就是資料!

資料元素(data element):資料的基本單位。

例如班級A內的一個學生就可以看做一個資料元素。

資料項(data item):資料元素的基本特徵,是資料中不可分割的最小單位!

例如班級A內一名學生的學號就是一個資料項,姓名是另外一個資料項!

資料物件(data object):資料的子集,資料內性質相同的資料元素的集合。

例如班級A內按照成績排名,優秀成績的學生的集合就是一個資料物件!成績良好的學生組成的集合是另一個資料物件!

那讓我們總結一下這些概念的關係:

資料由一個個資料元素組成,資料元素包含多個資料項!
而資料可以按照某種性質標準分割成多個資料物件!一個資料物件內是由一定數量的符合該資料物件性質的資料元素組成。

以上這些概念我們這整個資料結構和演算法的學習過程中都會用到,所以請大家務必好好思考並掌握這些內容!

下面就讓我們看看資料結構的種類。

首先按照資料元素的儲存關係我們可以將資料結構分為兩種:

  • 線性資料結構
  • 非線性資料結構

線上性資料結構中,資料元素是線性並且連續排列的!就像現實中排隊購票這樣!陣列,連結串列,佇列,棧是線性結構的典型代表!而非線性結構的資料元素並不一定連續的,資料結構中的樹和圖是非線性結構的典型代表!

下面就讓我們一起來暢遊資料結構的世界,讓我先帶大家來認識一下我們接下來要學習的各種資料結構,在接下來的章節我們會更詳細的學習這些資料結構。

就像我說的,我們的世界存在多種資料結構,但有些並不通用或常用。其實我們的前輩們已經總結了一些最常用的資料結構,它們是:

  • 陣列
  • 連結串列
  • 佇列
  • 優先佇列
  • 集合
  • 雜湊表

陣列

陣列是PHP中的一種內建資料型別,但是大家考慮過為什麼PHP的陣列可以儲存不同型別資料呢?明明PHP語言是基於C語言打造的,在C語言中陣列是固定大小的並且只能儲存一種型別的資料!但在PHP中陣列並不定長而且可以儲存多種型別資料!這是如何實現的呢?其實PHP中的陣列底層是基於有序的雜湊表來構建的!雜湊表的概念我們後面會講!我們這裡簡單的認為關聯陣列就是雜湊表就可以了。這樣就意味著PHP陣列實際就是雜湊表這種資料結構。所以陣列在PHP中我們可以認為是PHP內建的資料結構!確切的說我們可以使用陣列來模擬多種資料結構!這些內容我們將在陣列一章進行更多講解!

連結串列

連結串列也是一種線性資料結構,與陣列不同的是陣列是邏輯上連續並且實體記憶體中也連續的結構!這就意味著在記憶體陣列對應這著一塊兒連續的空間。而連結串列只要求邏輯上連續就可以了,這樣就意味著邏輯上連續的資料在記憶體中並不一定是連續的!在實際應用中,連結串列是非常實用的資料結構!在接下來的章節中,我們會學習連結串列的相關內容!其實連結串列有很多種類,例如單連結串列,迴圈連結串列,雙向連結串列等等,當然這些內容後面會詳細講解,是不是充滿期待!

佇列

佇列也是一種線性資料結構!這種資料結構真的可以描述我們常見的排隊購票的場景!元素總是隻能在佇列的一頭出去,而在佇列的一頭進入佇列!所以佇列是先進先出的結構,很特別吧?接著看,下面的結構也很特別!

優先佇列

優先佇列也是一種佇列,只不過有點特別!這個可以拿醫院排隊來說明,雖然都是排隊,但是總是急診優先!也就說資料元素是有權重的!但整體而言,仍舊是一個佇列!我們將詳細學習這種資料結構!

棧也是一種線性資料結構!這個可以對比佇列學習!因為與佇列的先進先出不同,對於棧結構,資料元素是後進先出的!很特別?那到底有什麼用呢!在哪用呢?留個懸念,我們會好好學習相關內容!

集合

集合不再是線性資料結構!它是鬆散的資料,代表了弱關係的資料元素的彙集!但是集合也很特殊,集合中的元素是不會重複的!這個特性就非常有用了,至於如何用!詳情後面講!

雜湊表

雜湊表又叫做雜湊表!正如前面介紹的,PHP的陣列底層實現就是雜湊表!簡單的理解雜湊表就是類似關聯陣列一樣的鍵值對!至於其原理到底是什麼,有哪些操作,在相關章節我們會揭開它都面紗!

樹是非線性資料結構!而且是十分重要且常用的資料結構!樹有很多種類,而且每一種都有大用處!我很激動能為你介紹樹!在樹的這一章我會帶你一起學習內容十分豐富的這種結構!

其實堆是一種特殊的樹!而且其用途也很廣泛,例如堆排序,解決圖論問題啊等等!堆又分為“大根堆”和“小根堆”,分別有什麼用呢?在堆章節我們會詳細研究和學習!

圖又叫圖論!也是一種非線性結構,而且是這些資料結構中最難 一種!它的內容非常的豐富!其用途也非常廣泛!前面我們已經瞭解過它的一些簡單場景,例如人與人的關係,交通規劃!在圖這一章我會帶你進入圖的世界,深入剖析圖論的用法!

1.6 認識演算法

到目前我們已經討論了很多型別的資料結構!但是我們也瞭解到資料結構主要意義是解決問題的資料的儲存問題!要真正的解決問題,還離不開另一項重要的內容:演算法!

前面我們已經瞭解了演算法的定義,演算法是對特定問題解決步驟的描述,現實生活中我們稱之為方法,電腦科學中表現為指令的序列。但是如何去判定是不是演算法呢?或者說符合什麼特點的我們可以稱之為演算法呢?讓我們一起來分析一下。

1.6.1 演算法的特性

演算法具有五個基本特性:輸入、輸出、有窮性、確定性和可行性。

輸入輸出

演算法允許具有零個或多個輸入,但是必須至少有一個或多個輸出。 演算法允許輸入這個不難理解,但有些情況例外,比如我們寫一個函式只想輸出固定的“hello world”就可以不需要輸入引數了!但是演算法是一定需要有輸出的,因為解決完問題一定要有結果!那麼對於演算法而言輸出的形式可以是列印輸出,也可以是返回一個或多個值等。

有窮性

有窮性是指演算法在執行有限的步驟之後,可以自動結束而不會出現無限迴圈,並且每一個步驟的執行時間都是在可接受的範圍內。直白一些說就是演算法的實現程式碼不能是死迴圈的!並且目標是1分鐘內實現結果演算法卻要執行1年!這樣雖然有窮但是並不是合格的演算法!

確定性

確定性是指演算法的每一步驟都具有確定的含義,不會出現二義性。舉個例子來說就是有確定的輸入就應該有固定的輸出!不能是這次輸入A得到結果B,下一次輸入A卻得到結果C!這就違反了確定性原則!

可行性

可行性是指再現有的條件下演算法的每一個步驟都應該是可以實現的而不是隻是空想或者你設計了一個20年之後可以實現的演算法,這個在當下都是沒有意義的。

現在我們應該明白符合以上五個特點的程式我們就可以看做是演算法。但是解決一個問題的方法有多種,這就意味著可以對某一個問題設計多個演算法來解決!那麼如果要我們來設計演算法來解決問題,要掌握什麼原則才能設計出比較優良的演算法呢?

1.6.2 演算法設計和實現的原則

要設計出優良的演算法,前輩們已經總結出了一些原則供我們來參考!這樣我們就可以站在巨人的肩膀上寫我們自己的演算法了!

基本的原則有五個: 正確性、健壯性、高效性、環保性和可讀性!

正確性

正確性是設計算的最基本的原則!如果不能正確的解決問題,那設計演算法的意義又是什麼呢?這個容易理解!但是所謂的“正確”在這裡確是多層面的! - 演算法的實現上沒有錯誤。即我們編寫的程式碼沒有語法錯誤。 - 演算法對於合法的輸入應該能得到滿足要求的輸出結果! - 演算法對非法的輸入應該得到合適的反饋結果。 - 演算法程式是精心選擇的,甚至刁難的測試資料都能得到滿足要求的結果。 - 演算法是針對該問題的而不是針對其它問題的!否則滿足上面四項也是錯誤的!

前面的容易理解,最後一條反例是:要設計一個排序的演算法你卻設計了一個搜尋的演算法,或許語法正確,資料輸入也能得到結果,但是並不能解決目標問題。這種常識上的沒有錯誤在這裡也是不正確的!

健壯性

健壯性是在證確性的基礎之上對演算法更高層次的要求!是說程式是穩定可靠的!不能說執行一段時間程式就要掛掉!或者因為一次非法輸入程式就崩潰了! 健壯性就是要求程式穩定執行!

高效性

高效性是說演算法的時間效率儘量的高!也就是我們常常說的“做事兒的效率”!在這裡就是演算法解決問題的效率!其實這個也是大家常常去評判一個演算法優劣的很重要的標準!

環保性

提到環保性,很多人是不是感覺莫名其妙!難道演算法也要環保嗎?環保除了人人有責,演算法也有責?其實這裡的環保性是指演算法的資源利用越少越好!即我們常說的佔用記憶體越低越好!其實這個也好理解!我們電腦上的資源都是有限的!對於演算法而言,肯定是佔用的記憶體的少一些是更有優勢的!

可讀性

最後一個原則往往被很多人忽略掉,但也是最重要的原則! 演算法的實現最終是要靠程式的,在這裡我們是要用PHP編寫程式來實現演算法!那麼程式是不是一次性寫完,以後就再也不會修改維護了呢?顯然不是!

程式讀的次數要遠遠多於寫的次數!

編寫過程中要讀,維護修改要讀,升級要讀,甚至要刪掉先要找到,這個也要先讀!這也是為什麼我說這條原則是最重要的原則!程式實際上是寫給人看寫給計算機執行的!軟體的維護成本要遠遠高於製作成本!你可以想想一下面對一堆可讀性非常差的程式碼和麵對可讀性非常好的程式碼的感受!我相信這一點有一定工作經驗的人一定可以感同身受!

所以,為了設計和實現更好的演算法,我們應該好好的參考這些經過驗證的原則!希望大家都能設計和實現優秀的演算法!

其實上述的五個設計原則不光可以幫助我們設計出優秀的演算法,還可以作為我們評判一個演算法好壞的原則!尤其是其中的高效性原則和環保性原則!因為這兩個原則是可以量化的!所以我們往往也利用這兩個原則來評判一個演算法的質量!下面一章就讓我們來看一下我們是如何利用這兩個原則來評判演算法的!

1.7 小結

本章我們瞭解了資料結構和演算法即它們的重要性,並詳細介紹了資料結構的分類,引入了ADT的概念。 本章介紹了很多概念,我們將在未來的學習中不斷用到它們,希望大家能好好的掌握本章的這些內容

相關文章