list的实现和使用

list 深入讲解

1. 简述与适用场景

list 是双向链表的标准实现,适用于:

  • 频繁在容器中间进行插入/删除的场景(已知位置的情况下这些操作为 O(1))。
  • 需要稳定的指针/迭代器(对于不被删除的元素,list 的迭代器在大多数操作后仍然有效)。

不适合场景:

  • 需要频繁随机访问(operator[] 不可用,跳转到第 n 个元素需 O(n))。
  • 大量排序且希望用 sortsort 需要随机访问迭代器)。
cpp 复制代码
cpp
list<int> lt;
lt.push_back(1);
lt.push_back(3);
lt.push_back(2);
lt.push_back(6);

for (auto e : lt) cout << e << " ";

2. 迭代器分类(复盘)

按功能:iteratorconst_iteratorreverse_iterator 等。

按性质(底层决定):

  • 单向:forward_list(仅支持 ++
  • 双向:list(支持 ++--
  • 随机访问:vector/deque(支持 +/-

关键点:算法要求特定迭代器类别(例如 sort 要求随机访问迭代器),因此不能直接对 list 使用 sort

3. 增删改查基础

  • push_back / push_front:在尾/头插入元素。
  • emplace_back / emplace_front:直接在容器内部构造对象,避免额外拷贝/移动。

emplace_back可以代替push_back使用且在默写场景下效率更高。

cpp 复制代码
cpp
struct A {
    A(int a1 = 1, int a2 = 2) : _a1(a1), _a2(a2) {}
    int _a1;
    int _a2;
};

list<A> lt1;
A aa1(1,1);
lt1.push_back(aa1);
lt1.emplace_back(3,3); // 直接构造
  • insert(it, value):在迭代器 it 之前插入(O(1))。
  • erase(it):删除 it 指向的节点(返回下一个迭代器)。
  • remove(value):查找并删除所有等于 value 的元素(按值删除,不需要迭代器)。
  • splice(...):在常数时间内把一个链表或一段节点从一个 list 移动到另一个 list(不拷贝节点,仅变指针)。
    • lt1.splice(it, lt2):把 lt2 全部插入到 lt1it 前,lt2 变空。
    • lt.splice(lt.begin(), lt, it):把单个节点移动到头部。
    • lt.splice(lt.begin(), lt, it, lt.end()):把 [it, end) 段移动到头部。
  • reverse():链表自身提供反转(lt.reverse())。
    • 注意:std::reverse(lt.begin(), lt.end()) 对双向迭代器同样可用,但 list 有专门的 reverse()

4. 排序与合并

  • std::sort 不能直接用于 list(需随机访问迭代器)。
  • std::list 提供 list::sort()(内部是归并排序,稳定,适合链表)。
    • 可以传入仿函数或函数对象,如 lt.sort(greater<int>()) 进行降序。
  • 如果希望利用 std::sort 的速度(随机访问算法常常更快),可以先把 list 内容拷贝到 vector,排序后再 assignlist
cpp 复制代码
cpp
vector<int> v(lt.begin(), lt.end());
sort(v.begin(), v.end());
lt.assign(v.begin(), v.end());
  • merge:合并两个已排序的 list(将元素从参数表 list 中移动到目标 list),操作后被合并的 list 变空。
cpp 复制代码
cpp
x.sort();
y.sort();
x.merge(y); // y 变空,x 成为合并后的有序列表

5. 性能与复杂度总结(常见操作)

  • push_back, push_front: O(1)
  • insert(在已知位置): O(1)
  • erase(给定迭代器): O(1)
  • find(基于值): O(n)
  • splice:O(1)(只是改变指针,不拷贝)
  • sort()list::sort): O(n log n)(归并排序)
  • remove(value): O(n)

注意:虽然对单个已知位置的插入/删除为 O(1),但查找位置仍然可能需要 O(n)。

6. 常见"坑"与建议

  • 不能对 list 使用 std::sort(会编译错误);应使用 list::sort 或先复制到 vector
  • splice 非常高效,但要注意源 list 中被移动节点的迭代器/引用在语义上仍然有效并且指向原节点(但被移走后会在新容器中可用)。
  • list 执行大量随机访问(步进迭代器若干次以到达位置)会很慢,应考虑 vectordeque
  • emplace_back 在构造复杂对象时通常比 push_back 更高效,因为能避免不必要的拷贝/移动。
  • 在并发环境下 list 不提供线程安全,操作前需加锁或采用其他并发容器。

7. 小结

  • std::list 在需要频繁、局部化的插入和删除时是优秀的选择,并通过 splicemerge 等操作能以常数时间移动节点。
  • 若需随机访问或使用需要随机访问迭代器的算法(如 std::sort),应使用 vector/deque 或将 list 的数据转换到 vector 再处理。
  • 选择容器时优先考虑操作模式:插入/删除 vs 随机访问 vs 内存紧凑性与缓存友好性(vector 通常更缓存友好)。

list模拟实现

cpp 复制代码
namespace list
{
    template<class T>
    class list_node
    {
    public:
        T _data;
        list_node<T>* _next;
        list_node<T>* _prev;

        list_node(const T& data = T())
            : _data(data)
            , _next(nullptr)
            , _prev(nullptr)
        {}
    };

    //重点:迭代器的实现
	template<class T>
	struct list_iterator//默认
	{
		typedef list_node<T> Node;
		typedef list_iterator<T> Self;
		//const iterator -> 迭代器本身不能修改
		//const_iterator -> 指向内容不能修改

		Node* _node;

		T& operator*()
		{
			return _node->_data;
		}
		Self& operator++()//前置
		{
			_node = _node->next;
			return *this;
		}
		Self& operator++(int)//后置
		{
			Self tmp(*this);
			_node = _node->next;
			return tmp;
		}
		Self& operator--()
		{
			_node = _node->_prev;
			return *this;
		}
		Self& operator--(int)
		{
			Self tmp(*this);
			_node = _node->prev;
			return tmp;
		}
		T& operator->()
		{
			return _node->_data;
		}

		bool operator!=(const Self& s) const
		{
			return _node != s._node;
		}

		list_iterator(Node* node)
			:_node(node)
		{}
};
	//const迭代器的实现
	template<class T>
	struct list_const_iterator//默认
	{
		typedef list_node<T> Node;
		typedef list_const_iterator<T> Self;
		Node* _node;

		list_const_iterator(Node* node)
			:_node(node)
		{}

		const T& operator*()
		{
			return _node->_data;
		}

		Self& operator++()//前置
		{
			_node = _node->next;
			return *this;
		}

		Self& operator++(int)//后置
		{
			Self tmp(*this);
			_node = _node->next;
			return tmp;
		}

		Self& operator--()
		{
			_node = _node->_prev;
			return *this;
		}

		Self& operator--(int)
		{
			Self tmp(*this);
			_node = _node->prev;
			return tmp;
		}

		const T* operator->()
		{
			return _node->_data;
		}

		bool operator!=(const Self& s) const
		{
			return _node != s._node;
		}
	};

    template<class T>
    class list
    {
        typedef list_node<T> Node;
    public:
        typedef list_iterator<T, T&, T*> iterator;
        typedef list_iterator<T, const T&, const T*> const_iterator;

        list()
            : _head(new Node())
            , _size(0)
        {
            _head->_next = _head;
            _head->_prev = _head;
        }

        ~list()
        {
            clear();
            delete _head;
        }

        list(const list<T>& lt)
            : list()
        {
            for (auto const& e : lt)
                push_back(e);
        }

        list<T>& operator=(list<T> lt)
        {
            swap(lt);
            return *this;
        }

        void swap(list<T>& lt)
        {
            std::swap(_head, lt._head);
            std::swap(_size, lt._size);
        }

        iterator begin() { return iterator(_head->_next); }
        iterator end() { return iterator(_head); }
        const_iterator begin() const { return const_iterator(_head->_next); }
        const_iterator end() const { return const_iterator(_head); }

        void push_back(const T& x)
        {
            Node* newnode = new Node(x);
            Node* tail = _head->_prev;

            tail->_next = newnode;
            newnode->_prev = tail;
            newnode->_next = _head;
            _head->_prev = newnode;

            ++_size;
        }

        iterator insert(iterator pos, const T& x)
        {
            Node* cur = pos._node;
            Node* prev = cur->_prev;

            Node* newnode = new Node(x);

            newnode->_next = cur;
            cur->_prev = newnode;
            newnode->_prev = prev;
            prev->_next = newnode;

            ++_size;
            return iterator(newnode);
        }

        iterator erase(iterator pos)
        {
            assert(pos != end());
            Node* cur = pos._node;
            Node* prev = cur->_prev;
            Node* next = cur->_next;

            prev->_next = next;
            next->_prev = prev;

            delete cur;
            --_size;

            return iterator(next);
        }

        void pop_back()
        {
            assert(!empty());
            erase(iterator(_head->_prev));
        }

        void pop_front()
        {
            assert(!empty());
            erase(begin());
        }

        void clear()
        {
            Node* cur = _head->_next;
            while (cur != _head)
            {
                Node* next = cur->_next;
                delete cur;
                cur = next;
            }
            _head->_next = _head;
            _head->_prev = _head;
            _size = 0;
        }

        size_t size() const { return _size; }
        bool empty() const { return _size == 0; }

    private:
        Node* _head;
        size_t _size;
    };
}

其中,迭代器的实现部分我们可以使用高度相似的模板复用实例化来优化代码

cpp 复制代码
template<class T, class Ref, class Ptr>
struct list_iterator//默认
{
	typedef list_node<T> Node;
	typedef list_iterator<T, Ref, Ptr> Self;
	Node* _node;

	list_const_iterator(Node* node)
		:_node(node)
	{}

	Ref operator*()
	{
		return _node->_data;
	}

	Self& operator++()//前置
	{
		_node = _node->next;
		return *this;
	}

	Self& operator++(int)//后置
	{
		Self tmp(*this);
		_node = _node->next;
		return tmp;
	}

	Self& operator--()
	{
		_node = _node->_prev;
		return *this;
	}

	Self& operator--(int)
	{
		Self tmp(*this);
		_node = _node->prev;
		return tmp;
	}

	Ptr operator->()
	{
		return _node->_data;
	}
	bool operator!=(const Self& s) const
	{
		return _node != s._node;
	}
};
相关推荐
Non importa2 小时前
用滑动窗口代替暴力枚举:算法新手的第二道砍
java·数据结构·c++·学习·算法·leetcode·哈希算法
顾子羡_Gu2 小时前
算法进阶指南:搜索与数论基础
数据结构·算法
2301_773730312 小时前
数据结构—队列
数据结构
課代表2 小时前
Windows 批处理 bat 变量扩展名
windows·命令行·bat·批处理·扩展名·递归遍历·后缀名
量子炒饭大师3 小时前
Cyber骇客的脑机双链回流码 ——【初阶数据结构与算法】线性表之双向链表
数据结构·c++·链表
2401_841495643 小时前
【LeetCode刷题】缺失的第一个正数
数据结构·python·算法·leetcode·数组·哈希·缺失最小正整数
C++业余爱好者3 小时前
Java 中的数据结构详解及应用场景
java·数据结构·python
电脑小管家3 小时前
路由器怎么重新设置wifi密码
网络·windows·计算机外设·智能路由器·电脑
yuezhilangniao3 小时前
Windows 系统变量未完全清楚 - 代理执行一半 导致 pip 和 Postman 连接失败的解决指南
windows·postman·pip