文章首發於公衆號一件風衣(ID:yijianfengyi)編程
在編程中,咱們常使用一組有順序的數據來表示某個有意義的數據,這種一組元素的序列的抽象,就是線性表,簡稱表,是不少複雜數據結構的實現基礎,在Python中,list和tuple就能夠看做是線性表的實現。數據結構
1、線性表的性質和ADTapp
(一)幾個基本概念
1.線性表是一組有窮元素(元素能夠是任何類型的數據)拍成的序列,元素的位置稱做下標,下標從0開始計數;
2.表中元素的個數稱做表的長度,不含任何元素的表稱爲空表,空表的長度爲0;
3.非空表的第一個元素爲首元素,最後一個元素爲尾元素,除首元素外,每個元素的上一個元素稱爲前驅元素,除尾元素外,每個元素的後一個元素稱爲後繼元素;佈局
(二)表抽象數據類型
1.表的操做
考慮到需求,咱們對錶可能有以下操做:
•建立一個空表,或者根據提供的元素序列初始化一個新表;
•判斷是否爲空表、輸出表的長度(元素個數)、檢查是否存在某個元素;
•在表中插入一個元素、刪除特定位置的元素或按照內容刪除元素;
•對整個表進行的操做,好比兩個表的合併、一個表按照某種運算生成一個新表等;
•遍歷測試
2.表抽象數據類型的僞代碼描述
根據以上操做,咱們能夠設計表抽象數據類型僞代碼以下:spa
ADT List: #表抽象數據類型 List(self) #建立一個新表 is_empty(self) #判斷self是否爲空表 len(self) #得到self的長度 prepend(self,elem) #在self的首元素前插入元素elem append(self,elem) #在self的尾元素後插入元素elem insert(self,elem,i) #在self的位置i處插入元素elem,其餘元素保持相對位置不變 del_first(self) #刪除self的首元素 del_last(self) #刪除self的尾元素 del(self,i) #刪除self的第i個元素 search(self,elem) #查找elem在self中出現的位置,不存在則返回-1 forall(self,op) #對self中的每一個元素執行操做op
3.經典的線性表實現模型
•將表中的元素順序存放在一片足夠大的連續存儲位置裏,稱做順序表,元素在存儲區內的物理順序就是該表的元素的邏輯順序,本文後面講討論順序表;
•將表中的元素經過連接的形式保持順序,存儲在一系列存儲區內,稱做鏈表,在下一篇文章中討論。設計
2、順序表的實現code
(一)順序表的佈局方案orm
先考慮一種簡單狀況:若是表中的每個元素佔用的存儲空間相同,那麼能夠直接在內存中順序存儲,假設第0個元素的存儲位置爲l_0,每一個元素佔用的空間爲c=size(e),那麼第i個元素的存儲位置就是l_i=l_0 + c * i,此時實現對某個位置元素的訪問,能夠直接經過計算出的位置訪問,時間複雜度爲O(1)。
那麼當元素長度不同的時候怎麼解決呢?咱們能夠按照剛纔的方式,在順序表中存放元素的存儲位置,而元素另行存儲,這個順序表就稱做是這組數據的索引,這就是最簡單的索引結構了。索引順序表的每一個元素爲地址,佔用空間同樣,直接訪問索引再依據索引中存放的地址找到實際元素,時間複雜度依然爲O(1)。
新的問題來了,表的操做要求表的長度是能夠變化的,增刪操做均會引發表長度的變化,那麼如何給表分配空間呢?Python中的tuple類型就是一種不可變的數據類型,在創建時根據元素個數分配存儲區域,但須要變化長度的時候,就不能用這種分配方式了。對象
解決這種方式有一種方法是分配一大片區域,在表的開始位置記錄這個表的容量和現有元素個數,表中除了構造時的元素外,其餘空間留出空位供新元素使用。至於若是須要的空間超出了表的容量了怎麼辦呢?這個咱們後面再討論,如今咱們先依照上面的思路,考慮各類操做的實現。
(二)順序表基本操做的實現
1.建立空表
分配一塊內存區域,記錄表的容量,同時將表的元素個數設置爲0,例如max=8,num=0;
2.判斷表空表滿
很簡單,num=0時爲表空,num=max時爲表滿;O(1)
3.訪問下標爲i的元素
首先檢查下標i是否合法,即i在0到num-1之間(能取到邊界值),合法則計算實際位置,由實際位置返回元素值;O(1)
4.遍歷訪問元素
用一個整數標誌記錄遍歷到達的位置,每一個元素的位置同理計算得出,先後位置能夠減一加一實現,注意遍歷時不可超出邊界;O(n)
5.查找某值在表中第一次出現的位置
基於下標線性檢索,依次比較元素和該值是否相同,相同則返回索引,若檢索完不存在相同,則返回-1;O(n)
6.查找某值在表中k位置後第一次出現的位置
原理與上一條相同,只不過索引從k開始而已。O(n)
7.尾端插入新元素
先不考慮分配更多空間的狀況,表滿時插入返回錯誤提示,不滿時,直接在該位置插入,並修改num的值;O(1)
8.新元素插入到位置k
這是插入的通常狀況,尾端時k=num。先檢查k是否合法,若是合法,將表中k位置和以後的元素都向後移動,以保證表的順序,而後在k位置插入該元素,更新num值;O(n)
9.刪除尾元素
直接num減一就行,在邏輯上已經刪除了尾元素;O(1)
10.刪除位置k的元素
將位置k以後的元素依次前移,num減一;O(n)
11.基於條件的刪除
遍歷,刪除;O(n*n)
(三)補充說明
1.順序表的實現方式
2.表滿以後的操做
插入時若是表滿,能夠申請一片更大的空間,而後將表的元素所有複製過去,而後改變表對象的連接指向新區域,插入新元素,這樣就能夠實現表的動態擴容。
3.擴容的大小
能夠是線性擴容,例如每次增長10個元素存儲空間,考慮到每次擴容須要複製,此時插入一個元素的平均時間複雜度爲O(n),顯然不太理想,另外一種一種方法加倍擴容,每次擴容後容量翻倍,此時插入操做的複雜度爲O(1),雖然效率提升了,但形成了空間的浪費。(時間複雜度的具體計算在此不作討論)
(四)Python中的list類型
Python中的list就是個可變的順序表類型,實現了以上各類操做,感興趣能夠去看具體實現的代碼,其中表容量操做由解釋器完成,避免了認爲設置容量引起的錯誤。最後列舉幾個操做的使用:
1.訪問
list[i]
2.獲取元素個數
len(list)
3.返回最大值,最小值
max(list) min(list)
4.將元組轉化爲列表
list(seq)
5.尾部插入新元素
list.append(elem)
6.統計某個元素出現的次數
list.count(elem)
7.尾部一次性追加元素序列
list.extend(seq)
8.找出第一個值爲elem的元素的索引
list.index(elem)
9.在i位置插入elem
list.insert(i,elem)
10.彈出第i個元素,並返回該元素的值,默認爲最後一個元素
list.pop(i)
11.移除第一個值爲elem的元素
list.remove(elem)
12.將list反向
list.reverse()
13.清空列表
list.clear()
14.複製列表
list.copy()
15.對列表進行排序
list.sort(cmp=None,key=None,reverse=False)
cmp爲排序方法,key爲用來比較的元素,reverse爲True時降序,默認False爲升序,具體使用很靈活,能夠參考相關文檔。
思考:設計一個列表(之後咱們會知道這種列表叫作棧),能夠實現
push(x):將x插入隊尾
pop():彈出最後一個元素,並返回該值
top():返回第一個元素
getMin():返回列表中的最小值
而且要求每一個操做的時間複雜度都爲O(1),難點在於getMin()的時間複雜度。
如下是上篇思考題個人實現,歡迎提建議:
import datetime class Student: _idnumber = 0 #用於計數和生成累加學號 @classmethod #類方法,用於生成學號 def _generate_id(cls): cls._idnumber += 1 year = datetime.date.today().year return "{:04}{:05}".format(year,cls._idnumber) def __init__(self,name,sex,birthday): #驗證輸入後初始化 if not sex in ("男","女"): raise StudentValueError(sex) if not isinstance(name,str): raise StudentValueError(name) try: birth = datetime.date(*birthday) except: raise StudentValueError(birthday) self._name = name self._sex = sex self._birthday = birth self._studentid = Student._generate_id() def print(self): #打印所有屬性 print(",".join((self._studentid,self._name,self._sex,str(self._birthday)))) def setName(self,newname): #設置姓名屬性,其餘屬性同理 if not isinstance(newname,str): raise StudentValueError(name) self._name = newname def getAge(self): #計算年齡 today = datetime.date.today() try: birthday = self._birthday.replace(year = today.year) #若是生日是2月29且今年不是閏年則會異常 except ValueError: birthday = self._birthday.replace(year = today.year,day = today.day - 1) #解決方法是把日減一 if birthday > today: #沒到今年生日則減一,到了則不減 return today.year - self._birthday.year - 1 else: return today.year - self._birthday.year class StudentValueError(ValueError): #用於拋出異常的類 pass
測試效果以下: