單鏈表是一種常見的線性數據結構,由節點(Node)組成,每個節點包含數據域和指針域(指向下一個節點)。以下是單鏈表的核心操作實現,包括節點定義、鏈表類封裝、增刪查改、遍歷、銷燬等功能。
1. 完整代碼實現
cpp
運行
#include <iostream>
#include <stdexcept> // 用於異常處理
using namespace std;
// 定義鏈表節點結構體
template <typename T>
struct ListNode {
T data; // 數據域
ListNode<T>* next; // 指針域,指向下一個節點
// 節點構造函數
ListNode(T val) : data(val), next(nullptr) {}
};
// 單鏈表類封裝
template <typename T>
class SinglyLinkedList {
private:
ListNode<T>* head; // 頭節點指針(哨兵節點,不存儲數據)
int size; // 鏈表元素個數
public:
// 構造函數:初始化空鏈表
SinglyLinkedList() {
head = new ListNode<T>(T()); // 頭節點(哨兵),數據域默認值
size = 0;
}
// 析構函數:銷燬鏈表
~SinglyLinkedList() {
clear(); // 清空所有節點
delete head; // 釋放頭節點
head = nullptr;
}
// 1. 清空鏈表
void clear() {
ListNode<T>* curr = head->next; // 從第一個有效節點開始
while (curr != nullptr) {
ListNode<T>* temp = curr;
curr = curr->next;
delete temp; // 釋放當前節點
}
head->next = nullptr;
size = 0;
}
// 2. 在鏈表尾部添加元素
void push_back(T val) {
ListNode<T>* newNode = new ListNode<T>(val);
ListNode<T>* curr = head;
// 找到最後一個節點
while (curr->next != nullptr) {
curr = curr->next;
}
curr->next = newNode;
size++;
}
// 3. 在鏈表頭部添加元素
void push_front(T val) {
ListNode<T>* newNode = new ListNode<T>(val);
newNode->next = head->next; // 新節點指向原第一個節點
head->next = newNode; // 頭節點指向新節點
size++;
}
// 4. 刪除指定值的第一個節點
bool remove(T val) {
ListNode<T>* prev = head;
ListNode<T>* curr = head->next;
while (curr != nullptr) {
if (curr->data == val) {
prev->next = curr->next; // 跳過待刪除節點
delete curr; // 釋放內存
size--;
return true;
}
prev = curr;
curr = curr->next;
}
return false; // 未找到該值
}
// 5. 根據索引刪除節點(索引從0開始)
bool removeAt(int index) {
if (index < 0 || index >= size) {
throw out_of_range("Index out of bounds");
}
ListNode<T>* prev = head;
ListNode<T>* curr = head->next;
for (int i = 0; i < index; i++) {
prev = curr;
curr = curr->next;
}
prev->next = curr->next;
delete curr;
size--;
return true;
}
// 6. 查找元素是否存在
bool find(T val) const {
ListNode<T>* curr = head->next;
while (curr != nullptr) {
if (curr->data == val) {
return true;
}
curr = curr->next;
}
return false;
}
// 7. 獲取指定索引的元素(索引從0開始)
T get(int index) const {
if (index < 0 || index >= size) {
throw out_of_range("Index out of bounds");
}
ListNode<T>* curr = head->next;
for (int i = 0; i < index; i++) {
curr = curr->next;
}
return curr->data;
}
// 8. 修改指定索引的元素
bool set(int index, T val) {
if (index < 0 || index >= size) {
return false;
}
ListNode<T>* curr = head->next;
for (int i = 0; i < index; i++) {
curr = curr->next;
}
curr->data = val;
return true;
}
// 9. 獲取鏈表長度
int getSize() const {
return size;
}
// 10. 判斷鏈表是否為空
bool isEmpty() const {
return size == 0;
}
// 11. 遍歷打印鏈表
void print() const {
ListNode<T>* curr = head->next;
if (curr == nullptr) {
cout << "Linked list is empty" << endl;
return;
}
cout << "Linked list: ";
while (curr != nullptr) {
cout << curr->data << " -> ";
curr = curr->next;
}
cout << "nullptr" << endl;
}
};
// 測試代碼
int main() {
// 創建int類型的鏈表
SinglyLinkedList<int> list;
// 測試添加元素
list.push_back(10);
list.push_back(20);
list.push_front(5);
list.print(); // 輸出:5 -> 10 -> 20 -> nullptr
// 測試查找
cout << "Find 10: " << boolalpha << list.find(10) << endl; // true
cout << "Find 30: " << boolalpha << list.find(30) << endl; // false
// 測試獲取元素
cout << "Index 1: " << list.get(1) << endl; // 10
// 測試修改元素
list.set(1, 15);
list.print(); // 5 -> 15 -> 20 -> nullptr
// 測試刪除元素
list.remove(15);
list.print(); // 5 -> 20 -> nullptr
list.removeAt(1);
list.print(); // 5 -> nullptr
// 測試清空
list.clear();
list.print(); // Linked list is empty
// 測試邊界情況
try {
list.get(0); // 拋出異常
} catch (const out_of_range& e) {
cout << "Exception: " << e.what() << endl;
}
return 0;
}
2. 核心概念解析
(1)節點結構(ListNode)
data:存儲節點的實際數據,使用模板支持任意類型(int、string、自定義類型等)。next:指向鏈表中下一個節點的指針,尾節點的next為nullptr。
(2)頭節點(哨兵節點)
- 代碼中使用哨兵頭節點(不存儲有效數據),目的是統一空鏈表和非空鏈表的操作邏輯,避免處理
head為nullptr的邊界情況。 - 例如:空鏈表時
head->next = nullptr,而非空時head->next指向第一個有效節點。
(3)核心操作説明
|
操作
|
功能描述
|
時間複雜度
|
|
|
尾部添加元素
|
O(n)
|
|
|
頭部添加元素
|
O(1)
|
|
|
刪除指定值的第一個節點
|
O(n)
|
|
|
查找元素是否存在
|
O(n)
|
|
|
獲取 / 修改指定索引元素
|
O(n)
|
|
|
清空鏈表
|
O(n)
|
3. 關鍵注意事項
- 所有
new創建的節點必須在析構 / 刪除時用delete釋放,避免內存泄漏。 - 析構函數先調用
clear()釋放所有有效節點,再釋放頭節點。
- 操作索引時需檢查是否越界(
index < 0 || index >= size)。 - 空鏈表的遍歷 / 刪除操作需特殊處理,避免訪問
nullptr。
- 代碼使用模板
template <typename T>,支持任意數據類型(如SinglyLinkedList<string>、SinglyLinkedList<double>)。
- 從
head->next開始遍歷,直到curr == nullptr結束。
4. 擴展優化方向
- 尾指針優化:添加
tail指針,將push_back的時間複雜度從 O (n) 降為 O (1)。 - 雙向鏈表:擴展為雙向鏈表(每個節點增加
prev指針),支持反向遍歷和高效刪除指定節點。 - 迭代器封裝:實現迭代器(
iterator),支持 C++ 風格的範圍遍歷(for (auto val : list))。 - 異常安全:使用智能指針(
unique_ptr)替代裸指針,自動管理內存,避免手動delete。