W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
內(nèi)存空間是所有程序的公共資源,在一個復(fù)雜的系統(tǒng)運行環(huán)境下,空閑的內(nèi)存空間可能散落在內(nèi)存各處。我們知道,存儲數(shù)組的內(nèi)存空間必須是連續(xù)的,而當數(shù)組非常大時,內(nèi)存可能無法提供如此大的連續(xù)空間。此時鏈表的靈活性優(yōu)勢就體現(xiàn)出來了。
「鏈表 linked list」是一種線性數(shù)據(jù)結(jié)構(gòu),其中的每個元素都是一個節(jié)點對象,各個節(jié)點通過“引用”相連接。引用記錄了下一個節(jié)點的內(nèi)存地址,通過它可以從當前節(jié)點訪問到下一個節(jié)點。
鏈表的設(shè)計使得各個節(jié)點可以被分散存儲在內(nèi)存各處,它們的內(nèi)存地址是無須連續(xù)的。
圖 4-5 鏈表定義與存儲方式
觀察圖 4-5 ,鏈表的組成單位是「節(jié)點 node」對象。每個節(jié)點都包含兩項數(shù)據(jù):節(jié)點的“值”和指向下一節(jié)點的“引用”。
如以下代碼所示,鏈表節(jié)點 ListNode 除了包含值,還需額外保存一個引用(指針)。因此在相同數(shù)據(jù)量下,鏈表比數(shù)組占用更多的內(nèi)存空間。
/* 鏈表節(jié)點結(jié)構(gòu)體 */
struct ListNode {
int val; // 節(jié)點值
ListNode *next; // 指向下一節(jié)點的指針
ListNode(int x) : val(x), next(nullptr) {} // 構(gòu)造函數(shù)
};
建立鏈表分為兩步,第一步是初始化各個節(jié)點對象,第二步是構(gòu)建引用指向關(guān)系。初始化完成后,我們就可以從鏈表的頭節(jié)點出發(fā),通過引用指向 next 依次訪問所有節(jié)點。
linked_list.cpp
/* 初始化鏈表 1 -> 3 -> 2 -> 5 -> 4 */
// 初始化各個節(jié)點
ListNode* n0 = new ListNode(1);
ListNode* n1 = new ListNode(3);
ListNode* n2 = new ListNode(2);
ListNode* n3 = new ListNode(5);
ListNode* n4 = new ListNode(4);
// 構(gòu)建引用指向
n0->next = n1;
n1->next = n2;
n2->next = n3;
n3->next = n4;
數(shù)組整體是一個變量,比如數(shù)組 nums
包含元素 nums[0]
和 nums[1]
等,而鏈表是由多個獨立的節(jié)點對象組成的。我們通常將頭節(jié)點當作鏈表的代稱,比如以上代碼中的鏈表可被記做鏈表 n0
。
在鏈表中插入節(jié)點非常容易。如圖 4-6 所示,假設(shè)我們想在相鄰的兩個節(jié)點 n0 和 n1 之間插入一個新節(jié)點 P ,則只需要改變兩個節(jié)點引用(指針)即可,時間復(fù)雜度為 O(1) 。
相比之下,在數(shù)組中插入元素的時間復(fù)雜度為 O(n) ,在大數(shù)據(jù)量下的效率較低。
圖 4-6 鏈表插入節(jié)點示例
linked_list.cpp
/* 在鏈表的節(jié)點 n0 之后插入節(jié)點 P */
void insert(ListNode *n0, ListNode *P) {
ListNode *n1 = n0->next;
P->next = n1;
n0->next = P;
}
如圖 4-7 所示,在鏈表中刪除節(jié)點也非常方便,只需改變一個節(jié)點的引用(指針)即可。
請注意,盡管在刪除操作完成后節(jié)點 P 仍然指向 n1 ,但實際上遍歷此鏈表已經(jīng)無法訪問到 P ,這意味著 P 已經(jīng)不再屬于該鏈表了。
圖 4-7 鏈表刪除節(jié)點
linked_list.cpp
/* 刪除鏈表的節(jié)點 n0 之后的首個節(jié)點 */
void remove(ListNode *n0) {
if (n0->next == nullptr)
return;
// n0 -> P -> n1
ListNode *P = n0->next;
ListNode *n1 = P->next;
n0->next = n1;
// 釋放內(nèi)存
delete P;
}
在鏈表訪問節(jié)點的效率較低。如上節(jié)所述,我們可以在 O(1) 時間下訪問數(shù)組中的任意元素。鏈表則不然,程序需要從頭節(jié)點出發(fā),逐個向后遍歷,直至找到目標節(jié)點。也就是說,訪問鏈表的第 i 個節(jié)點需要循環(huán) i?1 輪,時間復(fù)雜度為 O(n) 。
linked_list.cpp
/* 訪問鏈表中索引為 index 的節(jié)點 */
ListNode *access(ListNode *head, int index) {
for (int i = 0; i < index; i++) {
if (head == nullptr)
return nullptr;
head = head->next;
}
return head;
}
遍歷鏈表,查找鏈表內(nèi)值為 ?target
? 的節(jié)點,輸出節(jié)點在鏈表中的索引。此過程也屬于線性查找。
linked_list.cpp
/* 在鏈表中查找值為 target 的首個節(jié)點 */
int find(ListNode *head, int target) {
int index = 0;
while (head != nullptr) {
if (head->val == target)
return index;
head = head->next;
index++;
}
return -1;
}
表 4-1 總結(jié)對比了數(shù)組和鏈表的各項特點與操作效率。由于它們采用兩種相反的存儲策略,因此各種性質(zhì)和操作效率也呈現(xiàn)對立的特點。
表 4-1 數(shù)組與鏈表的效率對比
數(shù)組 | 鏈表 | |
---|---|---|
存儲方式 | 連續(xù)內(nèi)存空間 | 離散內(nèi)存空間 |
緩存局部性 | 友好 | 不友好 |
容量擴展 | 長度不可變 | 可靈活擴展 |
內(nèi)存效率 | 占用內(nèi)存少、浪費部分空間 | 占用內(nèi)存多 |
訪問元素 | ||
添加元素 | ||
刪除元素 |
如圖 4-8 所示,常見的鏈表類型包括三種。
/* 雙向鏈表節(jié)點結(jié)構(gòu)體 */
struct ListNode {
int val; // 節(jié)點值
ListNode *next; // 指向后繼節(jié)點的指針
ListNode *prev; // 指向前驅(qū)節(jié)點的指針
ListNode(int x) : val(x), next(nullptr), prev(nullptr) {} // 構(gòu)造函數(shù)
};
圖 4-8 常見鏈表種類
單向鏈表通常用于實現(xiàn)棧、隊列、哈希表和圖等數(shù)據(jù)結(jié)構(gòu)。
雙向鏈表常被用于需要快速查找前一個和下一個元素的場景。
循環(huán)鏈表常被用于需要周期性操作的場景,比如操作系統(tǒng)的資源調(diào)度。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: