You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

LinkedList的pop_back()方法问题:尾节点删除后前驱节点next未置空

问题分析与解决方案

核心问题

你当前的pop_back()实现存在两个关键问题:

  • 仅删除了mLast节点并置空,但原倒数第二个节点的next指针仍指向已释放的内存(即你所说的垃圾值),会导致后续遍历(如operator<<)触发未定义行为
  • 单向链表的特性决定,要找到尾节点的前一个节点必须从表头遍历到表尾,这是你觉得效率低的根本原因

方案1:修复单向链表的pop_back(保证正确性,时间复杂度O(n))

如果坚持使用单向链表,必须通过遍历找到倒数第二个节点,才能正确更新指针逻辑。你的尝试方向正确,可优化代码如下:

template<typename T>
inline T LinkedList<T>::pop_back()
{
    if (isEmpty())
        throw std::out_of_range("LinkedList is empty"); // 用异常替代返回NULL,符合C++规范

    T lastValue = mLast->value;

    // 情况1:链表仅含一个节点
    if (mFirst == mLast)
    {
        delete mFirst;
        mFirst = mLast = nullptr;
    }
    // 情况2:链表含多个节点,遍历找倒数第二个节点
    else
    {
        Node* current = mFirst;
        // 找到next指向mLast的节点
        while (current->next != mLast)
        {
            current = current->next;
        }
        delete mLast;
        mLast = current;
        mLast->next = nullptr; // 关键:清空新尾节点的next指针
    }

    decreaseSize();
    return lastValue;
}

方案2:改为双向链表(实现O(1)时间的pop_back)

要彻底解决效率问题,唯一办法是给节点增加前驱指针,改为双向链表。这样pop_back时可直接通过mLast->prev找到前一个节点,无需遍历:

修改后的核心代码片段

template<typename T>
class LinkedList
{
public:
    struct Node
    {
        T value;
        Node* next = nullptr;
        Node* prev = nullptr; // 新增前驱指针
    };

    Node *mFirst = nullptr;
    Node *mLast = nullptr;

    // ... 其他成员保持不变

    template<typename T>
    inline void LinkedList<T>::push_back(const T& value)
    {
        Node *node = new Node{value, nullptr, nullptr};

        if (!isEmpty())
        {
            mLast->next = node;
            node->prev = mLast; // 设置新节点的前驱
            mLast = node;
        }
        else
        {
            mLast = mFirst = node;
        }
        increaseSize();
    }

    template<typename T>
    inline T LinkedList<T>::pop_back()
    {
        if (isEmpty())
            throw std::out_of_range("LinkedList is empty");

        T lastValue = mLast->value;
        Node* prevNode = mLast->prev;

        delete mLast;
        mLast = prevNode;

        // 若链表仅剩的最后一个节点被删除,同步置空mFirst
        if (mLast == nullptr)
        {
            mFirst = nullptr;
        }
        else
        {
            mLast->next = nullptr; // 清空新尾节点的next指针
        }

        decreaseSize();
        return lastValue;
    }
};

该版本的pop_back时间复杂度为O(1),完全无需遍历,彻底解决效率问题。


关于智能指针的问题

你用std::shared_ptr未解决问题,大概率是因为双向链表使用shared_ptr会出现循环引用(节点的next和prev均为shared_ptr,导致节点无法被正确释放)。正确做法是用std::shared_ptr管理next,用std::weak_ptr管理prev:

struct Node
{
    T value;
    std::shared_ptr<Node> next = nullptr;
    std::weak_ptr<Node> prev; // 用weak_ptr避免循环引用
};

后续pop_back时,通过mLast->prev.lock()即可安全获取前驱节点的shared_ptr,既保证内存安全又不会出现循环引用。


内容的提问来源于stack exchange,提问作者Mikel Grenlou

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 01:05:23