數據結構--線性表和鏈表的基礎知識

近期準備重新學習一下常用數據結構和基本算法,並計劃將這些內容的只是做一個整理和歸類,準備慢慢寫一個常用數據結構與基本算法的系列博文,博文列表參見:常用數據結構與基本算法博文系列,目前內容還比較少,後續慢慢補充。本文主要內容是介紹 數據結構--線性表和鏈表的基礎知識。

一 線性表概述

1.1 線性表概念

線性表,全名爲線性存儲結構。使用線性表存儲數據的方式可以這樣理解,即“把所有數據用一根線兒串起來,再存儲到物理空間中”。

如上圖所示,這是一組具有“一對一”關係的數據,我們接下來採用線性表將其儲存到物理空間中。首先,用“一根線兒”把它們按照順序“串”起來,如圖下圖 所示:

1.2 線性表存儲結構

上圖中,左側是“串”起來的數據,右側是空閒的物理空間。把這“一串兒”數據放置到物理空間,我們可以選擇以下兩種方式,如下圖所示。

圖 3a) 是多數人想到的存儲方式,而圖 3b) 卻少有人想到。我們知道,數據存儲的成功與否,取決於是否能將數據完整地復原成它本來的樣子。如果把圖 3a) 和圖 3b) 線的一頭扯起,你會發現數據的位置依舊沒有發生改變(和圖 1 一樣)。因此可以認定,這兩種存儲方式都是正確的。上圖中我們可以看出,線性表存儲數據可細分爲以下 2 種:

  1. 如圖 3a) 所示,將數據依次存儲在連續的整塊物理空間中,這種存儲結構稱爲順序存儲結構(簡稱順序表);
  2. 如圖 3b) 所示,數據分散的存儲在物理空間中,通過一根線保存着它們之間的邏輯關係,這種存儲結構稱爲鏈式存儲結構(簡稱鏈表);

將具有“一對一”關係的數據“線性”地存儲到物理空間中,這種存儲結構就稱爲線性存儲結構(簡稱線性表),線性表存儲結構可細分爲順序存儲結構和鏈式存儲結構。使用線性表存儲的數據,如同向數組中存儲數據那樣,要求數據類型必須一致,也就是說,線性表存儲的數據,要麼全不都是整形,要麼全部都是字符串。一半是整形,另一半是字符串的一組數據無法使用線性表存儲。(有一些弱類型編程語言(如python、MATLAB等)中的數組或者列表中元素的數據類型可以不一致,這都是在我們今天講的基礎上進行擴展的)。

1.3 前驅和後繼

數據結構中,一組數據中的每個個體被稱爲“數據元素”(簡稱“元素”)。例如,圖 1 顯示的這組數據,其中 1、2、3、4 和 5 都是這組數據中的一個元素。另外,對於具有“一對一”邏輯關係的數據,我們一直在用“某一元素的左側(前邊)或右側(後邊)”這樣不專業的詞,其實線性表中有更準確的術語:

  • 某一元素的左側相鄰元素稱爲“直接前驅”,位於此元素左側的所有元素都統稱爲“前驅元素”;
  • 某一元素的右側相鄰元素稱爲“直接後繼”,位於此元素右側的所有元素都統稱爲“後繼元素”;

以圖 1 數據中的元素 3 來說,它的直接前驅是 2 ,此元素的前驅元素有 2 個,分別是 1 和 2;同理,此元素的直接後繼是 4 ,後繼元素也有 2 個,分別是 4 和 5。如下圖4 所示: 

二 順序表

2.1 順序表的概念

 順序表,全名順序存儲結構,是線性表的一種。在前面的內容中我們提到,線性表用於存儲邏輯關係爲“一對一”的數據,順序表自然也不例外。不僅如此,順序表對數據的物理存儲結構也有要求。順序表存儲數據時,會提前申請一整塊足夠大小的物理空間,然後將數據依次存儲起來,存儲時做到數據元素之間不留一絲縫隙。例如,使用順序表存儲集合 {1,2,3,4,5},數據最終的存儲狀態如下圖所示:


由此我們可以得出,將“具有 '一對一' 邏輯關係的數據按照次序連續存儲到一整塊物理空間上”的存儲結構就是順序存儲結構。通過觀察上圖中數據的存儲狀態,我們可以發現,順序表存儲數據同數組非常接近。其實,順序表存儲數據使用的就是數組

 2.2 順序表的基本操作

 順序表的基本操作包括順序表的初始化、插入元素、刪除元素、更改元素、查找元素等。這些基本操作的很簡單,就不一一解釋了,具體代碼後續加上。

三 鏈表

3.1 鏈表的概念

鏈表,別名鏈式存儲結構或單鏈表,用於存儲邏輯關係爲 "一對一" 的數據。與順序表不同,鏈表不限制數據的物理存儲狀態,換句話說,使用鏈表存儲的數據元素,其物理存儲位置是隨機的例如,使用鏈表存儲 {1,2,3},數據的物理存儲狀態如下圖所示:

我們看到,上圖根本無法體現出各數據之間的邏輯關係。對此,鏈表的解決方案是,每個數據元素在存儲時都配備一個指針,用於指向自己的直接後繼元素。如下圖所示,數據元素隨機存儲,並通過指針表示數據之間邏輯關係的存儲結構就是鏈式存儲結構。

