STL list合并

2023-11-13

知识点来源:cplusplus STL list
网上很多关于list的操作很少有提及到怎么合并,要说这个合并几乎是每个数据结构课提及到的O(1)操作的必修知识点。同时还有人甚至搞不清楚什么叫Merge(归并)和合并(Union)。归并的意思同归并排序是一致的,是两个有序列合并成一个长的有序列。因此操作必定需要O(n)啊,但是这些人肯定没讨论到复杂度,并把Merge称作为合并,因此导致了极大的误导。
首先C++的list出于操作的需要,要实现迁移式操作,肯定是要破坏被操作的另一个链表,导致这个操作的是splice。
splice方法有三种情况:

void splice (iterator position, list& x);
void splice (iterator position, list& x, iterator i);
void splice (iterator position, list& x, iterator first, iterator last); 

splice方法会改变x的链表结构,是完全的将x中的元素挪到操作的元素上,position是对于原操作的链表而言的,如果取y.splice(y.end(), x)就是在y的末尾添加x的所有元素,并把x清空。
对于第二种操作,相当于在y做了insert然后在x做了erase,复杂度来说肯定是O(1),具体实现会比insert + erase快几条指令。
第三种情况其实和第二种情况的“相当于”没啥差别,就是做了稍微快点的insert + erase,因此复杂度和移动的元素数量相关。
而对于第一种情况,就是指O(1)复杂度的Union操作,即将两表合并,x的表完全移动到y的后面或者给定position的位置,x表中的元素被清空。
对于归并操作,网上其他人讲的没啥出入,理解过归并排序后,就可以理解到merge是啥。
通过splice,可以用STL实现Fib堆,具体不展开讨论。







补充一个GNU的源码:
list.cpp

// 23.2.2.4 list operations:
      void
#if __cplusplus >= 201103L
      splice(const_iterator __position, list&& __x) noexcept
#else
      splice(iterator __position, list& __x)
#endif
      {
	_GLIBCXX_DEBUG_VERIFY(std::__addressof(__x) != this,
			      _M_message(__gnu_debug::__msg_self_splice)
			      ._M_sequence(*this, "this"));
	this->_M_transfer_from_if(__x, _Not_equal(__x._M_base().end()));
	_Base::splice(__position.base(), _GLIBCXX_MOVE(__x._M_base()));
      }

#if __cplusplus >= 201103L
      void
      splice(const_iterator __position, list& __x) noexcept
      { splice(__position, std::move(__x)); }
#endif

      void
#if __cplusplus >= 201103L
      splice(const_iterator __position, list&& __x, const_iterator __i) noexcept
#else
      splice(iterator __position, list& __x, iterator __i)
#endif
      {
	__glibcxx_check_insert(__position);

	// We used to perform the splice_alloc check:  not anymore, redundant
	// after implementing the relevant bits of N1599.

	_GLIBCXX_DEBUG_VERIFY(__i._M_dereferenceable(),
			      _M_message(__gnu_debug::__msg_splice_bad)
			      ._M_iterator(__i, "__i"));
	_GLIBCXX_DEBUG_VERIFY(__i._M_attached_to(std::__addressof(__x)),
			      _M_message(__gnu_debug::__msg_splice_other)
			     ._M_iterator(__i, "__i")._M_sequence(__x, "__x"));

	// _GLIBCXX_RESOLVE_LIB_DEFECTS
	// 250. splicing invalidates iterators
	this->_M_transfer_from_if(__x, _Equal(__i.base()));
	_Base::splice(__position.base(), _GLIBCXX_MOVE(__x._M_base()),
		      __i.base());
      }

#if __cplusplus >= 201103L
      void
      splice(const_iterator __position, list& __x, const_iterator __i) noexcept
      { splice(__position, std::move(__x), __i); }
#endif

      void
#if __cplusplus >= 201103L
      splice(const_iterator __position, list&& __x, const_iterator __first,
	     const_iterator __last) noexcept
#else
      splice(iterator __position, list& __x, iterator __first,
	     iterator __last)
#endif
      {
	__glibcxx_check_insert(__position);
	__glibcxx_check_valid_range(__first, __last);
	_GLIBCXX_DEBUG_VERIFY(__first._M_attached_to(std::__addressof(__x)),
			      _M_message(__gnu_debug::__msg_splice_other)
			      ._M_sequence(__x, "x")
			      ._M_iterator(__first, "first"));

	// We used to perform the splice_alloc check:  not anymore, redundant
	// after implementing the relevant bits of N1599.

	for (_Base_const_iterator __tmp = __first.base();
	     __tmp != __last.base(); ++__tmp)
	  {
	    _GLIBCXX_DEBUG_VERIFY(__tmp != _Base::end(),
				  _M_message(__gnu_debug::__msg_valid_range)
				  ._M_iterator(__first, "first")
				  ._M_iterator(__last, "last"));
	    _GLIBCXX_DEBUG_VERIFY(std::__addressof(__x) != this
				  || __tmp != __position.base(),
				_M_message(__gnu_debug::__msg_splice_overlap)
				  ._M_iterator(__tmp, "position")
				  ._M_iterator(__first, "first")
				  ._M_iterator(__last, "last"));
	    // _GLIBCXX_RESOLVE_LIB_DEFECTS
	    // 250. splicing invalidates iterators
	    this->_M_transfer_from_if(__x, _Equal(__tmp));
	  }

	_Base::splice(__position.base(), _GLIBCXX_MOVE(__x._M_base()),
		      __first.base(), __last.base());
      }

