閱讀目錄
- 一、線性表的概念和表抽象數據類型
- 二、順序表的實現
- 三、鏈接表
- 四、鏈表的變形和操作
- 五、課后部分編程練習(初學時寫的,僅供參考)
一、線性表的概念和表抽象數據類型
1、表的概念和性質
線性表示某類元素的一個集合,記錄着元素之間的一種順序關系。
理解表的下標,空表,表的長度,順序關系,首元素,尾元素,前驅元素和后繼元素等概念。
2、表抽象數據類型
從實現者角度考慮兩個問題:
- 如何把該結構內部的數據組織好。
- 如何提供一套有用並且必要的操作。
線性表的操作
- 創建操作。考慮提供初始元素的序列問題。
- 解析操作。判斷是否為空,表的長度,取得表內特定數據等。
- 變動操作。添加刪除特定元素等。
- 連表操作。兩表組合得到新表等。
- 遍歷操作。對每個元素進行操作等。
表抽象數據類型

3、線性表的實現
研究數據結構的實現問題,主要考慮兩個方面的問題:
- 計算機內存的特點。以及如何保存元素的關系。(隱式還是顯式)
- 各種重要操作的效率。
根據上面兩個問題,得出兩種基本的實現模型:
- 將元素順序放在一大塊的連續存儲區內,這樣實現的表稱之為順序表。
- 將元素放在通過鏈接構造起來的一系列存儲塊中,這樣的實現稱為鏈接表。
二、順序表的實現
順序表的實現思路很簡單,表中的元素順序放在一片足夠大的連續內存中。首元素存入存儲區的開始位置,其余元素依次存儲。元素之間的關系通過元素在存儲區里的物理位置表示(隱式表示元素間的關系)。
1、基本實現方式

- 表里要保存的元素類型相同,因此每個表元素所需的存儲量相同,可以根據公式Loc(ei) = Loc(e0) + c * i很快求出元素ei的地址,從而訪問到它的元素。
- 表中的元素大小不一。將實際元素另行存儲,在順序表里的各單元保存的是對相應元素的引用信息。這樣,通過上面公式計算出引用信息的位置,在通過引用信息訪問到元素本身。這樣的順序表也被稱為對實際數據的索引。
線性表的一個重要的性質是可以添加刪除元素,那么就會帶來一個問題,我們在建立一個表時,需要給它分配多大的內存?
一個合理的辦法是分配完已有的元素之外,再留一些空位以備添加操作使用。

上圖實例是一個順序表的完整信息。包括容量大小,元素個數,以及各個元素內容。
2、順序表基本操作的實現
創建和訪問操作
- 創建空表,創建空表時,需要分配一塊元素存儲,記錄表的容量並將元素計數值設置為0。
- 簡單判斷操作。表空或者表滿都是O(1)。
- 訪問給定下標i的元素。O(1)。計算地址,訪問元素。
- 遍歷操作。O(n)。
- 查找給定元素d的(第一次出現的)位置,這種操作稱為檢索或者查找。在沒有其他信息的情況下,只能通過用d與表中元素逐個比較的方式實現檢索,稱為線性檢索。O(n)。
- 查找給定元素d在位置k之后的第一次出現的位置。與上面操作類似。O(n)。
最后幾個操作都需要檢查表元素的內容,屬於基於內容的檢索。數據存儲和檢索是一切計算和信息處理的基礎。
不修改表結構的操作只有兩種模式,一種是直接訪問,一種是基於一個整型變量,按照下標循環並檢查和處理(也就是變量操作)。
變動操作:加入元素

尾端加入新數據項。把新數據項存入表中的第一個空位,即下標num的位置。如果這個時候表滿了,操作就失敗。顯然這個操作是O(1)。
新數據存入元素存儲區的第i個單元。這是一般情況。
首先需要下標是否合法,要把新數據存入這里,又不能簡單拋棄原有的數據,就必須把該項數據移走。
移走方式有兩種:
- 不需要保持原有的序列,那么只需要把原來處於i位置的元素移到最后面,然后把新元素放到i元素的位置。這種操作能在O(1)時間完成。
- 如果要保持原有的序列,就需要不斷把i位置之后的元素逐一后移。這種操作最壞和平均復雜度都是O(n)。python采用這種方法。
變動操作:刪除元素

尾端刪除數據。將元素計數值num-1,就相當把表尾的元素刪除了。O(1)。
刪除位置id數據。這個跟加入跟加入操作類似,也是分兩種情況。
- 如果沒有保序要求,就追吧num-1位置的元素拷貝過去,覆蓋掉原來的元素。
- 如果有保序要求,就需要刪除原來的元素之后,逐一上移其余元素。顯然非保序定位刪除操作的時間復雜度是O(1)。而保序定位刪除的復雜度是O(n),因為其中可能會移動一系列元素。
基於條件的刪除
這種刪除操作並不是給定被刪元素的位置,而是給出需要刪除的數據項本身。或是一個滿足刪除的條件。這個顯然跟檢索有關,需要先找到元素,再刪除它。
順序表及其操作的性質
順序表的各種訪問操作,如果其執行中不需要掃描表內容的全部或者一部分,其時間復雜度都是O(1),需要掃描表內容操作時間復雜度都是O(n)。
順序表的優點:
- 可以O(1)時間的按位置訪問元素。
- 元素在表里的存儲緊湊,只需要O(1)空間存放少量輔助信息。
缺點:
- 如果表很大,即需要很大片的連續內存空間。
- 內存一旦確定大小,就不能變化,導致內存利用不合理。
- 在執行一般加入和刪除操作的時候,通常需要移動元素,效率低。
- 在建表的初始就需要考慮存儲區大小。
3、順序表的結構
一個順序表的完整信息包括兩部分,一部分是表中的元素集合,另一部分是為實現正確操作而需要記錄的信息(也就是表中的個數和大小等信息)。
兩種基本實現方式

一體式實現,有關信息跟表元素放在一起。
- 優點:比較緊湊,有利於管理。
- 缺點:不同的表對象大小不一。還有就是創建之后元素的存儲區就固定了。
分離式實現,在表對象里只保存與整個表有關的信息,實際元素存放在另外一個獨立的元素存儲區對象里,通過鏈接與基本表對象關聯。
- 優點:表對象大小統一,不同對象關聯不同大小的元素存儲區。
- 缺點:表的創建和管理,必須考慮多個獨立對象。
替換元素存儲區
分離式實現的最大優點是可以在標識不變的情況下,為表對象更換一塊元素存儲區。
操作流程:
- 另外申請一塊更大的元素存儲區。
- 把表中已有的元素復制到新存儲區。
- 用新的元素存儲區替代原來的元素存儲區(改變表對象的元素區鏈接)
- 實際加入新的元素。
人們把利用采用這種技術實現的表稱之為動態順序表。
后端插入和存儲區擴充
把一個動態順序表從0逐漸擴大到n,如果是前端添加,那么整個就是O(n2)復雜度。
如果是從后端添加,一次操作是O(1)的復雜度,不過這里要考慮一個問題,就是元素的替換問題。當整個表滿的時候,就會申請一個新的內存復制已有的元素,這個操作是O(m)復雜度,m是當時表中元素的個數。
那么就有一個問題,如果安排初始表中元素的數量,以及它的擴展策略?
很明顯,如果一次擴展的多,會造成內存空間的浪費,而如果擴展的不多,那么就會頻繁地進行復制替換操作,造成性能下降。
下面用兩種不同的擴展策略來說明一下這個問題。
- 線性增長策略。即每次擴展固定數目的元素。假設每次替換增加10個元素。那么,從0到1000總的復制次數是10+20+....+990 = 49500 = 1/20*n的平方,這樣即使是單次操作的尾端添加O(1)也會讓整個過程變成O(n的平方)。原因就是頻繁的替換元素存儲區,而且隨着元素變多,每次替換復制的元素也越來越多。因此,應該考慮一種策略是隨着元素數量的增加,替換存儲區的頻率不斷的降低。
- 加倍操作。每次擴展原元素數據的一倍。假設表元素從0到1024,那么表增長過程中復制元素的次數是1+2+4+8+....+512=1023=log2(下標)1024 -1。這樣在整個表增長的過程中,元素的賦值次數也就是O(n)。每次插入操作的平均復雜度是O(1)。
當然,后一個策略也有缺點,比如當元素很多的時候,一次擴展的空間就很大,這樣如果用不完,就會造成空間浪費。解決方法,可以兩種策略一起使用,另外如果對於高要求的程序,需要增加一個設定容量的操作,當下面的操作需要高要求的時候,就事前把表的容量修改到一個足夠大的值,保證在關鍵計算的時候不會出現替換操作。
4、python中list
list的基本實現技術
python中的list就是一種元素個數可變的線性表,並且添加刪除元素后維持原來的順序。
- 基於下標的高效位置訪問和更新。O(1)。
- 允許任意添加元素,不會出現表滿的情況,在添加的過程中對象的標識不變。(分離式結構的動態順序表)。
- 表中的元素保存在一塊連續的存儲區內。
- 使用加倍策略。初始的時候是8,然后一次替換加4倍。當到達50000的時候,變成加倍策略。
一些主要操作的性質
- len()。O(1)
- 元素訪問和賦值。O(1)
- 一般位置添加,刪除,切片,extend,pop()非尾端刪除,都是O(n)操作。
python的一個問題是沒有提供list檢查容量的操作,也沒有設置容量的操作,所有關於容量的操作都由python解釋器自動完成。
- 優點:降低編程人員的負擔。
- 缺點:限制了表的使用方式。
其他幾個操作
- clear()。O(1)。可以另外分配一個空表,然后更改存儲區,等待解釋器回收內存塊。也可以將表的計數值設置為0,但是這種並沒有釋放內存。
- reverse()。看下面代碼,很容易得出他是O(n)操作。
- sort()。O(nlogn)。具體參考后面章節的排序。
def reverse(self): elems = self.elems i, j = 0, len(elems)-1 while i < j: elems[i], elems[j] = elems[j], elems[i] i, j = i+1, j-1
三、鏈接表
1、實現鏈接表的基本思路
實現線性表的另外一種方式就是鏈接表,它用鏈接關系顯示表示元素之間的順序關系。這種表也稱為鏈表。
基本思路:
* 把表中元素分別存儲在一批獨立的存儲塊(表的結點)中。
* 保證從組成表結構的任一結點可找到與其對應的下一個結點。
* 在前面一個結點里面顯示記錄下一個結點的鏈接。
這樣只要找到了第一個結點,就能找到所有的結點。
2、實現單鏈表的操作分析
單鏈表的結點是一個二元組,它的表元素域elem保存着作為表元素的數據項(或者數據項的關聯信息),鏈接域next保存同一個表里下一個結點的標識。

一個單鏈表不僅要有結點,還需要一個表頭指針,這樣就可以通過這個指針找到所有表中的節點。這個表頭指針最開始指向的是表頭節點。
- 一個單鏈表由一些具體的表結點構成。
- 每個結點是一個對象,有自己的標識。
- 結點之間通過結點鏈接建立起單向的順序。
為了表示一個鏈接的結束,只需要給表的最后結點的鏈接域設置一個None值。
class LNode: def __init__(self, elem, next_=None): self.elem = elem self.next = next_ #為了不與python的next重名
基本鏈表操作
- 創建空鏈表。只需把表頭指針設置為空鏈接,也就是None就可以了。
- 刪除鏈接表。在C里,需要通過明確的操作釋放一個個節點所用的存儲。在python里,只需要把表指針指向None,python解釋器就會回收表結點中不用的存儲。
- 判斷表是否為空。表指針是否為None。
- 判斷表是否滿。一般鏈表不會滿,除非存儲空間用完。
加入操作
表首端插入

- 創建一個新結點,並存入數據。
- 將新結點的引用域(next)設置為原表頭。
- 將表頭指針指向新的結點。
可以看出這只需要幾個賦值操作,因此復雜度是O(1)。
一般情況的插入

- 創建一個新結點q,並存入數據。
- 找到要插入結點的前一個結點pre。這時pre的下一個結點是pre.next
- 先把新結點q的引用域設置為p以前的下一個結點也即是pre.next。再把pre的引用域(next)設置為新結點q。
這個操作主要是第2步需要找到要插入結點的前一個結點,需要遍歷整個鏈表。因此整個復雜度是O(n)。
刪除元素
刪除表首元素
讓表頭指針指向表的第二個元素就可以了。python內存管理器會自動回收表頭元素。也就是,self.head = self.head.next。
一般情況的元素刪除

一般情況的刪除跟一般情況的添加一樣,需要找到相關元素。刪除的時候,先找到要刪除的前一個元素p,然后把它的引用(next)設置為要刪除元素的后一個元素。也就是pre.next = pre.next.next
掃描、定位和遍歷
在上面不論是一般刪除還是一般添加,都需要找到要操作元素的前一個結點。由於單鏈表只有一個方向,因此,從表頭指針開始,做一個循環,就能找到整個鏈表中的所有結點。這個過程稱為鏈表掃描。
p = head # 掃描指針 while p is not None and 其他條件: 對p中的數據做操作 p = p.next
p = head while p is not None and i > 0: i -= 1 p = p.next
p = head while p is not None: if elemt == p.elem: return p = p.next
鏈表操作的復雜度
- 創建空表O(1)
- 刪除表O(1)
- 判斷空表O(1)
- 加入元素,首端加入O(1),一般加入O(n)。
- 刪除表元素,刪除表頭O(1),一般刪除O(n)。
表的長度

看表的設計,如果設計表時,有表長度len的參數,那么刪除,添加元素必須維持這個參數。這時表的長度就是O(1)。只需獲得屬性值就可以。
如果沒有這個參數,每次就長度,就需要遍歷鏈表。O(n)的復雜度。
p = head i = 0 while p is not None: p = p.next i += 1
3、單鏈表的實現
class LinkedListUnderFlow(ValueError): ''' 自定義異常類,為了更快定位是鏈表中的異常,從而進行處理。 ''' pass
class LNode: ''' 鏈表中的節點類 ''' def __init__(self, elem, next_=None): self.elem = elem self.next = next_
class LList: ''' 鏈表的實現 ''' def __init__(self): slef.head = None # 表頭指針 def empty(self): ''' 判斷表是否為空表 ''' return self.head is None def prepend(self, elem): ''' 在表首端添加元素 ''' self.head = LNode(elem, self.head) def pop(self): ''' 刪除表頭結點並返回刪除的元素 ''' if self.empty(): raise LinkedListUnderFlow('in pop') e = self.head.elem self.head = self.head.next return e def append(self, elem): ''' 在表尾添加元素 ''' if self.empty(): # 如果表是空的,必須設置表頭指針指向新元素 self.head = LNode(elem) return p = self.head # 掃描指針 while p.next is not None: # 找到表中的最后一個元素p。 p = p.next p.next = LNode(elem) def pop_last(self): ''' 刪除表中的最后一個元素,並返回其所刪除的元素 ''' if self.empty(): raise LinkedListUnderFlow('in pop_last') if self.head.next is None: # 表中只有一個元素 e = self.head.elem self.head = None else: p = self.head while p.next.next is not None: # 找到要刪除元素的前一個元素 p = p.next e = p.next.elem p.next = None return e def find(self, pred): ''' 滿足條件pred的第一個元素 ''' p = self.head while p is not None if pred(p.elem): return p.elem p = p.next def print_all(self): ''' 打印鏈表中所有的元素,以,分隔開 ''' p = self.head while p is not None: print(p.elem, end='') if p.next is not None: print(',', end='') p = p.next print('') # 只是為了輸出一個換行符 def for_each(self, proc): ''' 對每個元素做一個指定的操作函數 ''' p = self.head while p is not None: proc(p.elem) p = p.next def elements(self): ''' 遍歷這個鏈表,使用迭代器 ''' p = self.head while p is not None: yiel p.elem p = p.next def filter(self, pred): ''' 滿足條件pred的所有元素 ''' p = self.head while p is not None: if pred(p.elem): yiel p.elem p = p.next
四、鏈表的變形和操作
1、單鏈表的簡單變形
前面的單鏈表有一個缺點,如果要從尾端加入元素的話,必須要先找到最后一個元素,這樣復雜度就成為了O(n)。

可以通過給表增加一個尾結點的引用域,也就是尾指針,這樣就可以在O(1)的時間找到尾元素,從而達到O(1)的復雜度。
新的表除了尾部添加功能,跟尾部刪除功能,以及與尾指針有關的操作不一樣外,其他的操作跟單鏈表一樣,因此,可以選擇繼承這個單鏈表。
class LList1(LList): def __init__(self): super().__init__() self.rear = None def prepend(self, elem): ''' 首端添加,注意,當為空表的時候,必須設置尾指針。 ''' if self.empty(): self.head = LNode(elem) self.rear = self.head return self.head = LNode(elem, self.head) def append(self, elem): ''' 尾端添加,可以達到O(1)的復雜度,因為不用循環表去找最后一個結點了。通過尾指針就可以直接找到。這個設計跟前面求表長的設計一樣,都是通過一個維持一個屬性值的正確性,來減少遍歷的操作 ''' if self.empty(): self.head = LNode(elem) self.rear = self.head return self.rear.next = LNode(elem) #將新結點放入尾結點的next引用域,使之與鏈表串起來 self.rear = self.rear.next #將尾指針指向鏈表 def pop_last(self): ''' 尾端刪除,還是O(n)的復雜度,因為尾指針只能找到最后一個結點,而尾端刪除需要找到最后一個結點的前一個結點。因此還需要循環一遍表,唯一與前面不同的地方是,刪除的時候需要維護尾指針。 ''' if self.empty(): raise LinkedListUnderFlow('in pop_last') if self.head.next is None: e = self.head.elem self.head = self.rear = None # 這一步也可以直接寫成self.head = None,因為判斷表是否為空是用首指針來判斷的。只要手指針為空就確定這個表沒有元素了。 else: p = self.head while p.next.next is not None: p = p.next e = p.next.elem p.next = None slef.rear = p return e
首端刪除跟前面單鏈表是一樣的,只要首指針指向None就說明表空,因此在首端刪除的時候,並不需要維護尾指針。
表設計的內在一致性
就是在一個類內部,如果其他條件不變,設計原則盡量保持統一。
def prepend(self, elem): ''' 這是從首端添加的另一個版本,雖然代碼肯定正確,而且代碼量還少,但是它有一個不好的地方,就是判斷表空的時候使用的尾指針,這就違背了表設計的內置一致性的原則。不值得推薦。因為在這個類的其他方法中,檢查表空都是用的首指針。 ''' self.head = LNode(elem, self.head) if self.rear is None: # 判斷是空表 self.rear = self.head
2、循環單鏈表
單鏈表的另外一個變形是循環單鏈表,其中最后一個結點的next域不是指向None,而是指向表的第一個結點。這樣就只需要一個表尾指針就可以了。