各數據元素配備指針

3.2 鏈表的節點

從上圖可以看到,鏈表中每個數據的存儲都由以下兩部分組成,即鏈表中存儲各數據元素的結構如下圖所示:

  1. 數據元素本身,其所在的區域稱爲數據域;
  2. 指向直接後繼元素的指針,所在的區域稱爲指針域;

上圖所示的結構在鏈表中稱爲節點。也就是說,鏈表實際存儲的是一個一個的節點,真正的數據元素包含在這些節點中,如下圖所示:

 

3.3 鏈表的組成要素

其實,上圖所示的鏈表結構並不完整。一個完整的鏈表需要由以下幾部分構成:
  1. 頭指針:一個普通的指針,它的特點是永遠指向鏈表第一個節點的位置。很明顯,頭指針用於指明鏈表的位置,便於後期找到鏈表並使用表中的數據;
  2. 節點:鏈表中的節點又細分爲頭節點、首元節點和其他節點:
    • 頭節點:其實就是一個不存任何數據的空節點,通常作爲鏈表的第一個節點。對於鏈表來說,頭節點不是必須的,它的作用只是爲了方便解決某些實際問題;
    • 首元節點:由於頭節點(也就是空節點)的緣故,鏈表中稱第一個存有數據的節點爲首元節點。首元節點只是對鏈表中第一個存有數據節點的一個稱謂,沒有實際意義;
    • 其他節點:鏈表中其他的節點;
因此,一個存儲 {1,2,3} 的完整鏈表結構如下圖 所示:

注意:鏈表中有頭節點時,頭指針指向頭節點;反之,若鏈表中沒有頭節點,則頭指針指向首元節點。

3.4 鏈表的分類

鏈表根據其前驅、後繼的索引特徵可以分爲單向鏈表、雙向鏈表和循環鏈表三類。
單向鏈表:表中各節點中都只包含一個指針(遊標),且都統一指向直接後繼節點,通常稱這類鏈表爲單向鏈表(或單鏈表)。單鏈表的數據塊和鏈表形式就是我們前面介紹的。
雙向鏈表:表中各各節點之間的邏輯關係是雙向的,節點中都包含兩個指針,一個指向直接後繼節點、一個指向直接前驅節點,通常稱這類鏈表爲雙向鏈表(或雙鏈表)。雙向鏈表中各節點包含以下 3 部分信息(如下圖所示):
  1. 指針域:用於指向當前節點的直接前驅節點;
  2. 數據域:用於存儲數據元素。
  3. 指針域:用於指向當前節點的直接後繼節點;

循環鏈表:表中頭尾節點相連,構成一個環。需要注意的是,雖然循環鏈表成環狀,但本質上還是鏈表,因此在循環鏈表中,依然能夠找到頭指針和首元節點等。循環鏈表和普通鏈表相比,唯一的不同就是循環鏈表首尾相連,其他都完全一樣。循環鏈表又可以分爲單向循環鏈表和雙向循環鏈表。

3.5 鏈表的基本操作

鏈表的基本操作與順序表一樣,包括鏈表的初始化、插入元素、刪除元素、更改元素、查找元素等。這些基本操作的含義很簡單,就不一一解釋了,具體代碼後續加上。

3.5.1 單鏈表的基本操作

插入元素操作:向單鏈表中增添元素,根據添加位置不同,可分爲以下 3 種情況:

  • 插入到鏈表的頭部(頭節點之後),作爲首元節點;
  • 插入到鏈表中間的某個位置;
  • 插入到鏈表的最末端,作爲鏈表中最後一個數據元素;

雖然新元素的插入位置不固定,但是鏈表插入元素的思想是固定的,只需做以下兩步操作,即可將新元素插入到指定的位置:

  1. 將新結點的 next 指針指向插入位置後的結點;
  2. 將插入位置前結點的 next 指針指向插入結點;

例如,我們在鏈表 {1,2,3,4} 的基礎上分別實現在頭部、中間部位、尾部插入新元素 5,其實現過程如下圖所示:

 

刪除元素操作:從鏈表中刪除指定數據元素時,實則就是將存有該數據元素的節點從鏈表中摘除,但作爲一名合格的程序員,要對存儲空間負責,對不再利用的存儲空間要及時釋放。因此,從鏈表中刪除數據元素需要進行以下 2 步操作:

  1. 將結點從鏈表中摘下來;
  2. 手動釋放掉結點,回收被結點佔用的存儲空間;

例如,從存有 {1,2,3,4} 的鏈表中刪除元素 3,則此代碼的執行效果如下圖所示:

3.5.2 雙鏈表的基本操作

插入元素操作:雙向鏈表的插入元素的操作與單向鏈表基本一樣,也是分爲三種情況,只是在插入元素時需要考慮前驅和後繼兩個指針的變化。

 

  • 添加至表頭:

  • 添加到中間位置:

  • 添加到末尾:

刪除元素操作:雙鏈表刪除結點時,也是跟單向鏈表一樣,只需遍歷鏈表找到要刪除的結點,然後將該節點從表中摘除即可。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章