#if __cplusplus >= 201103L
      void
      splice(const_iterator __position, list& __x,
	     const_iterator __first, const_iterator __last) noexcept
      { splice(__position, std::move(__x), __first, __last); }
#endif

stl_list.h

      // [23.2.2.4] list operations
      /**
       *  @brief  Insert contents of another %list.
       *  @param  __position  Iterator referencing the element to insert before.
       *  @param  __x  Source list.
       *
       *  The elements of @a __x are inserted in constant time in front of
       *  the element referenced by @a __position.  @a __x becomes an empty
       *  list.
       *
       *  Requires this != @a __x.
       */
      void
#if __cplusplus >= 201103L
      splice(const_iterator __position, list&& __x) noexcept
#else
      splice(iterator __position, list& __x)
#endif
      {
	if (!__x.empty())
	  {
	    _M_check_equal_allocators(__x);

	    this->_M_transfer(__position._M_const_cast(),
			      __x.begin(), __x.end());

	    this->_M_inc_size(__x._M_get_size());
	    __x._M_set_size(0);
	  }
      }

#if __cplusplus >= 201103L
      void
      splice(const_iterator __position, list& __x) noexcept
      { splice(__position, std::move(__x)); }
#endif

#if __cplusplus >= 201103L
      /**
       *  @brief  Insert element from another %list.
       *  @param  __position  Const_iterator referencing the element to
       *                      insert before.
       *  @param  __x  Source list.
       *  @param  __i  Const_iterator referencing the element to move.
       *
       *  Removes the element in list @a __x referenced by @a __i and
       *  inserts it into the current list before @a __position.
       */
      void
      splice(const_iterator __position, list&& __x, const_iterator __i) noexcept
#else
      /**
       *  @brief  Insert element from another %list.
       *  @param  __position  Iterator referencing the element to insert before.
       *  @param  __x  Source list.
       *  @param  __i  Iterator referencing the element to move.
       *
       *  Removes the element in list @a __x referenced by @a __i and
       *  inserts it into the current list before @a __position.
       */
      void
      splice(iterator __position, list& __x, iterator __i)
#endif
      {
	iterator __j = __i._M_const_cast();
	++__j;
	if (__position == __i || __position == __j)
	  return;

	if (this != std::__addressof(__x))
	  _M_check_equal_allocators(__x);

	this->_M_transfer(__position._M_const_cast(),
			  __i._M_const_cast(), __j);

	this->_M_inc_size(1);
	__x._M_dec_size(1);
      }

#if __cplusplus >= 201103L
      /**
       *  @brief  Insert element from another %list.
       *  @param  __position  Const_iterator referencing the element to
       *                      insert before.
       *  @param  __x  Source list.
       *  @param  __i  Const_iterator referencing the element to move.
       *
       *  Removes the element in list @a __x referenced by @a __i and
       *  inserts it into the current list before @a __position.
       */
      void
      splice(const_iterator __position, list& __x, const_iterator __i) noexcept
      { splice(__position, std::move(__x), __i); }
#endif

#if __cplusplus >= 201103L
      /**
       *  @brief  Insert range from another %list.
       *  @param  __position  Const_iterator referencing the element to
       *                      insert before.
       *  @param  __x  Source list.
       *  @param  __first  Const_iterator referencing the start of range in x.
       *  @param  __last  Const_iterator referencing the end of range in x.
       *
       *  Removes elements in the range [__first,__last) and inserts them
       *  before @a __position in constant time.
       *
       *  Undefined if @a __position is in [__first,__last).
       */
      void
      splice(const_iterator __position, list&& __x, const_iterator __first,
	     const_iterator __last) noexcept
#else
      /**
       *  @brief  Insert range from another %list.
       *  @param  __position  Iterator referencing the element to insert before.
       *  @param  __x  Source list.
       *  @param  __first  Iterator referencing the start of range in x.
       *  @param  __last  Iterator referencing the end of range in x.
       *
       *  Removes elements in the range [__first,__last) and inserts them
       *  before @a __position in constant time.
       *
       *  Undefined if @a __position is in [__first,__last).
       */
      void
      splice(iterator __position, list& __x, iterator __first,
	     iterator __last)
#endif
      {
	if (__first != __last)
	  {
	    if (this != std::__addressof(__x))
	      _M_check_equal_allocators(__x);

	    size_t __n = _S_distance(__first, __last);
	    this->_M_inc_size(__n);
	    __x._M_dec_size(__n);

	    this->_M_transfer(__position._M_const_cast(),
			      __first._M_const_cast(),
			      __last._M_const_cast());
	  }
      }

#if __cplusplus >= 201103L
      /**
       *  @brief  Insert range from another %list.
       *  @param  __position  Const_iterator referencing the element to
       *                      insert before.
       *  @param  __x  Source list.
       *  @param  __first  Const_iterator referencing the start of range in x.
       *  @param  __last  Const_iterator referencing the end of range in x.
       *
       *  Removes elements in the range [__first,__last) and inserts them
       *  before @a __position in constant time.
       *
       *  Undefined if @a __position is in [__first,__last).
       */
      void
      splice(const_iterator __position, list& __x, const_iterator __first,
	     const_iterator __last) noexcept
      { splice(__position, std::move(__x), __first, __last); }
#endif


    // Moves the elements from [first,last) before position.
      void
      _M_transfer(iterator __position, iterator __first, iterator __last)
      { __position._M_node->_M_transfer(__first._M_node, __last._M_node); }

具体来讲,会存在一个问题,就是说为什么第三种情况并非O(1)呢?出自于空间的计算:

size_t __n = _S_distance(__first, __last);
this->_M_inc_size(__n);
__x._M_dec_size(__n);

也就是说,由于需要计算size的大小,会导致一个std::distance的计算,这个在list的迭代器里只有O(n)的复杂度。而迁移的transfer其实是O(1)的,注意到和第一种方法同用一个_M_transfer。而第一种方法和第三种区别在于,第一种实际上的size变更不用计算,可以直接取x的size并增加。因此,STL在内部实际上确确实实地实现了O(1)迁移。并暴露给splice使用。
而除了第一第二种方法是O(1)外,第三种方法是O(n)的,仅仅因为要记录size的大小而产生的高额复杂度。而来自<list>的还包括了对每个节点的地址规范化,姑且认为_M_transfer_from_if是做这种事的。
它的解释是:

/** Transfers all iterators @c x that reference @c from sequence,
	are not singular, and for which @c __pred(x) returns @c
	true. @c __pred will be invoked with the normal iterators nested
	in the safe ones. */
template<typename _Predicate>
void _M_transfer_from_if(_Safe_sequence& __from, _Predicate __pred);

简单来说就是让序列在迭代器层面支持安全迁移,这是最终面向编程者的list,产生的复杂度也有O(n)。总的来说,第三种操作必然产生O(n)的复杂度。对于如何选取部分的元素做O(1)迁移操作,如果不从stl内部操作中加入直接元素数量操作和提前安全化是不可能的。安全化后采用_M_transfer就为O(1),而操作的元素数量由编程者直接负责,不过这些操作对于一般情况来说是用不到的,而且相当于要做一些函数钩子和重载。如此麻烦,要么不用stl,要么一开始的算法就是分开维护的list。

要知道,对于一个完整的list,合并splice(y.splice(y.end(), x))永远是O(1)。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

STL list合并 的相关文章

