線性表


閱讀目錄

  • 一、線性表的概念和表抽象數據類型
  • 二、順序表的實現
  • 三、鏈接表
  • 四、鏈表的變形和操作
  • 五、課后部分編程練習(初學時寫的,僅供參考)

一、線性表的概念和表抽象數據類型

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、順序表的結構

一個順序表的完整信息包括兩部分,一部分是表中的元素集合,另一部分是為實現正確操作而需要記錄的信息(也就是表中的個數和大小等信息)。

兩種基本實現方式

一體式實現,有關信息跟表元素放在一起。

  • 優點:比較緊湊,有利於管理。
  • 缺點:不同的表對象大小不一。還有就是創建之后元素的存儲區就固定了。

分離式實現,在表對象里只保存與整個表有關的信息,實際元素存放在另外一個獨立的元素存儲區對象里,通過鏈接與基本表對象關聯。

  • 優點:表對象大小統一,不同對象關聯不同大小的元素存儲區。
  • 缺點:表的創建和管理,必須考慮多個獨立對象。

替換元素存儲區

分離式實現的最大優點是可以在標識不變的情況下,為表對象更換一塊元素存儲區。

操作流程:

  1. 另外申請一塊更大的元素存儲區。
  2. 把表中已有的元素復制到新存儲區。
  3. 用新的元素存儲區替代原來的元素存儲區(改變表對象的元素區鏈接)
  4. 實際加入新的元素。

人們把利用采用這種技術實現的表稱之為動態順序表。

后端插入和存儲區擴充

把一個動態順序表從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
list的反轉

三、鏈接表

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。
  • 判斷表是否滿。一般鏈表不會滿,除非存儲空間用完。

加入操作

表首端插入

  1. 創建一個新結點,並存入數據。
  2. 將新結點的引用域(next)設置為原表頭。
  3. 將表頭指針指向新的結點。

可以看出這只需要幾個賦值操作,因此復雜度是O(1)。

一般情況的插入

  1.  創建一個新結點q,並存入數據。
  2.  找到要插入結點的前一個結點pre。這時pre的下一個結點是pre.next
  3. 先把新結點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
list的插入排序

使用插入排序的思路和移動鏈表元素的思路對鏈表進行排序。與順序表的插入排序不同是,它尋找要插入的位置不同。順序表是從右往左移動尋找,而單鏈表由於只有一個方向,從左往右移動,因此它是從左往右移動去尋找。當找到位置之后,再用一個臨時變量保序原位置的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
課后第七題

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM