1-6 列表:链表实现

一、链表的基本概念

什么是链表
链表是一种线性数据结构,元素存储在节点中,每个节点包含数据域和指针域,通过指针将节点串联起来。

数组 vs 链表 内存布局对比:

数组:  [A][B][C][D][E]    连续内存,支持随机访问
       ↑
    基地址

链表:  [A|•]-->[B|•]-->[C|•]-->[D|•]-->[E|⊘]  非连续内存,只能顺序访问
       ↑                             
      头指针(head)                      ⊥ 表示空指针(nullptr)

节点结构定义

template <typename T>
struct Node 
{
    T data;           // 数据域
    Node* next;       // 指针域,指向下一个节点
    
    Node(const T& val) : data(val), next(nullptr) {}
};

二、基于链表的列表实现

编号 函数名 功能 时间复杂度 空间复杂度
2.1 构造函数
析构函数
创建空链表/指定初始化列表
释放所有节点内存
O(1)
O(n)
O(1)
O(1)
2.2 pushBack 在链表尾部添加元素 O(n) / O(1)* O(1)
2.3 pushFront 在链表头部添加元素 O(1) O(1)
2.4 insert 在指定位置插入元素 O(n) O(1)
2.5 popBack 删除尾部元素 O(n) O(1)
2.6 popFront 删除头部元素 O(1) O(1)
2.7 remove 删除指定位置元素 O(n) O(1)
2.8 removeByValue 查找并删除第一个匹配元素 O(n) O(1)
2.9 operator[]
at
front
back
访问元素(链表不支持O(1)随机访问)
安全访问
访问首元素
访问尾元素
O(n)
O(n)
O(1)
O(n) / O(1)*
O(1)
2.10 find
contains
线性查找 O(n) O(1)
2.11 clear
empty
清空链表
判断是否为空
O(n)
O(1)
O(1)
O(1)
2.12 拷贝控制 深拷贝/移动语义 O(n)/O(1) O(n)/O(1)
2.13 begin
end
迭代器支持 O(1) O(1)

*带尾指针优化后可达 O(1)


2.1 节点结构与基础框架

#include <iostream>
#include <stdexcept>
#include <initializer_list>

template <typename T>
class LinkedList 
{
private:
    // 节点结构
    struct Node 
    {
        T data;
        Node* next;
        Node(const T& val) 
          : data(val)
          , next(nullptr) 
        {}
    };
    
    Node* m_head;       // 头指针
    Node* m_tail;       // 尾指针(优化尾部操作)
    int m_size;         // 节点数量

public:
    // 默认构造函数
    LinkedList() 
        : m_head(nullptr)
        , m_tail(nullptr)
        , m_size(0) 
    {}
    
    // 初始化列表构造
    LinkedList(std::initializer_list<T> initList) 
        : m_head(nullptr), m_tail(nullptr), m_size(0) 
    {
        for (const auto& val : initList) 
        {
            pushBack(val);
        }
    }
    
    // 析构函数
    ~LinkedList() 
    {
        clear();
    }
    
    // 禁止拷贝(或实现深拷贝,见2.12)
    // 基础功能实现...
};

2.2 尾部添加 (pushBack)

带尾指针优化,O(1) 时间复杂度

// 在尾部添加元素 - O(1)
void pushBack(const T& value) 
{
    Node* newNode = new Node(value);
    
    if (m_head == nullptr) 
    {
        // 空链表
        m_head = m_tail = newNode;
    } 
    else 
    {
        // 非空链表,尾插
        m_tail->next = newNode;
        m_tail = newNode;
    }
    m_size++;
}
// 可视化演示
初始: 空链表  head=nullptr, tail=nullptr
pushBack(10):  [10]  head=tail=10
pushBack(20):  [10]-->[20]  tail=20
pushBack(30):  [10]-->[20]-->[30]  tail=30

2.3 头部添加 (pushFront)

// 在头部添加元素 - O(1)
void pushFront(const T& value) 
{
    Node* newNode = new Node(value);
    newNode->next = m_head;
    m_head = newNode;
    
    if (m_tail == nullptr) 
    {
        // 原链表为空,尾指针也要更新
        m_tail = newNode;
    }
    m_size++;
}
// 可视化演示
初始: [10]-->[20]-->[30]
pushFront(5):  [5]-->[10]-->[20]-->[30]
               ↑
              head更新

