資料結構-線性表、連結串列

sleeeeeping發表於2024-07-20

一、線性表介紹

1、線性結構

​ 在資料元素存在非空有限集中:

  • 存在唯一的一個被稱為“第一個”的資料元素
  • 存在唯一的一個被稱為“最後一個”的資料元素
  • 除了第一個外,集合中每個資料元素都只有一個前趨元素
  • 除了最後一個外,集合中每個資料元素都只有一個後繼元素

2、線性表

線性表是一個有n個資料元素的有限序列,同一個線性表中的元素必定有相同特性,元素之間存在序偶關係

線性表中的元素個數\(n(n \leq 0)\)定義為該表的長度,當\(n = 0\)時稱為空表,非空表中每個資料元素都有一個確定的位置(下標)

線性表是一個相當靈活的資料結構,它的長度可以根據需要增長或縮短

二、線性表的順序的表示和儲存:

​ 線性表的儲存使用一組連續記憶體來依次儲存線性表中的資料元素。

注意:1、要時刻保持元素之間的連續性、2、千萬不要越界

優點:1、支援隨機訪問 2、查詢、修改、排序效率比較高 3、大塊的連續記憶體不容易產生記憶體碎片

缺點:1、對元素插入、刪除時效率很低 2、大塊記憶體對記憶體要求較高

三、線性表的鏈式表示和儲存:

​ 鏈式儲存結構不要求記憶體位置物理上是連續的,因此元素可以儲存在記憶體的任何位置(可以是連續的,也可以不連續)

​ 元素a[i]和a[i+1]之間的邏輯關係不是依靠相互位置,而是在元素中增加一個一個指向其後繼元素的資料項(元素指標),從而表示相互之間的邏輯關係,元素本身的資料+後繼元素的地址 組成了儲存映像,俗稱 節點(node)

typedef struct Node {
  	TYPE val;			//	資料域  
   	struct Node* next;	//	指標域
} Node;

若干個節點透過指標域依次連線起來,形成的線性表結構稱為鏈式表,簡稱連結串列,如果指標域中只有一個指向下一個節點的指標,這種連結串列稱為單向連結串列。

程式碼實現

單向連結串列

​ 單向連結串列中必須有一個指向第一個節點的指標,該指標稱為頭指標,被它指向的節點稱為頭節點

​ 頭節點可以儲存、也可以不儲存有效資料,如果不儲存有效資料的話,那麼頭節點只是單純地作為一個佔位節點存在

​ 最後一個節點稱為尾節點,尾節點的next指向空(NULL),作為結束標誌

1、不帶頭節點的單向連結串列

​ 定義:第一個節點中的資料域儲存有效資料。

​ 注意:當需要對單連結串列的頭指標發生修改時,例如頭新增、頭刪除、插入等操作,引數需要傳遞頭指標的地址(二級指標),處理相對麻煩

​ 注意:當進行刪除時,需要獲取到待刪除節點的前趨節點,但是如果刪除的位置剛好是第一個節點,它沒有前趨節點,所以需要額外判斷處理

程式碼實現

2、帶頭節點的單向連結串列

定義:第一個節點中的資料域不儲存有效資料,該頭節點只是用於指向第一個有效資料的節點而存在。

所以,由於頭節點不會因為新增、插入、刪除該改變,所以不需要傳遞二級指標

typedef struct List {
    ListNode* head;		//	永遠指向頭節點	必須要有
    ListNode* tail;		//	尾指標,可以有 也可以沒有
    size_t size;		//	數量 可以有 也可以沒有
} List;

注意:尾指標tail,能直接找到最後一個節點,但是在尾刪除操作時,發揮不了作用,因為要找尾節點的前趨。

四、靜態連結串列

  • 靜態連結串列的節點儲存在一段連續記憶體中,透過節點中稱為遊標的一個正整數來訪問後繼節點
typedef StaticNode {
	TYPE data;		//	資料域
	int index;	//	遊標
} StaticNode;
  • 在靜態連結串列中進行插入、刪除時,只需要修改遊標的值即可不需要複製記憶體,也能達到連結串列的效果
  • 但是也犧牲了隨機訪問節點的功能,而且連結串列的優點也有缺失。
  • 是給沒有指標的程式語言提供一種操作單連結串列的方式。

五、迴圈連結串列

  • 迴圈連結串列的最後一個節點的next不再指向NULL,而是指向頭節點。如果是單連結串列就稱為單迴圈連結串列
  • 好處是能夠透過任意節點可以遍歷整個連結串列

六、雙向連結串列(雙向迴圈連結串列)

  • 所謂的雙向連結串列就是連結串列節點中有兩個指標域,一個指向前一個節點,叫做前趨指標(prev),另一個指向後一個節點,稱為後繼指標(next)
  • 因此可以從後往前遍歷連結串列,對於要訪問連結串列後半部的節點的操作效率更高
//	雙向連結串列的節點結構
typedef struct ListNode {
    struct ListNode* prev;		//	前趨
    TYPE data;
    struct ListNode* next;		//	 後繼
} ListNode;
  • 在雙向連結串列的基礎上,讓最後一個節點的next指向頭節點,讓頭節點的prev指向最後一個節點,構成了雙向迴圈連結串列

七、Linux核心連結串列

  • 在普通的連結串列中,目前面臨無法做到任何型別的資料都可以儲存的問題。
  • Linux核心連結串列是一種通用的雙向迴圈連結串列,面對通用的問題,Linux核心連結串列的節點乾脆不儲存任何資料域,只有指標域,節點只負責串聯起每個節點,不負責儲存資料。
  • 如果要使用Linux核心連結串列時,把節點放入到資料中。
目的:根據結構體中某個成員的地址,能夠計算出所在結構體的首地址,從而使用者在設計Linux核心連結串列的節點時,不需要一定放在在資料的首位成員,增加可用性

//  計算出結構體type的成員mem所在的地址距離第一個成員位置的位元組數           
#define offsetof(type,mem) \
    ((unsigned long)(&(((type*)0)->mem)))


//  根據結構成員mem的地址(ptr) 計算出它所在結構(type)變數的首地址
//  ptr要計算的某個節點的地址  type是ptr所在結構體名 mem是它的結構成員名
#define node_to_obj(ptr,type,mem) \
        ((type*)((char*)ptr - offsetof(type,mem)))

八、通用連結串列

Linux核心連結串列雖然設計很巧妙,但是不利於初學者使用,另一種通用的設計思路是藉助void*的相容性,來設計一種連結串列,稱為通用連結串列,這種連結串列還需要藉助回撥函式。

//	定義了一個函式指標變數
返回值 (*函式指標變數名)(引數列表);
	void (*funcp)(int num1,int num2);
//	該函式指標的型別是
返回值 (*)(引數列表);	
	void (*)(int,int);
//	函式指標型別重定義
typedef 返回值 (*重定義後的型別名)(引數列表);
	typedef void (*fp)(int,int);
	fp 就是 void (*)(int,int)這個函式指標型別 可以用來定義該型別的函式指標變數
	fp p;	//	p就是函式指標變數

相關文章