一個程式設計師對計算機底層原理的理解,決定了他職業生涯的上限。
圖片
單連結串列是面試中頻繁考察的重點之一。
今天,我們就來深入剖析單連結串列,涵蓋其定義、特點、優缺點、適用場景,以及一系列高頻演算法題(增刪改查、翻轉、找倒數第k個節點、判斷是否有環等)的解法,並附上C語言原始碼,讓你輕鬆掌握這一關鍵知識點。
單連結串列的定義
單連結串列是一種線性資料結構,由一系列節點組成,每個節點包含資料域和指向下一個節點的指標。這種結構透過指標將各個節點連線起來,形成一條鏈。
單連結串列的特點
動態性:連結串列長度不固定,可隨需求增加或減少節點。
節點不連續:連結串列節點在記憶體中無需連續儲存,有助於記憶體利用。
單連結串列的優缺點
優點
插入和刪除操作高效,特別是在連結串列頭部或中間位置。
適用於元素數量不固定的動態場景。
缺點
查詢效率低,無法直接透過索引訪問元素,需從頭節點順序遍歷。
單連結串列的使用場景
連結串列是非常重要的資料結構,應用場景廣泛。
適用於實現棧、佇列、樹等複雜資料結構。
在需要頻繁插入和刪除操作的場景中表現出色,如動態資料集合、快取淘汰策略等。
相較於陣列,連結串列在處理元素動態變化的場景時更具優勢;但在頻繁查詢元素的場景中,陣列更為合適。
單連結串列因其動態性和靈活性,在處理元素不固定且需要頻繁插入、刪除的場景中具有重要的應用價值。
程式碼實現
一、資料結構定義
// 定義單連結串列節點結構體
typedef struct ListNode {
int val;
struct ListNode* next;
} ListNode;
// 定義單連結串列結構體(包含頭節點)
typedef struct LinkedList {
ListNode* head;
} LinkedList;
// 建立一個新節點
ListNode* createNode(int val) {
ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
newNode->val = val;
newNode->next = NULL;
return newNode;
}
// 建立一個空連結串列
LinkedList* createLinkedList() {
LinkedList* list = (LinkedList*)malloc(sizeof(LinkedList));
list->head = NULL;
return list;
}
二、增刪改查操作
插入操作
- 頭部插入
// 在連結串列頭部插入新節點
void insertAtHead(LinkedList* list, int val) {
ListNode* newNode = createNode(val);
newNode->next = list->head;
list->head = newNode;
}
- 中間插入
// 在指定位置(假設位置從1開始計數)之後插入新節點
void insertAfterPosition(LinkedList* list, int position, int val) {
ListNode* newNode = createNode(val);
ListNode* current = list->head;
int count = 1;
while (current!= NULL && count < position) {
current = current->next;
count++;
}
if (current!= NULL) {
newNode->next = current->next;
current->next = newNode;
} else {
printf("Position out of range.\n");
}
}
- 尾部插入
// 在連結串列尾部插入新節點
void insertAtTail(LinkedList* list, int val) {
ListNode* newNode = createNode(val);
if (list->head == NULL) {
list->head = newNode;
return;
}
ListNode* current = list->head;
while (current->next!= NULL) {
current = current->next;
}
current->next = newNode;
}
刪除操作
- 刪除頭部節點
// 刪除連結串列頭部節點
void deleteHead(LinkedList* list) {
if (list->head!= NULL) {
ListNode* temp = list->head;
list->head = list->head->next;
free(temp);
} else {
printf("List is empty.\n");
}
}
- 刪除中間節點
// 刪除指定位置(假設位置從1開始計數)的節點
void deleteAtPosition(LinkedList* list, int position) {
ListNode* current = list->head;
ListNode* prev = NULL;
int count = 1;
while (current!= NULL && count < position) {
prev = current;
current = current->next;
count++;
}
if (current!= NULL) {
if (prev == NULL) {
list->head = current->next;
} else {
prev->next = current->next;
}
free(current);
} else {
printf("Position out of range.\n");
}
}
- 刪除尾部節點
// 刪除連結串列尾部節點
void deleteTail(LinkedList* list) {
if (list->head == NULL) {
printf("List is empty.\n");
return;
}
ListNode* current = list->head;
ListNode* prev = NULL;
while (current->next!= NULL) {
prev = current;
current = current->next;
}
if (prev == NULL) {
list->head = NULL;
} else {
prev->next = NULL;
}
free(current);
}
查詢操作
// 查詢連結串列中是否存在指定值
int searchList(LinkedList* list, int val) {
ListNode* current = list->head;
while (current!= NULL) {
if (current->val == val) {
return 1;
}
current = current->next;
}
return 0;
}
修改操作
// 修改指定位置(假設位置從1開始計數)節點的值
void modifyAtPosition(LinkedList* list, int position, int newVal) {
ListNode* current = list->head;
int count = 1;
while (current!= NULL && count < position) {
current = current->next;
count++;
}
if (current!= NULL) {
current->val = newVal;
} else {
printf("Position out of range.\n");
}
}
三、演算法進階
翻轉連結串列
// 翻轉單連結串列
ListNode* reverseList(LinkedList* list) {
ListNode* prev = NULL;
ListNode* current = list->head;
ListNode* next = NULL;
while (current!= NULL) {
next = current->next;
current->next = prev;
prev = current;
current = next;
}
list->head = prev;
return list->head;
}
找連結串列的倒數第 k 個節點
// 找到單連結串列的倒數第 k 個節點
ListNode* findKthFromEnd(LinkedList* list, int k) {
ListNode* slow = list->head;
ListNode* fast = list->head;
// 讓快指標先走 k 步
for (int i = 0; i < k; i++) {
if (fast == NULL) {
printf("k is larger than the length of the list.\n");
return NULL;
}
fast = fast->next;
}
// 然後慢指標和快指標一起走,當快指標走到連結串列末尾時,慢指標就是倒數第 k 個節點
while (fast!= NULL) {
slow = slow->next;
fast = fast->next;
}
return slow;
}
判斷單連結串列是否有環
// 判斷單連結串列是否有環
int hasCycle(LinkedList* list) {
ListNode* slow = list->head;
ListNode* fast = list->head;
while (fast!= NULL && fast->next!= NULL) {
slow = slow->next;
fast = fast->next->next;
if (slow == fast) {
return 1; // 有環
}
}
return 0; // 無環
}
希望這篇文章能對你有所幫助,記得收藏起來哦,方便隨時查閱和複習。