文章首發於公衆號一件風衣(ID:yijianfengyi)面試
名人名言強調基礎的重要性的句子不勝枚舉,數據結構與算法做爲計算機專業的必學科目,其重要性不言而喻。算法
在以往的教學體系中,數據結構與算法一般結合C語言進行教學,而近年來Python的興起,已經引發了教學上的變化,據我瞭解,已經有部分大學把C語言和Python同時做爲計算機專業的基礎編程課了。編程
這個系列就和你們一塊兒學習數據結構與算法的Python實現,聽說找工做面試時特別喜歡問基礎的算法問題。數據結構
1、抽象數據類型(Abstract Data Type,縮寫爲ADT)編程語言
ADT是計算機領域一種很基礎的方法,基本的思想就是數據抽象。圍繞抽象出來的數據類型定義各類運算(函數),造成一個完整的模塊,提供接口以供調用,這就有了模塊化的編程思想。模塊化
(一)數據類型和數據構造函數
在任何編程語言中,都定義了一些基本的數據類型,以Python爲例,基本類型有邏輯類型bool、數值類型int、float等、字符串類型str和一些組合數據類型。學習
對於每一種類型,都定義了相應的運算,好比bool類型的值能夠爲True或False,運算包括and(與)、or(或)、not(非)這類邏輯運算,int類型的值能夠爲整數,定義了加減乘除等運算……spa
可是不少時候基本數據類型是不夠用的,好比有理數、複數等,在此以複數爲例,數學上覆數的形式爲a+bi(i爲-1的平方根),定義一個複數類型的變量,咱們須要兩個值,好比寫爲:code
a1 = 2 b1 = 1
就能夠表示複數2+i,一樣,能夠定義複數上的加法運算函數:
def complex_plus(a1,b1,a2,b2): realpart = a1 +a2 imaginarypart = b1 + b2 return realpart,imaginarypart
計算2+3i與3-4i的和就能夠這麼使用:
a3 , b3 = complex_plus(2,3,3,-4)
雖然實現了這個功能,可是用兩個獨立的數來表示一個複數顯然不合適,給後續的使用帶來了很大不便,不過咱們能夠改進一下,用一個二元組來表示複數,約定第一項爲實部,第二項爲虛部:
c1 = (2,3) c2 = (3,-4)
那麼加法運算函數能夠這麼寫:
def complex_plus(c1,c2): realpart = c1[0] + c2[0] imaginarypart = c1[1] + c2[1] return realpart,imaginarypart
調用就能夠直接這麼寫:
c3 = complex_plus(c1,c2)
雖然改進了使用方式,可是依然有很大的問題:
一是用普通的元組來表示複數,不能和其餘的元組相互區分,其餘能夠用元組表示的類型依然能夠進行一樣的運算,好比有理數也用(a,b)來表示,那麼能夠調用複數的加法運算,把一個複數和一個有理數加起來——實部加分子,虛部加分母,這顯然不科學;
二是複數的形式還算簡潔,只有兩個參數,對於不少參數的數據類型還使用元組的話,那可真是太麻煩了。
總的來講,這樣的表示方法雖然能實現功能,但形成了不好的可讀性,使用和修改起來都會很困難,因而咱們須要面向對象的方法來解決這種問題。
(二)抽象數據類型的概念和描述
抽象數據類型把數據定義爲抽象的對象集合,只爲他們定義可用的操做,而不用暴露具體的實現細節。對一個數據類型的操做分爲三類:
1.構造:基於已知的數據類型構造所須要的數據類型,好比基於兩個整數表示一個有理數,或是基於兩個實數表示一個複數等;
2.解析:經過對象獲取須要的信息,好比從一個複數對象中獲取實部或者虛部,從一個有理數對象中獲取分子或者分母;
3.變更:修改對象的內部信息,好比修改複數的實部大小或虛部大小,對象的名稱沒變,可是內部的信息發生了變化。
咱們經過一個int之類的名字來表明這個數據類型,就可使用它了。
編程語言的內置數據類型也是這麼一種抽象數據類型,構造是必要的,根據是否有變更咱們能夠把數據類型分爲可變數據類型和不變數據類型,Python中,str就是一種不變數據類型,list就是一種可變數據類型。
那麼如何描述一個抽象數據類型呢?
以複數爲例,有以下僞代碼:
ADT ComplexNumber:#定義複數抽象數據類型 ComplexNumber(float a,float b)# 構造複數a+bi +(ComplexNumber c1,ComplexNumber c2)# 求複數c1 + c2 -(ComplexNumber c1,ComplexNumber c2)# 求複數c1 - c2 *(ComplexNumber c1,ComplexNumber c2)#求複數c1 * c2 /(ComplexNumber c1,ComplexNumber c2)#求複數c1 / c2 getReal(ComplexNumber c1)# 獲取c1的實部 getImaginary(ComplexNumber c1)# 獲取c1的虛部
這裏用ADT表示這是一個抽象數據類型,ComplexNumber爲類型名稱,同理咱們也能夠用相似的方法表示一個有理數,一個日期,一個銀行帳戶的基本信息等。
總的來講,ADT就是圍繞某個數據類型定義的模塊,接口和實現分離,提供接口完成該數據類型的各類操做。
2、Python類
Python做爲面向對象的編程語言,用類(class)定義全部類型,包括內置的數據類型都是類,咱們能夠這麼理解——類是面向對象編程語言的基本類型,一切數據類型都是基於類定義的。
(一)複數的初階定義
依然以複數爲例,咱們能夠在Python中這麼定義一個類:
class ComplexNumber: def __init__(self,realpart,imaginarypart=0): self.realpart = realpart self.imaginarypart = imaginarypart def plus(self,another): realpart = self.realpart + another.realpart imaginarypart = self.imaginarypart + another.imaginarypart return ComplexNumber(realpart, imaginarypart) def print(self): print(str(self.realpart) + 「+」 +str(self.imaginarypart) + 「i」)
class是關鍵字,表示類定義的開始,ComplexNumber就是這個類的名字。
每一個類中咱們都會定義__init__函數,稱爲初始化方法,用於構造一個該類的新對象,咱們以類名做爲函數建立實例化對象,如:
c1 = ComplexNumber(2,3)
在調用的時候,應當給出除self外的其餘參數。
realpart和imaginarypart都是複數類的屬性,不用單獨聲明,即便在初始化中沒有,在賦值時也會自動建立,但通常不這麼作。
調用其餘方法時,也是self表示本實例,應當給出其餘參數,好比:
c2 = c1.plus(ComplexNumber(3,-2))
此時c1的值做爲plus方法的第一個參數,新構造的複數爲第二個。
同理,print函數中只有一個參數self,因此調用的時候這麼寫:
c2.print()
執行以上語句後咱們能夠獲得以下輸出:
(二)複數的高階定義
對於複數,咱們常用模的概念,對於z=a+bi,模|z|定義以下:
因此咱們須要函數module(),來求取複數的模。能夠定義以下
def module(): return math.sqrt(self.realpart *self.realpart, self.imaginarypart * self.imaginarypart)
調用輸出一個複數時直接這麼寫:
print(c2.module())
其中math.sqrt()是求平方根的函數,須要在程序開頭導入math包:
import math
在定義Python的類時,有這樣的約定,如下劃線開頭的屬性名或函數名都看成內部使用的名字,不可以在類外使用。此外以兩根下劃線開頭(不以兩根下劃線結尾)的屬性會被特殊處理保護,不得在類外使用。若是咱們不但願複數的虛部實部屬性被直接修改,初始化方法中咱們能夠在屬性前加下劃線,阻止類外的訪問:
def __init__(self,realpart,imaginarypart=0): self._realpart = realpart self._imaginarypart = imaginarypart
然而咱們有時又很須要讀取這兩個屬性,因而咱們能夠定義解析操做:
def realpart(self): return self._realpart def imaginarypart(self): return self._imaginarypart
這樣能夠讀取受保護的屬性了。
咱們再考慮新的問題,在數學上咱們能夠直接用加減乘除的符號來操做複數,按照咱們上面的定義,顯然調用起來很不方便,要是能直接把方法定義在操做符上,那使用起來也就方便多了。
好消息是Python中能夠這麼作!Python中爲全部的運算符都定義了特殊的方法名,這類特殊方法名都以雙下劃線開始,雙下劃線結束,如__add__表示加號,__mul__表示乘號,因而對於複數中的加法,咱們能夠以下實現:
def __add__(self,another): realpart = self._realpart + another.realpart() imaginarypart = self._imaginarypart + another.imaginarypart() return ComplexNumber(realpart, imaginarypart)
注意這段代碼中self和another獲取屬性的方式的區別。
相似的,咱們能夠定義其餘運算符的操做(包括比較運算符),具體的方法名能夠查找Python的文檔。但重載運算符時要注意限制條件,好比虛部實部均爲0的複數不能做爲除數等。
(三)幾點總結和提示
1.類定義時格式以下:
class <類名>: <語句組>
2.對類中的屬性的訪問,採用圓點記法;
3.實例對象初始化時自動調用__init__(),第一個參數self表示正在初始化的對象自身;
4.在定義類時,應當避免屬性名和方法名相同;
5.靜態方法、類方法、做用域、繼承等往後再提。
思考:若是要定義一個學生類,屬性包括姓名,性別,生日,學號,方法包括計算年齡,修改屬性,打印所有屬性,初始化時生成學號,規則是年份+順序五位十進制數,你會怎麼實現這個類呢?下一篇中會貼出個人實現方法,歡迎探討。(提示:類中可定義屬性,該屬性不屬於任何實例,只屬於類自己)