這裡我們只介紹線性表中 儲存結構不同的 順序表 和 連結串列,以及操作受限的 棧 和 佇列 。
資料結構與演算法系列文章 資料結構與演算法 - 時間複雜度 資料結構與演算法 - 線性表
目錄
一、 概述 二、順序表 2.1、 插入元素 2.2、 刪除元素 2.3、 特點 三、連結串列 3.1、線性連結串列(單連結串列) 3.1.1、插入元素 3.1.2、刪除元素 3.2、迴圈連結串列(單向) 3.3、雙向連結串列 3.4、雙向迴圈連結串列 3.5、連結串列的特點 四、棧 4.1、順序棧 4.2、鏈棧 五、佇列 5.1、順序佇列 5.2、鏈佇列
一、概述
線性表 是具有線性結構特點、最簡單且最常用的一種資料結構。 線性表 ( Linear List) 是具有相同特性的資料元素組成的一個有限序列。其元素可以是整數、字元等簡單資料,也可以是由多個資料項組成的複合形式,甚至可以是一頁書或其他更復雜的資訊。 例如,由26個大寫英文字母組成的字母表(A,B,C,…,x,Y,Z)就是一個線性表,表中的每個資料元素均是一個大寫字母。再如,某高校1990年以來擁有的副教授以上職稱的教師個數(48,64,77,93,112,136,167,…,235)也是一個線性表,表中的每個資料元素均是一個正整數。這兩個線性表都是包含簡單資料元素的例子。 線性表 中的資料元素可以是多種形式的,但是,對於同一個線性表,其資料元素必須具有同一種形式,也就是說,同一線性表中的資料元素必須同屬一個資料物件集合,表中相鄰的資料元素之間存在某種序偶關係。
線性結構 特點: 在資料元素的非空有限集合中,存在唯一的一個稱為“第一個”的資料元素(頭結點);存在唯一的一個“最後一個”的資料元素(末結點)除第一個外,集合中的每個資料元素都只有一個直接前驅;除最後一個外,集合中的每個資料元素都只有一個直接後繼。
二、順序表
順序表 是指用一組地址連續的儲存單元依次儲存線性表中的資料元素的線性表 。 因為這種方式與高階程式設計語言中一維陣列的表示和實現相一致,所以也稱為陣列表示法。採用順序表表示的線性表,表中邏輯位置相鄰的資料元素將存放到儲存器中實體地址相鄰的儲存單元之中,表中元素的邏輯關係與儲存順序(物理關係)相符,換言之,順序表中資料元素的邏輯關係是以其在儲存結構中的物理(位置)關係來表示的。 因此,在順序表中,可以根據順序表中資料元素的位序,隨機訪問表中的任一元素,即順序表是一種隨機存取的儲存結構。
-
2.1、 順序表的插入
順序表的插入是指在順序表的第i-1個元素和第i個元素之間插入一個新的元素,此時順序表中插入位置前後元素之間的邏輯關係發生變化,因此除非插入位置是當前表中的最後一個元素之後,否則必須通過順序移動資料元素的儲存位置才能實現。插入過程示意圖如圖所示:
順序表的插入演算法時間耗費主要是元素的移動,移動元素的個數取決於插入位置。 最好情況,插入位置在順序表表尾,無須移動元素;最壞情況是在第一個位置插入,需要移動n個元素。在長度為n的順序表i位置前插入一個元素,需要移動n-i+1個元素,可以以有n+1個插入位置,在插入位置等概率條件下,插入一個元素的平均移動次數為1/(n+1)*∑(n-i+1)=n/2,因此演算法的時間複雜度為O(n)。 注意:∑(n-i+1)表示下標是i=1,上標是n+1的求和表示式。
-
2.2、順序表的刪除
順序表的刪除和插入的過程類似,需要移動刪除元素後面的所有元素儲存位置。過程如圖所示:
同插入演算法一樣,刪除演算法時間主要消耗在元素的移動。最好情況,刪除位置在順序表末尾,無須移動元素;最壞情況,刪除位置是第一個元素,需要移動n-1個元素。在長度為n的順序表的i位置刪除元素,需要移動n-i個元素,在刪除位置等概率條件下,刪除一個元素的平均移動次數為1/n*∑(n-i)=(n-1)/2,因此演算法的時間複雜度為O(n)。 注意:∑(n-i)表示下標是i=1,上標是n+1的求和表示式。
-
2.3、順序表的特點
順序表的特點 是以“儲存位置相鄰”表示兩個元素之間的前驅後繼關係。順序表的優點是可以隨機存取表中任意一個元素。其主要缺點一是容易產生儲存空間的浪費;二是每做一次插入或刪除操作時,平均來說必須移動表中一半元素。因此,順序表經常主要應用於為查詢而很少做插入和刪除操作、表長變化不大的線性表。
三、連結串列
所謂 連結串列 ,就是指採用鏈式儲存結構表示和實現的線性表。 連結串列的特點是採用一組任意的儲存單元來存放線性表中的資料元素,這些儲存單元可以是連續的,也可以是不連續的。 在連結串列中,資料元素之間的邏輯關係並不依賴其對應的儲存地址,而是通過設定專門用於指示資料元素之間邏輯關係的指標來描述。 因此,在連結串列中的每個資料元素是由用於存放代表其本身資訊的資料域和用於存放指示資料元素之間邏輯關係的指標域兩部分組成的,資料元素的這種特殊儲存方式,稱為 結點(Node) 。 根據連結串列結點中包含指標域的指標個數、指標指向和連線方式,可將連結串列分為線性連結串列、迴圈連結串列、雙向連結串列、多重連結串列、十字連結串列、二叉連結串列、鄰接表、鄰接多重表等,其中線性連結串列、迴圈連結串列和雙向連結串列用於實現線性表的鏈式儲存結構,其他形式則用於實現擴充套件線性結構(陣列和廣義表等)或非線性結構(樹、圖等)。
-
3.1、線性連結串列(單連結串列)
線性連結串列也稱 單連結串列 ,在每個結點中只包含一個指標,用於指示該結點的直接後繼結點,整個連結串列通過指標相連,最後一個節點因為沒有後集結點,其指標置為空NUll。結構示意圖如上圖所示。
3.1.1、單連結串列的插入
線性連結串列的插入是指線上性連結串列的第-1個結點和第i個結點之間插入一個新的結點,此時,線性連結串列中插入位置前後結點之間的邏輯關係發生變化,因此除非插入位置是當前表中的最後一個結點之後,否則必須通過同時修改插入位置之前結點和當前插入結點的指標指向才能實現。 線性連結串列的插入無須移動元素,插入演算法的基本步驟如下圖所示: (1)尋找插入位置,即第i-1個結點的位置。 (2)申請結點儲存空間,生成新結點。 (3)修改第-1個結點和新結點指標域,將新結點連結在連結串列中。
線性連結串列的插入演算法時間主要消耗在尋找插入位置上,需要從連結串列頭指標開始依次訪問結點,直到找到插入位置,因此演算法的時間複雜度為O(n)。
3.1.2、單連結串列的刪除
線性連結串列的刪除是指刪除線性連結串列的第i個結點,此時,線性連結串列中刪除位置前後結點之間的邏輯關係發生變化,因此必須通過修改刪除位置之前結點的指標指向才能實現。刪除過程示意圖,如上。 同插入結點一樣,刪除過程首先尋找要刪除結點的前一個結點位置,再通過修改結點指標域完成刪除操作。 因此演算法的時間複雜度為也O(n)。
-
3.2、迴圈連結串列(單向)
線上性連結串列中,最後一個結點的指標指向NULL,表示該線性連結串列的結束。如果把表尾結點的指標改為指向該連結串列的第一個結點,則整個線性連結串列構成一個閉合的迴路,稱這種頭尾相連的線性連結串列形式為 迴圈連結串列 。 整個連結串列就像一條單向環形的公交線路,人們可以從線路中的任一站點出發到達整個線路的其他站點。插入和刪除的時間複雜度也為O(n),迴圈連結串列儲存結構示意圖如下:
-
3.3、雙向連結串列
如果線上性連結串列的結點中增加一個指標域,用來指向結點的直接前驅,則從表中的任一結點出發,既可以向後查詢結點的後繼,也可以向前查詢結點的前驅。整個連結串列包含分別指向前驅和後繼的兩條鏈,稱為 雙向連結串列 。雙向連結串列的儲存結構示意如圖:
-
3.4、雙向迴圈連結串列
使雙向連結串列中的兩條鏈均構成閉合迴路,則形成 雙向迴圈連結串列 。雙向迴圈連結串列結構示意圖以及插入和刪除過程示意圖如下:
-
3.5、連結串列的特點
連結串列的特點 是以“指標”指示其後繼元素,連結串列中的元素可以儲存在任意一組儲存單元中。因此,連結串列的優點是無須預先估計線性表的大小,節省儲存開銷,且做插入、刪除操作時,無須移動元素;其主要缺點是無論連結串列做插入、刪除還是查詢,均需要從連結串列的起始位置開始,連結串列不具備隨機存取的特性。
四、棧
棧 是一種操作受限的線性表,上面提到的順序表和連結串列可以在表的兩端和表內進行插入刪除操作,而 棧僅允許在一端(棧頂)進行插入刪除操作 ,也就是進(入)棧和出棧操作,棧頂是棧讀取資料的唯一入口,操作遵循“ 先進後出(LIFO) ”的原則。例如Object-C中的導航控制器就是用棧的特性實現的。
根據儲存結構的不同,也可以把棧分為 順序棧 和 鏈棧 兩種儲存結構。
-
4.1、順序棧
棧的順序儲存也稱為順序棧,它利用一組地址連續的儲存單元依次存放自棧底到棧頂的元素,同時附設棧頂標識top來指示棧頂元素在順序棧中的位置。順序棧的入棧和出棧操作結構示意圖如圖所示:
-
4.2、鏈棧
棧的鏈式儲存也稱為鏈棧,它和連結串列的儲存原理一樣,都可以利用閒散空間來儲存元素,用指標來建立各結點之間的邏輯關係。鏈棧也會設定一個棧頂元素的識別符號top,稱為棧頂指標。鏈棧的入棧和出棧操作結構示意圖如圖所示
五、佇列
佇列 是也一種操作受限的線性表,但佇列和棧不同, 佇列只允許在一端(隊尾)進行插入(入隊)操作 ,在另一端(隊頭)進行刪除(出隊)操作,操作遵循的是“ 先進先出(FIFO) ”原則。 佇列中會有一個指標指向隊頭,這個指標稱為 隊頭指標front 。當有元素出隊時,隊頭指標向後移動,指向下一個元素,下一個元素成為新的隊頭元素(類似於棧的棧頂指標);佇列中也會有一個指標指向隊尾,稱為 隊尾指標rear ,隊尾指標是指向最後一個元素之後的一個空指標。當有元素需要入隊時,就插入到隊尾指標所指位置處,插入之後,隊尾指標向後移動,指向下一個空位。當佇列已滿時,元素不能再入隊;同理,當佇列為空,無法執行出隊操作。
根據儲存結構的不同,也可以把佇列分為 順序佇列 和 鏈式佇列 兩種儲存結構。
-
5.1、順序佇列
使用順序表實現的佇列稱作 順序佇列 。順序佇列的實現和順序表的實現相似,只是在順序佇列中只允許在一端進行插入,在另一端進行刪除。定義兩個變數 front與rear分別標識隊頭與隊尾,當刪除隊頭元素時, front後移到下一個位置;當插入新元素時,在rear指示的位置插入,插入後,rear向後移動指向下一個儲存位置。順序佇列的出入隊操作示意圖如下:
-
注意 :佇列的“假溢位”
在順序佇列的儲存過程中,可能出現“溢位”現象,佇列的“溢位”有兩種情況,一種真“溢位”,另一種為假“溢位“。 所謂 真“溢位” 是指當佇列分配的空間已滿,此時再往裡儲存元素則會出現“溢位”,這種“溢位”是真的再無空間來儲存元素,是真“溢位”;而 假“溢位” 是指佇列尚有空間而出現的“溢位”情況。當 front端有元素出隊時, front向後移動;當rear端有元素入隊時,rear向後移動,若rear已指到佇列中下標最大的位置,此時雖然 front前面有空間,但再有元素入隊也會出現“溢位”,這種“溢位”叫作“假溢位”,示意圖如下。 解決“假溢位”有兩種方法。一是採用“移動佇列”的方法,即每當執行一次出隊操作,則依次將隊頭和隊尾指標向陣列的起始位置移動,始終保持隊頭在陣列的起始位置,這種方法的代價是產生大量的元素移動,顯然不是一個好方法;另一種方法就更合理高效了,就是採用5.2迴圈佇列。
-
5.2、迴圈佇列
為了解決順序佇列中的假“溢位”現象,充分利用陣列的儲存空間,可以將順序佇列的頭尾相連,構成一個 迴圈佇列,迴圈佇列一般都是用陣列來實現的。將迴圈佇列假想為一個環狀的空間,如下圖所示。 在迴圈佇列中, front與rear都是可以迴圈移動的,當隊空時, front=rear成立;當隊滿時, front=rear也成立。因此顯然不能只憑 front=rear來判斷隊空還是隊滿。 為了解決這個問題,在迴圈佇列中有一個約定:少用一個元素空間,當隊尾標識的rear在隊頭標識front的上一個位置時,佇列為滿。此時,判斷隊空和隊滿的條件分別如下: 隊空時: front==rear為真。 隊滿時:(rear+1)% MAXSIZE== front為真 ( MAXSIZE是佇列容量的大小)。 迴圈佇列中ront和rear的移動不再是簡單的加1,因為是迴圈的,可能原本指在末尾,前進一個單位就是又一個迴圈的開始,所以每次移動都要對佇列容量 MAXSIZE取模:front = (front +1)% MAXSIZE,rear = (rear+1)% MAXSIZE。 在迴圈佇列中,求佇列的長度也不僅僅是rear與ront相減這麼簡單,因為,rear的值有可能比front小,這樣的相減結果是負值,顯然不對。在求迴圈佇列的長度時,都是用rear加上佇列容量,減去 front的值後,再對容量取模:(rear+ MAXSIZE- front)% MAXSIZE。
-
5.3、鏈式佇列
用連結串列來實現的佇列也稱為 鏈式佇列 ,在鏈式佇列中也用指標 front與rear分別指示隊頭與隊尾,在隊頭 front處刪除元素,在隊尾rear處插入元素。與順序佇列不同,鏈式佇列的rear指標指向最後一個元素。鏈式佇列的結構和出入隊操作示意圖如下:
注:文中的圖片均轉摘自網路