2.4 指定位置插入 (insert)

// 在指定索引处插入 - O(n)
void insert(int index, const T& value) 
{
    if (index < 0 || index > m_size) 
    {
        throw std::out_of_range("Insert index out of range");
    }
    
    if (index == 0) 
    {
        pushFront(value);
        return;
    }
    
    if (index == m_size) 
    {
        pushBack(value);
        return;
    }
    
    // 找到第 index-1 个节点
    Node* prev = m_head;
    for (int i = 0; i < index - 1; i++) 
    {
        prev = prev->next;
    }
    
    Node* newNode = new Node(value);
    newNode->next = prev->next;
    prev->next = newNode;
    m_size++;
}
// 可视化演示: 在索引1处插入X
初始: [A]-->[B]-->[C]-->[D]  在index=1插入X
步骤1: 找到prev = A (index-1 = 0)
步骤2: newNode->next = B
步骤3: A->next = X
结果: [A]-->[X]-->[B]-->[C]-->[D]

2.5 尾部删除 (popBack)

// 删除尾部元素 - O(n) 需要找到倒数第二个节点
void popBack() 
{
    if (isEmpty()) 
    {
        throw std::runtime_error("Cannot pop from empty list");
    }
    
    if (m_head == m_tail) 
    {
        // 只有一个节点
        delete m_head;
        m_head = m_tail = nullptr;
    } 
    else 
    {
        // 找到倒数第二个节点
        Node* prev = m_head;
        while (prev->next != m_tail) 
        {
            prev = prev->next;
        }
        
        delete m_tail;
        m_tail = prev;
        m_tail->next = nullptr;
    }
    m_size--;
}
// 可视化演示
初始: [A]-->[B]-->[C]-->[D]  tail=D
步骤1: 找到prev = C (C->next == D)
步骤2: delete D
步骤3: C->next = nullptr, tail = C
结果: [A]-->[B]-->[C]  tail=C

2.6 头部删除 (popFront)

// 删除头部元素 - O(1)
void popFront() 
{
    if (isEmpty()) 
    {
        throw std::runtime_error("Cannot pop from empty list");
    }
    
    Node* temp = m_head;
    m_head = m_head->next;
    delete temp;
    
    if (m_head == nullptr) 
    {
        // 链表变空
        m_tail = nullptr;
    }
    m_size--;
}

2.7 指定位置删除 (remove)

// 删除指定索引元素 - O(n)
void remove(int index) 
{
    checkIndex(index);
    
    if (index == 0) 
    {
        popFront();
        return;
    }
    
    // 找到第 index-1 个节点
    Node* prev = m_head;
    for (int i = 0; i < index - 1; i++) 
    {
        prev = prev->next;
    }
    
    Node* toDelete = prev->next;
    prev->next = toDelete->next;
    
    // 如果删除的是尾节点,更新tail
    if (toDelete == m_tail) 
    {
        m_tail = prev;
    }
    
    delete toDelete;
    m_size--;
}

2.8 按值删除 (removeByValue)

// 查找并删除第一个匹配元素 - O(n)
bool removeByValue(const T& value) 
{
    if (isEmpty()) return false;
    
    // 检查头节点
    if (m_head->data == value) 
    {
        popFront();
        return true;
    }
    
    // 遍历查找
    Node* prev = m_head;
    while (prev->next != nullptr && prev->next->data != value) 
    {
        prev = prev->next;
    }
    
    if (prev->next == nullptr) 
    {
        return false;  // 未找到
    }
    
    Node* toDelete = prev->next;
    prev->next = toDelete->next;
    
    if (toDelete == m_tail) 
    {
        m_tail = prev;
    }
    
    delete toDelete;
    m_size--;
    return true;
}

2.9 元素访问

⚠️ 链表不支持 O(1) 随机访问,必须遍历

// 下标访问 - O(n) 不推荐频繁使用
T& operator[](int index) 
{
    return getNode(index)->data;
}

