一、線性表介紹
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就是函式指標變數