线性表的链式存储方式为链表(Linked List)
链表中的每个元素结点都需要保存以下两部分信息。
- 存储数据信息的部分,称为数据域。
- 存储前驱(prev/prior)或后继(next)结点的逻辑关系,称为指针域。
根据元素结点中指针域存储的指针个数和类型的不同,链表还可细分为单向链表、双向链表、单向环形链表(单向循环链表)、双向环形链表(双向循环链表)以及静态链表。
单向链表
如果元素结点只包含一个指针域,则称该链表为单向链表(Singly Linked List)。单向链表的结点结构如图所示。

SinglyLinkedList.h实现单向链表
//
// 单向链表
//
#ifndef DS_SINGLYLINKEDLIST_H
#define DS_SINGLYLINKEDLIST_H
#include <iostream>
using namespace std;
template <class T>
//单向链表的节点结构
struct Node{
T data;//数据
Node* next;//指针,指向下一个节点的地址
Node(const T& value, Node* n = nullptr) {
data = value;
next = n;
}
};
template <class T>
class SinglyLinkedList {
private:
Node<T>* head;//始终指向链表头部的指针
public:
SinglyLinkedList():head(nullptr){}
~SinglyLinkedList() {
auto* current = head;
while (current != nullptr) {//通过head指针遍历链表并删除每个节点,直到链表为空
auto next = current->next;
delete current;//释放当前节点内存
current = next;
}
head = nullptr;
}
//在链表头部添加一个元素
void prepend(const T& data) {
Node<T>* newNode = new Node(data);
newNode->next = head;//新节点的next指针指向原头节点
head = newNode;//更新头节点为新节点
}
//向链表末尾添加一个元素
void append(const T& data) {
Node<T>* newNode = new Node(data);
if (head == nullptr) {
head = newNode;
} else {
Node<T>* current = head;
while (current->next != nullptr) {//通过next指针遍历至链表
current = current->next;
}
current->next = newNode;//链表末尾节点的next指针指向新节点
}
}
//按data查找节点,返回指针
Node<T>* findNode(const T& data) {
auto currentNode = head;//从头节点开始遍历
while (currentNode != nullptr) {
if (currentNode->data == data) {
return currentNode;
}
currentNode = currentNode->next;//继续遍历下一个节点
}
return nullptr;//如果未找到返回nullptr
}
//按data删除节点
void remove(const T& data) {
if (head == nullptr) return;//链表为空直接返回
if (head->data == data) {//如果头节点就是要删除的节点
auto temp = head;
head = head->next;//将原头节点的下一个节点更新为头节点
delete temp;//释放原头节点的内存
return;
}
auto current = head;
while (current->next != nullptr) {//遍历当前节点的下一个节点
if(current->next->data == data) {//找到要删除的节点
auto* temp = current->next;//保存要删除的节点指针
current->next = current->next->next;//变更当前节点的next指向
delete temp;//释放节点的内存
return;
}
current = current->next;
}
}
//遍历所有元素
void traverse() const {
Node<T>* current = head;
while (current != nullptr) {
cout << current->data << " -> ";
current = current->next;
}
cout << "nullptr" << endl;
}
};
#endif //DS_SINGLYLINKEDLIST_H
main.cpp测试单向链表
//
// 测试SinglyLinkedList
//
#include <iostream>
using namespace std;
#include "SinglyLinkedList.h"
int main() {
SinglyLinkedList<string> sll;
sll.append("b");
sll.prepend("a");
sll.append("c");
sll.append("d");
sll.append("e");
sll.traverse();
sll.remove("d");
sll.traverse();
sll.remove("a");
sll.traverse();
sll.remove("e");
sll.traverse();
cout << sll.findNode("c");
return 0;
}
DoubleLinkedList.cpp实现双向链表
//
// 双向链表
//
#ifndef DS_DOUBLELINKEDLIST_H
#define DS_DOUBLELINKEDLIST_H
#include <iostream>
using namespace std;
template <class T>
//双向链表的节点结构
struct Node{
T data;//数据
Node* prev;//指向前一个节点的指针
Node* next;//指向后一个节点的指针
Node(const T& value, Node* p = nullptr, Node* n = nullptr) {
data = value;
prev = p;
next = n;
}
};
template <class T>
class DoubleLinkedList {
private:
Node<T>* head; //指向链表头部节点的指针
Node<T>* tail; //指向链表尾部节点的指针
public:
DoubleLinkedList() : head(nullptr), tail(nullptr) {}
~DoubleLinkedList() {
while (head != nullptr) {
Node<T>* current = head;
head = head->next;
delete current;
}
tail = nullptr;
}
// 在链表头部添加新节点
void prepend(const T& data) {
Node<T>* newNode = new Node(data);
if (head == nullptr) { //如果链表为空,新节点同时是头节点和尾节点
head = tail = newNode;
} else {
newNode->next = head;//新节点的next指向原头部节点
head->prev = newNode;//原头部节点的prev指向新节点
head = newNode;
}
}
// 在链表末尾添加新节点
void append(const T& data) {
Node<T>* newNode = new Node(data);
if (tail == nullptr) {//如果链表为空,新节点同时是头部节点和尾部节点
head = tail = newNode;
} else {
tail->next = newNode;//原尾部节点的next指向新节点
newNode->prev = tail;//新节点的prev指向原尾部节点
tail = newNode;
}
}
// 删除指定值的节点(只删除第一个匹配的节点)
void remove(const T& data) {
Node<T>* current = head;
while (current != nullptr) {
if (current->data == data) { //找到匹配的节点,进行删除操作
if (current->prev != nullptr) { // 如果不是头节点,则重新链接前一个节点和后一个节点
current->prev->next = current->next;
} else { // 是头节点的情况,更新头指针
head = current->next;
}
if (current->next != nullptr) { // 如果不是尾节点,则重新链接前一个节点和后一个节点
current->next->prev = current->prev;
} else { // 是尾节点的情况,更新尾指针
tail = current->prev;
}
delete current; // 删除当前节点并退出循环(只删除第一个匹配的节点)
return;
}
current = current->next; //继续搜索下一个节点
}
cout << "未找到值为 " << data << " 的节点" << endl;
}
//按data查找节点,返回指针
Node<T>* findNode(const T& data) {
Node<T>* currentNode = head;//从头节点开始遍历
while (currentNode != nullptr) {
if (currentNode->data == data) {
return currentNode;
}
currentNode = currentNode->next;//继续遍历下一个节点
}
return nullptr;//如果未找到返回nullptr
}
//遍历所有元素
void traverse() const {
Node<T>* current = head;
while (current != nullptr) {
cout << current->data << " -> ";
current = current->next;
}
cout << "nullptr" << endl;
}
};
#endif //DS_DOUBLELINKEDLIST_H
main.cpp测试双向链表
//
// 测试DoubleLinkedList
//
#include <iostream>
using namespace std;
#include "DoubleLinkedList.h"
int main() {
DoubleLinkedList<string> dll;
dll.append("b");
dll.prepend("a");
dll.append("c");
dll.append("d");
dll.append("e");
dll.traverse();
dll.remove("d");
dll.traverse();
dll.remove("a");
dll.traverse();
dll.remove("e");
dll.traverse();
cout << dll.findNode("c");
return 0;
}
单向循环链表
通过引入哨兵节点,链表的操作(如插入、删除)可以统一处理,避免了对空链表或头节点的特殊处理。
静态链表在不支持指针的编程语言(如Fortran或某些嵌入式环境)中很有用,但现代主流语言(如C++)更倾向使用动态内存管理,所以这里不作讨论。