const T& operator[](int index) const 
{
    return getNode(index)->data;
}

// 安全访问 - O(n)
T& at(int index) 
{
    checkIndex(index);
    return getNode(index)->data;
}

// 获取指定索引节点 - O(n)
Node* getNode(int index) const 
{
    Node* curr = m_head;
    for (int i = 0; i < index; i++) 
    {
        curr = curr->next;
    }
    return curr;
}

// 首尾访问 - O(1)
T& front() 
{
    if (isEmpty()) throw std::runtime_error("List is empty");
    return m_head->data;
}

T& back() 
{
    if (isEmpty()) throw std::runtime_error("List is empty");
    return m_tail->data;
}

2.10 查找操作

注意:由于源代码采用ASCII字符编写,编程语言通常使用!=来表示数学不等号,而并不属于ASCII字符集, 但此处写在markdown代码块中显示出来却是

// 线性查找 - O(n)
int find(const T& value) const   
{
    Node* curr = m_head;
    int index = 0;
    while (curr != nullptr) 
    {
        if (curr->data == value) 
        {
            return index;
        }
        curr = curr->next;
        index++;
    }
    return -1;
}

bool contains(const T& value) const 
{
    return find(value) != -1;
}

2.11 容量管理

// 清空链表 - O(n)
void clear() 
{
    Node* curr = m_head;
    while (curr != nullptr) 
    {
        Node* temp = curr;
        curr = curr->next;
        delete temp;
    }
    m_head = m_tail = nullptr;
    m_size = 0;
}

int size() const { return m_size; }
bool isEmpty() const { return m_size == 0; }

2.12 拷贝控制(深拷贝)

// 拷贝构造函数 - O(n)
LinkedList(const LinkedList& other) 
    : m_head(nullptr), m_tail(nullptr), m_size(0) 
{
    Node* curr = other.m_head;
    while (curr != nullptr) 
    {
        pushBack(curr->data);
        curr = curr->next;
    }
}

// 拷贝赋值 - O(n)
LinkedList& operator=(const LinkedList& other) 
{
    if (this != &other) 
    {
        clear();
        Node* curr = other.m_head;
        while (curr != nullptr) 
        {
            pushBack(curr->data);
            curr = curr->next;
        }
    }
    return *this;
}

// 移动构造 - O(1)
LinkedList(LinkedList&& other) noexcept
    : m_head(other.m_head)
    , m_tail(other.m_tail)
    , m_size(other.m_size) 
{
    other.m_head = other.m_tail = nullptr;
    other.m_size = 0;
}

// 移动赋值 - O(1)
LinkedList& operator=(LinkedList&& other) noexcept 
{
    if (this != &other) 
    {
        clear();
        m_head = other.m_head;
        m_tail = other.m_tail;
        m_size = other.m_size;
        other.m_head = other.m_tail = nullptr;
        other.m_size = 0;
    }
    return *this;
}

2.13 迭代器支持

// 简单迭代器:直接返回节点指针
class Iterator 
{
    Node* m_node;
public:
    Iterator(Node* node) : m_node(node) {}
    
    T& operator*() const { return m_node->data; }
    T* operator->() const { return &(m_node->data); }
    
    Iterator& operator++() 
    {
        m_node = m_node->next;
        return *this;
    }
    
    bool operator!=(const Iterator& other) const 
    {
        return m_node != other.m_node;
    }
    bool operator==(const Iterator& other) const 
    {
        return m_node == other.m_node;
    }
};

Iterator begin() { return Iterator(m_head); }
Iterator end() { return Iterator(nullptr); }

2.14 完整实现代码

#include <iostream>
#include <stdexcept>
#include <initializer_list>

template <typename T>
class LinkedList 
{
private:
    struct Node 
    {
        T data;
        Node* next;
        Node(const T& val) : data(val), next(nullptr) {}
    };
    
    Node* m_head;
    Node* m_tail;
    int m_size;

    void checkIndex(int index) const 
    {
        if (index < 0 || index >= m_size) 
        {
            throw std::out_of_range("Index out of range: " + std::to_string(index));
        }
    }
    
    Node* getNode(int index) const 
    {
        Node* curr = m_head;
        for (int i = 0; i < index; i++) 
        {
            curr = curr->next;
        }
        return curr;
    }

public:
    // 构造与析构
    LinkedList() : m_head(nullptr), m_tail(nullptr), m_size(0) {}
    
    LinkedList(std::initializer_list<T> initList) 
        : m_head(nullptr), m_tail(nullptr), m_size(0) 
        {
        for (const auto& val : initList) 
        {
            pushBack(val);
        }
    }
    
    ~LinkedList() { clear(); }
    
    // 拷贝控制
    LinkedList(const LinkedList& other) 
        : m_head(nullptr), m_tail(nullptr), m_size(0) 
    {
        Node* curr = other.m_head;
        while (curr != nullptr) 
        {
            pushBack(curr->data);
            curr = curr->next;
        }
    }
    
    LinkedList& operator=(const LinkedList& other) 
    {
        if (this != &other) 
        {
            clear();
            Node* curr = other.m_head;
            while (curr != nullptr) 
            {
                pushBack(curr->data);
                curr = curr->next;
            }
        }
        return *this;
    }
    
    LinkedList(LinkedList&& other) noexcept
        : m_head(other.m_head), m_tail(other.m_tail), m_size(other.m_size) 
    {
        other.m_head = other.m_tail = nullptr;
        other.m_size = 0;
    }
    
    LinkedList& operator=(LinkedList&& other) noexcept 
    {
        if (this != &other) 
        {
            clear();
            m_head = other.m_head;
            m_tail = other.m_tail;
            m_size = other.m_size;
            other.m_head = other.m_tail = nullptr;
            other.m_size = 0;
        }
        return *this;
    }

    // 添加操作
    void pushBack(const T& value) 
    {
        Node* newNode = new Node(value);
        if (m_head == nullptr) 
        {
            m_head = m_tail = newNode;
        } 
        else 
        {
            m_tail->next = newNode;
            m_tail = newNode;
        }
        m_size++;
    }
    
    void pushFront(const T& value) 
    {
        Node* newNode = new Node(value);
        newNode->next = m_head;
        m_head = newNode;
        if (m_tail == nullptr) m_tail = newNode;
        m_size++;
    }
    
    void insert(int index, const T& value) 
    {
        if (index < 0 || index > m_size) 
        {
            throw std::out_of_range("Insert index out of range");
        }
        if (index == 0) { pushFront(value); return; }
        if (index == m_size) { pushBack(value); return; }
        
        Node* prev = getNode(index - 1);
        Node* newNode = new Node(value);
        newNode->next = prev->next;
        prev->next = newNode;
        m_size++;
    }

    // 删除操作
    void popBack() 
    {
        if (isEmpty()) throw std::runtime_error("Cannot pop from empty list");
        if (m_head == m_tail) 
        {
            delete m_head;
            m_head = m_tail = nullptr;
        } 
        else 
        {
            Node* prev = m_head;
            while (prev->next != m_tail) prev = prev->next;
            delete m_tail;
            m_tail = prev;
            m_tail->next = nullptr;
        }
        m_size--;
    }
    
    void popFront() 
    {
        if (isEmpty()) throw std::runtime_error("Cannot pop from empty list");
        Node* temp = m_head;
        m_head = m_head->next;
        delete temp;
        if (m_head == nullptr) m_tail = nullptr;
        m_size--;
    }
    
    void remove(int index) 
    {
        checkIndex(index);
        if (index == 0) { popFront(); return; }
        
        Node* prev = getNode(index - 1);
        Node* toDelete = prev->next;
        prev->next = toDelete->next;
        
        if (toDelete == m_tail) m_tail = prev;
        delete toDelete;
        m_size--;
    }
    
    bool removeByValue(const T& value) 
    {
        if (isEmpty()) return false;
        if (m_head->data == value) { popFront(); return true; }
        
        Node* prev = m_head;
        while (prev->next != nullptr && prev->next->data != value) 
        {
            prev = prev->next;
        }
        if (prev->next == nullptr) return false;
        
        Node* toDelete = prev->next;
        prev->next = toDelete->next;
        if (toDelete == m_tail) m_tail = prev;
        delete toDelete;
        m_size--;
        return true;
    }

    // 访问操作
    T& operator[](int index) { return getNode(index)->data; }
    const T& operator[](int index) const { return getNode(index)->data; }
    
    T& at(int index) 
    {
        checkIndex(index);
        return getNode(index)->data;
    }
    
    T& front() 
    {
        if (isEmpty()) throw std::runtime_error("List is empty");
        return m_head->data;
    }
    
    T& back() 
    {
        if (isEmpty()) throw std::runtime_error("List is empty");
        return m_tail->data;
    }

    // 查找
    int find(const T& value) const 
    {
        Node* curr = m_head;
        int idx = 0;
        while (curr != nullptr) 
        {
            if (curr->data == value) return idx;
            curr = curr->next;
            idx++;
        }
        return -1;
    }
    
    bool contains(const T& value) const 
    {
        return find(value) != -1;
    }

    // 容量管理
    void clear() 
    {
        Node* curr = m_head;
        while (curr != nullptr) 
        {
            Node* temp = curr;
            curr = curr->next;
            delete temp;
        }
        m_head = m_tail = nullptr;
        m_size = 0;
    }
    
    int size() const { return m_size; }
    bool isEmpty() const { return m_size == 0; }

    // 迭代器
    class Iterator {
        Node* m_node;
    public:
        Iterator(Node* node) : m_node(node) {}
        T& operator*() const { return m_node->data; }
        Iterator& operator++() { m_node = m_node->next; return *this; }
        bool operator!=(const Iterator& other) const { return m_node != other.m_node; }
    };
    
    Iterator begin() { return Iterator(m_head); }
    Iterator end() { return Iterator(nullptr); }

    // 输出
    void print() const 
    {
        std::cout << "[";
        Node* curr = m_head;
        while (curr != nullptr) 
        {
            std::cout << curr->data;
            if (curr->next != nullptr) std::cout << " -> ";
            curr = curr->next;
        }
        std::cout << "] (size=" << m_size << ")\n";
    }
};

三、测试代码

void testLinkedList() 
{
    std::cout << "=== LinkedList Test ===\n";
    
    // 基本操作
    LinkedList<int> list = {10, 20, 30};
    list.print();  // [10 -> 20 -> 30]
    
    list.pushFront(5);
    list.pushBack(40);
    list.print();  // [5 -> 10 -> 20 -> 30 -> 40]
    
    // 插入删除
    list.insert(2, 15);
    list.print();  // [5 -> 10 -> 15 -> 20 -> 30 -> 40]
    
    list.remove(2);
    list.print();  // [5 -> 10 -> 20 -> 30 -> 40]
    
    // 查找
    std::cout << "Find 20: " << list.find(20) << "\n";
    std::cout << "Contains 100: " << list.contains(100) << "\n";
    
    // 遍历
    std::cout << "Iterate: ";
    for (auto& val : list) 
    {
        std::cout << val << " ";
    }
    std::cout << "\n";
    
    // 拷贝测试
    LinkedList<int> copy = list;
    copy.pushBack(999);
    std::cout << "Original: "; list.print();
    std::cout << "Copy: "; copy.print();
}

int main() 
{
    testLinkedList();
    return 0;
}

四、数组 vs 链表 对比总结

特性 数组实现 (ArrayList) 链表实现 (LinkedList)
内存结构 连续内存 非连续内存,节点分散
随机访问 O(1) O(n)
头部插入/删除 O(n) O(1)
尾部插入/删除 O(1) 均摊 O(1) 带尾指针
中间插入/删除 O(n) O(n),但无需移动元素
缓存友好性 高(局部性好) 低(指针跳转)
内存开销 可能浪费(预留容量) 额外指针开销(每个节点)
适用场景 频繁随机访问,较少插入删除 频繁头尾操作,不确定数据量

选择建议:

  • 需要频繁按索引访问 → 数组
  • 需要频繁在头部操作 → 链表
  • 数据量巨大且不确定 → 链表(无需预分配大块内存)
posted @ 2026-03-24 11:50  游翔  阅读(1)  评论(0)    收藏  举报