一、什么是单链表
单链表(Singly Linked List) 是一种线性数据结构,与数组类似,但它通过指针连接节点而不是连续的内存存储。每一个节点只能找到下一个节点,无法回退到上一个节点。
每个节点(Node)包含两部分:
- 数据域(data):存储节点的数据。
- 指针域(next):存储下一个节点的地址。
最后一个节点的 next 指针为 nullptr,表示链表结束。
单链表的示意图:

1.1 单链表的节点插入
#include <iostream>
#include <stdlib.h>
#include <time.h>
#include <string.h>
// 节点类型
struct Node {
int data_;
Node* next_;
Node(int data = 0) :data_(data),next_(nullptr) {}
};
// 单链表代码实现
class Clink {
public:
Clink() {
// 给head_初始化指向头节点
head_ = new Node();
}
~Clink() {
// 节点的释放
}
public:
// 链表的尾插法(特点:尾节点的地址域为nullptr) O(n)
void InsertTail(int val) {
// 先找到当前链表的末尾节点
Node* p = head_;
while (p->next_ != nullptr) {
// 没有找到尾节点,继续
p = p->next_;
}
// 生成新节点
Node* node = new Node(val);
// 把新节点挂在尾节点的后面
p->next_ = node;
}
// 链表的头插法 O(1)
void InsertHead(int val) {
Node* node = new Node(val);
node->next_ = head_->next_;
head_->next_ = node;
}
// 链表打印
void Show() {
Node* p = head_->next_;
while (p != nullptr) {
std::cout << p->data_ << " ";
p = p->next_;
}
std::cout << std::endl;
}
private:
Node* head_; // 指向链表的头节点
};
int main() {
Clink link;
srand(time(0));
for (int i = 0; i < 10; i++) {
int val = rand() % 100;
link.InsertTail(val);
std::cout << val << " ";
}
std::cout << std::endl;
link.Show();
}
输出结果:

1.2 单链表的节点删除
// 链表节点的删除
void Remove(int val) {
Node* q = head_;
Node* p = head_->next_;
while (p != nullptr) {
if (p->data_ == val) {
q->next_ = p->next_;
delete p;
return;
} else {
q = p;
p = p->next_;
}
}
}
1.3 单链表的节点释放
// 释放节点
void freeNode() {
Node* p = head_;
while (p != nullptr) {
head_ = head_->next_;
delete p;
p = head_;
}
}
1.4 单链表的搜索
// 搜索 O(n)
bool Find(int val) {
Node* p = head_->next_;
while (p != nullptr) {
if (p->data_ == val) {
return true;
} else {
p = p->next_;
}
}
return false;
}
1.5 单链表的逆序
头插法(Head Insertion Method)是理解单链表反转最直观、最形象的一种方式。它不依赖复杂的指针反转逻辑,而是逐个节点“插到新表头”前面。
操作步骤
假设原链表如下:
head → [1] → [2] → [3] → [4] → nullptr
我们准备一个空的新链表 newHead = nullptr,然后逐个把原链表的节点摘下来,插入到新链表的头部。
第 1 步
取出节点 [1]:
newHead = nullptr
head = [1] → [2] → [3] → [4]
操作:
node = head // node 指向 [1]
head = head->next // head 指向 [2]
node->next = newHead // [1]->next = nullptr
newHead = node // newHead = [1]
结果:
newHead → [1] → nullptr
head → [2] → [3] → [4]
第 2 步
取出节点 [2]:
node = head // node = [2]
head = head->next // head = [3]
node->next = newHead // [2]->next = [1]
newHead = node // newHead = [2]
结果:
newHead → [2] → [1] → nullptr
head → [3] → [4]
第 3 步
取出 [3] 插入头部:
newHead → [3] → [2] → [1] → nullptr
head → [4]
第 4 步
取出 [4] 插入头部:
newHead → [4] → [3] → [2] → [1] → nullptr
head → nullptr
循环结束,反转完成!
代码实现:
void printList(ListNode* head) {
while (head) {
cout << head->val;
if (head->next) cout << " -> ";
head = head->next;
}
cout << endl;
}
ListNode* reverseListInsert(ListNode* head) {
ListNode* newHead = nullptr;
while (head) {
ListNode* node = head;
head = head->next;
node->next = newHead;
newHead = node;
}
return newHead;
}
int main() {
// 构建链表 1->2->3->4->5
ListNode* head = new ListNode(1);
head->next = new ListNode(2);
head->next->next = new ListNode(3);
head->next->next->next = new ListNode(4);
head->next->next->next->next = new ListNode(5);
cout << "原链表: ";
printList(head);
head = reverseListInsert(head);
cout << "反转后: ";
printList(head);
}
输出结果:

带头结点的单链表在头部有一个不存放有效数据的节点(即 head),它的作用是方便统一插入、删除操作。
代码示例:
#include <iostream>
using namespace std;
struct ListNode {
int data;
ListNode* next;
ListNode(int d = 0) : data(d), next(nullptr) {}
};
// 头插法逆序(带头节点)
void ReverseList(ListNode* head) {
ListNode* p = head->next; // 当前节点
head->next = nullptr; // 断开原链表
while (p != nullptr) {
ListNode* next = p->next; // 暂存后继节点
p->next = head->next; // 插入到头部
head->next = p; // 更新头指针
p = next; // 处理下一个
}
}
// 打印链表
void PrintList(ListNode* head) {
ListNode* p = head->next;
while (p) {
cout << p->data << " ";
p = p->next;
}
cout << endl;
}
int main() {
// 构建带头节点的链表 head->1->2->3->4
ListNode* head = new ListNode();
ListNode* tail = head;
for (int i = 1; i <= 4; ++i) {
tail->next = new ListNode(i);
tail = tail->next;
}
cout << "原链表: ";
PrintList(head);
ReverseList(head);
cout << "逆序后: ";
PrintList(head);
return 0;
}
1.6 输出单链表倒数第k个节点
要找到单链表的倒数第 k 个节点,可以使用双指针(快慢指针) 技巧:
- 定义两个指针:fast 和 slow,都指向链表的头节点;
- 先让 fast 向前移动 k 步;
- 然后同时移动 fast 和 slow,直到 fast 到达链表末尾;
- 此时,slow 指向的节点就是倒数第 k 个节点。
假设链表如下:

我们要求输出倒数第 k = 2 个节点,即节点 [4]。
步骤一:初始化指针

将指针fast和slow同时指向头节点head
步骤二:让 fast 先走 k 步(k=2)
第 1 步:
slow指针保持不变,fase指针先向前走2步

第 2 步:
第一步结束之后,slow指针和fase指针同时向前走一步

从上述图中可以看出此时fast指针比slow指针多走了两步
步骤三:同时移动 fast 和 slow
每次 fast 和 slow 同时前进 1 步:



当 fast == nullptr 时,slow 就正好指向了 倒数第 2 个节点 [4]。
代码实现:
#include <iostream>
using namespace std;
struct ListNode {
int val;
ListNode* next;
ListNode(int x) : val(x), next(nullptr) {}
};
// 获取倒数第k个节点
ListNode* getKthFromEnd(ListNode* head, int k) {
if (!head || k <= 0) return nullptr;
ListNode* fast = head;
ListNode* slow = head;
// fast先走k步
for (int i = 0; i < k; ++i) {
if (!fast) return nullptr; // 链表长度小于k
fast = fast->next;
}
// 同时移动 fast 和 slow
while (fast) {
fast = fast->next;
slow = slow->next;
}
return slow;
}
int main() {
// 构建链表 1->2->3->4->5
ListNode* head = new ListNode(1);
head->next = new ListNode(2);
head->next->next = new ListNode(3);
head->next->next->next = new ListNode(4);
head->next->next->next->next = new ListNode(5);
int k = 2;
ListNode* node = getKthFromEnd(head, k);
if (node)
cout << "倒数第 " << k << " 个节点的值是: " << node->val << endl;
else
cout << "链表长度小于 " << k << endl;
return 0;
}
输出结果:

1684

被折叠的 条评论
为什么被折叠?