循環鏈表與普通鏈表的不同之處就是在於表循環結束的控制條件。其他,就是尾指針的維護,大體思路跟鏈表是一樣的。
class LCList: def __init__(self): self.rear = None def empty(self): return self.rear is None def prepend(self, elem): ''' 前端插入元素 ''' if self.empty(): self.rear = LNode(elem) self.rear.next = self.rear # 建立一個結點的自循環 else: self.rear.next = LNode(elem, self.rear.next) def append(self, elem): ''' 后端插入元素 ''' self.prepend(elem) self.rear = self.rear.next def pop(self): ''' 前端刪除元素 ''' if self.empty(): raise LinkedListUnderFlow('in LCList pop') if self.rear.next is sel.rear: e = self.rear.elem self.rear = None else: e = self.rear.next.elem # 前端的元素 self.rear.next = self.rear.next.next return e def print_all(self): ''' 打印整個循環鏈表 ''' if self.empty(): return p = self.rear.next while p is not self.rear: print(p.elem, end=',') p = p.next print(p.elem)
3、雙鏈表
單鏈表只能支持一個方向的掃描和逐步操作,因此它不能達到O(1)的尾部刪除時間復雜度。如果希望兩端插入和刪除操作都能高效完成,就需要兩個方向都能掃描,這樣要在結點中增加一個向前的引用域。然后,通過尾指針向前一步,就很容易找到這個要刪除尾結點的前一個元素。

class DLNode(LNode): def __init__(self, elem, prev=None, next_=None) super().__init__(elem, next_) self.prev = prev class DLList(LList1): ''' 繼承帶有尾指針的單鏈表 ''' def __init__(self): super().__init__() def prepend(self, elem): ''' 首端添加 需要維護結點的引用域prev,因此需要改寫 ''' if self.empty(): self.head = DLNode(elem) self.rear = self.head else: self.head.prev = DLNode(elem, next_=self.head) #將原來首結點的prev域設置為新結點。並將新結點的next域設置為原來的首結點 self.head = self.head.prev #將首指針指向新結點 def append(self, elem): ''' 尾端添加 ''' if self.empty(): self.head = DLNode(elem) self.rear = self.head else: self.rear.next = DLNode(elem, prev=self.rear) self.rear = self.rear.next def pop(self): ''' 首端刪除 ''' if self.empty(): raise LinkedListUnderFlow('in DLList pop') e = self.head.elem if self.head.next is None: self.head = None else: self.head.next.prev = None # 將第二個元素的prev引用域改為None self.head = self.head.next # 將首指針指向第二個元素 return e def pop_last(self): ''' 這個是O(1)操作,是經過了改良之后有着好性能的方法 ''' if self.empty(): raise LinkedListUnderFlow('in DLList pop_last') e = self.rear.elem # 最后一個結點元素 if self.head.next is None: self.head = None else: self.rear.prev.next = None # 將最后一個結點的前一個元素的next引用域設置為None。 self.rear = self.rear.prev return e
循環雙鏈表
雙鏈表也可以定義為循環鏈表。如下圖