随机推荐

  • int 0x80系统调用的参数传递规则

    系统调用的参数传递规则 传递给系统调用的参数则必须按照参数顺序依次存放到寄存器ebx ecx edx esi edi中 当系统调用完成之后 返回值存放在eax中 A 当系统调用所需参数的个数不超过5个的时候 执行 int 0x80 指令时
  • Chroom书签同步

    Chroom自带书签管理 而且有些管理书签的插件 我感觉自带书签管理栏就能满足我的个人需求 但是有一个问题 当我换了电脑后 原来的书签怎么同步 我为什么要使用Chroom 用其他浏览器广告太多了 比如360 也试着使用国内的其他浏览器 感觉
  • 如何在vscode配置php开发环境

    3 下载并安装vscodehttps code visualstudio com 下载的是一个压缩包 将其解压至一个目录 4 在vscode中安装调试插件右侧栏中点击扩展 输入xdebug 出来的php debug 点击安装 在菜单栏 文件
  • C++ 常用容器及其使用方法

    文章目录 本章内容概述 一 Vector 1 构造函数 2 增加函数 3 删除函数 4 属性函数 二 Unordered map 1 构造函数 2 增加函数 3 删除函数 4 属性函数 三 Stack 1 构造函数 2 访问方式 3 增加函
  • python: pandas与numpy(一)创建DataFrame数组与数组的简单操作

    目录 前言 1 创建Series数组 2 创建DataFrame数组 使用字典来创建DataFrame 使用列表来创建DataFrame 使用Series数组创建DataFrame 使用numpy函数创建DataFrame 3 在DataF
  • logback.xml

  • Mayor‘s posters(线段树染色)

    题目链接 Mayor s posters 2023 4 13 更新了代码 修复了错误的离散化长度 已在代码中注出 大致题意 有n个人依次贴海报 第i个海报的范围是 li ri 后面贴的海报会覆盖掉之前贴的海报 问 最终还能看到多少张海报 解
  • java的3大注释快捷键大全

    单行注释 行注释 一般用于单行或者少量代码 快捷键 光标 ctrl 或者 多行注释 块注释 一般用于多行或者大量代码 快捷键 选中 ctrl shift 文档注释 方法或类注释 一般用于对类和方法的说明 快捷键 光标 回车键 EnTer
  • 蓝桥杯Java必备基础知识总结大全【3W字】持续更新中

    本文会持续更新 如果对您有帮助的话可以点点关注 双击 本人2021年蓝桥杯C B组国二 今年转战Java 并整理此文 希望能够对大家有所帮助 第一次写这么长的文章 可能有的地方写的不是很好 还请大家多多谅解 我会持续进行改进并且更新 更新
  • 海思(MPP)媒体处理软件平台(3)-----VDEC

    sample vdec 视频解码 测试环境 在HI3531D开发板上运行 查看代码使用VSCode 运行 nfsroot mpp sample vdec sample vdec please choose the case which yo
  • [激光原理与应用-68]:光耦继电器、固态继电器、延时继电器、PLC开关信号

    一 PLC开关信号 备注 PLC需要的是开关信号 闭合或断开 不需要TTL电平信号 二 激光器输出信号 三 固态激光器工作原理 四 光耦继电器原理 五 光耦 延时 继电器原理 六 差分转单端原理
  • 排列组合知识

    排列组合的定义 排列 就是从n个元素中取出m个元素 按照一定的顺序排成一列 看到没 是要有顺序排成一列的 组合 也是从n个元素中取出m个元素 只不过是组成一个组 并不要求排成一列 只要组的成员不同就可以了 公式如下 例题 哪里用得上排列组合
  • 【Markdown】图片缩放

    01 原图表示 语法为 替代文本 图片链接地址 其中 替代文本是在无法显示图片时显示的替代文本 而图片链接是指向图片的URL或相对路径 例如 插入Panda图片 panda https img blog csdnimg cn e5f32e4
  • 亚信科技AntDB数据库专家参加向量数据库首次技术标准研讨会

    2023年7月19日下午 中国通信标准化协会大数据技术标准推进委员会数据库与存储工作组 CCSA TC601 WG4 联合中国信通院数据库应用创新实验室 CAICT DBL 在线上召开 向量数据库技术要求 标准首次研讨会 本次会议由中国信通
  • 单端反激——隔离型DC/DC变换器的设计及仿真

    单端反激 隔离型DC DC变换器的设计及仿真 技术指标 1 原理分析 2 参数设计 3 仿真验证 技术指标 输入电压 V s m i n
  • Spring Boot 2.2.6 源码之旅二十五SpringMVC源码之RequestMappingHandlerMapping的初始化三

    Spring Boot 2 2 6 源码之旅二十五SpringMVC源码之RequestMappingHandlerMapping的初始化三 简单流程图 MappingRegistry的一些映射 urlLookup一键多值的url和Requ
  • 那些会阻碍程序员成长的细节[4]

    照例 如果没有读过之前的系列 在这里可以先回顾一下 那些会阻碍程序员成长的细节 1 那些会阻碍程序员成长的细节 2 那些会阻碍程序员成长的细节 3 本文共 1637 字 预计阅读时间 5 分钟 不愿意跟领导走的近 是不是有这样的体会 凡事有
  • 【python标准库学习】re模块

    1 什么是re 正则表达式一门相对通用的语言 在python中也有对正则表达式的支持 那就是的内置re模块 正则表达式就是一系列的规则去匹配字符串然后进行相应的操作 这些规则网上一搜一大片 而re则是运用正则表达式来提供一系列的功能强大的接
  • Vue中如何进行打包与部署?

    Vue中如何进行打包与部署 Vue是一款流行的JavaScript框架 它提供了丰富的功能和组件 可以用于构建现代化的Web应用程序 在开发Vue应用程序时 我们通常需要进行打包和部署 本文将介绍Vue中的打包和部署 包括使用Webpack
  • STL list合并

    知识点来源 cplusplus STL list 网上很多关于list的操作很少有提及到怎么合并 要说这个合并几乎是每个数据结构课提及到的O 1 操作的必修知识点 同时还有人甚至搞不清楚什么叫Merge 归并 和合并 Union 归并的意思