4、兩個鏈表操作
鏈表反轉
先回憶list的reverse,用兩個下標,通過逐對交換元素位置,直到兩個下標碰頭,來完成反轉操作。
同樣的思路也可以用在雙鏈表上。
def reverse(self): ''' 雙鏈表反轉的方法,采用搬動元素的思路。 ''' i = self.head j = self.rear while i.prev is not j: # 當它的i超過j的時候終止循環,完成反轉 i.elem, j.elem = j.elem, i.elem i = i.next j = j.prev
上面的反轉雙鏈表的思路跟反轉list的思路是一樣的,都是搬動結點的元素。這種思路應用在單鏈表上也可以完成,但是復雜度O(n2)比較高,因為沒有從后向前的方向指針。
因此,這里考慮另外一種反轉鏈表的思路,那就是更改鏈表的引用域。通過改變結點的鏈接順序來改變表元素的順序。
基本思路:從一個表的首端不斷取下結點,將其加入另一個表的首端,這樣就完成了一個反轉過程。
def reverse(self): ''' 單鏈表反轉的方法,采用更改引用域的思路。O(n)的復雜度 ''' if self.empty(): return p = None while self.head is not None: q = slef.head # 不斷拿到首結點 self.head = q.next # 將首指針往后移動 q.next = p # 更改它的next域,使之指向下一個拿到的首結點 p = q self.head = p
鏈表排序
先對list的插入排序了解一下,后面有詳細介紹。也可以直接看第9章排序的插入排序部分。
def insert_sort(lst): for i in range(1, len(lst)): tmp = lst[i] # 找到無序區的第一個元素,把它插入有序區合適的地方 j = i while j > 0 and lst[j-1] > tmp: #如果超出下標或者找到要插入的位置,循環結束 lst[j] = lst[j-1] # 將比較大的元素逐一后移 j -= 1 lst[j] = tmp # 將元素插入找的那個位置 return lst
使用插入排序的思路和移動鏈表元素的思路對鏈表進行排序。與順序表的插入排序不同是,它尋找要插入的位置不同。順序表是從右往左移動尋找,而單鏈表由於只有一個方向,從左往右移動,因此它是從左往右移動去尋找。當找到位置之后,再用一個臨時變量保序原位置的elem。以便后來循環插入使用。
def sort1(self): ''' 使用插入排序移動元素的思路對單鏈表進行排序 ''' if slef.empty(): return p = self.head.next while p is not None: e = p.elem # 無序區的第一個元素 q = self.head while q is not p and q.elem <= e: # 從前往后找到要插入元素的節點位置 q = q.next while q is not p: # 找到位置,倒騰元素逐一后移 tmp = q.elem # 將q位置的元素先存起來 q.elem = e # 然后將e位置元素放到q位置 e = tmp # 將e的元素更換為以前q位置的元素,以備下次循環替換 q = q.next q.elem = e # 將最后一個元素回填 p = p.next
第二種單鏈表插入排序的實現方法,調整鏈表的引用域來完成。
def sort2(self): ''' 使用插入排序移動元素的思路對單鏈表進行排序 ''' p = self.head if p is None or p.next is None: return rem = p.next # 取得無序區的第一個元素 p.next = None # 將首元素的next引用域設置為None,因為反轉之后它就變成最后一個元素了。 while rem is not None: p = self.head q = None while p is not None and p.elem <= rem.elem: q = p p = p.next if q is None: self.head = rem else: q.next = rem q = rem rem = rem.next p.next = p
五、課后部分編程練習
# 給鏈表添加本章開始定義的線性抽象數據類型中沒有的操作。 class LList: def __len__(self): p = self._head e = 0 while p is not None: e += 1 p = p.next return e def insert(self, elem, i): if i < 0 or i > len(self): raise LinkedListUnderFlow elif i == 0: self.prepend(elem) else: p = self._head while p is not None and i > 1: i -= 1 p = p.next p.next = LNode(elem, p.next) def delt(self, i): if i < 0 or i >= len(self) or self._head is None: raise LinkedListUnderFlow elif i == 0: self.pop() else: p = self._head while p is not None and i > 1: i -= 1 p = p.next p.next = p.next.next def search(self, elem): p = self._head e = 0 while p is not None: if p.elem == elem: return e e += 1 p = p.next return -1
# 請為Llist類增加定位插入和刪除操作。 # 答案:見上面第一題
""" 給Llist增加一個元素計數值域num,並修改類中操作,維護這個計數值。 另外定義有一個求表中元素個數的len函數。 請比較這種實現和原來沒有元素計數值域的實現, 個說明其優缺點。 """ #答案: #這個實現很簡單,就是在 def __init__(self): self._head = None self._num = 0 #初始化計數值為0。 #然后在進行加入元素操作的時候,比如prepend,append,insert等方法中,令self._num += 1 #在刪除元素操作的時候,比如pop,pop_last,delt等方法中,令self._num -= 1 #最后寫一個len方法。 def len(self): return self._num #這樣做的好處是可以在O(1)時間內得到鏈表的個數,而原來是O(n)時間。 #因此代碼執行效率大大提高了。 #但是它的缺點是要實時維護一個計數變量num。
""" 課后練習第四題 請基於元素相等操作'=='定義一個單鏈表的相等比較函數。 另請基於字典序的概念,為鏈接表定義大於、小於、大於等於和小於等於判斷。 """ class LList: def __init__(self): self._head = None # == def __eq__(self, other): if not isinstance(other, LList): raise TypeError p, q = self._head, other.head() while (p is not None) and (q is not None): if p.elem == q.elem: p, q = p.next, q.next else: return False if (p and q) is not None: return False return True # < def __lt__(self, other): if not isinstance(other, LList): raise TypeError p, q = self._head, other.head() while (p is not None) and (q is not None): if p.elem < q.elem: return True elif p.elem == q.elem: p, q = p.next, q.next else: return False if (p is None) and (q is not None): return True return False # > def __gt__(self, other): if not isinstance(other, LList): raise TypeError p, q = self._head, other.head() while (p is not None) and (q is not None): if p.elem > q.elem: return True elif p.elem == q.elem: p, q = p.next, q.next else: return False if (p is not None) and (q is None): return True return False # <= def __le__(self, other): if not isinstance(other, LList): raise TypeError p, q = self._head, other.head() while (p is not None) and (q is not None): if p.elem < q.elem: return True elif p.elem == q.elem: p, q = p.next, q.next else: return False if (p is not None) and (q is None): return False return True # >= def __ge__(self, other): if not isinstance(other, LList): raise TypeError p, q = self._head, other.head() while (p is not None) and (q is not None): if p.elem > q.elem: return True elif p.elem == q.elem: p, q = p.next, q.next else: return False if (p is None) and (q is not None): return False return True
""" 請為鏈接表定義一個方法,它基於順序表參數構造一個鏈接表, 另請定義一個函數,它從一個鏈接表構造出一個順序表 """ def list_llist(self, mlist): for i in range(len(mlist)-1, -1, -1): self.prepend(mlist[i]) def llist_list(self): return [i for i in self.elements()]
""" 請為單鏈表類增加一個反向遍歷方法rev_visit(self, op),它能從后向前的順序把操作op逐個作用於表元素。你定義的方法在整個遍歷中訪問點的次數與表長度n是什么關系? 如果不是線性關系,請設法修改,使之達到線性關系。這里要求遍歷方法的空間代價是O(1) 提示: 你可以考慮為了遍歷而修改表的結構,只要能在遍歷的最后將表的結構復原。 """ def rev_visit(self, op): self.reverse() self.for_each(op) self.reverse()
""" 請為單鏈表類定義下面幾個元素刪除方法,並保持其他元素的相對順序 1、del_minimal() 刪除當時鏈表中的最小元素 2、del_if(pred) 刪除當前鏈表里所有滿足謂詞函數pred的元素 3、del_duplicate()刪除表中所有重復出現的元素。也就是說,表中任何元素的第一次出現保留不動, 后續與之相等的元素都刪除 要求所有的操作,復雜度均為O(n) """ def del_minimal(self): if self._head is None: raise LinkedListUnderFlow p = self._head min_num = p.elem while p.next is not None: if min_num > p.next.elem: min_num = p.next.elem p = p.next p = self._head q = None while p is not None: if not q and p.elem == min_num: self._head = self._head.next q = self._head elif p.elem == min_num: q.next = p.next if p.next is None: return else: q = p p = p.next def del_if(self, pred): """" 這種方法比較復雜,不能達到O(n)的復雜度要求 """ # p = self._head # index, i = [],0 # while p is not None: # if pred(p.elem): # index.append(i) # p = p.next # i += 1 # # print(index) # for j in range(len(index)-1,-1,-1): # self.delt(index[j]) # return index p = self._head q = None while p is not None: if not q and pred(p.elem): self._head = self._head.next q = self._head elif pred(p.elem): q.next = p.next if p.next is None: return else: q = p p = p.next def del_duplicate(self): p = self._head mlist = [] q = None while p is not None: if p.elem not in mlist: mlist.append(p.elem) q = p else: q.next = p.next if p.next is None: return p = p.next
