【问题标题】:How to change what value begin() iterator points to?如何更改 begin() 迭代器指向的值?
【发布时间】:2021-08-21 00:10:08
【问题描述】:

在使用 STL 的 C++ 中,我有一个 std::forward_list<int> 我想修改其迭代器,但我不确定如何/是否可能。

假设我有以下元素的前向列表:

std::forward_list<int> list {1,2,3,4,5};

在这里,list.begin() 返回1。我怎样才能移动list.begin()?假设我想将开始迭代器设置为索引 3。那么列表将如下所示:

{4,5,1,2,3} // includes adjusting list.end() the same amount of begin()

这可能吗?或者这是一种通过指针来做到这一点的方法?

【问题讨论】:

  • 我不认为它总是指向数组中的第一个元素。
  • std::next(list.begin(), index) - 虽然很贵
  • begin 总是引用第一个元素,end 迭代器总是引用最后一个元素之后的元素。您可以重新排列列表中的元素。如果这就是你想要的:std::rotate
  • 我认为没有你想要的解决方案。迭代器只是一个指针,主要数据的容器是私有分配的。与其他 stl 容器一样,std::forward_list 没有使用自己的成员函数进行 shift rotation 操作。或者,您可以通过 iterator++begin()+n 迭代到 !=begin()+n

标签: c++ linked-list iterator


【解决方案1】:

您似乎在问如何通过旋转列表元素来重新排列它们。有一种称为std::rotate(及其ranges 对应物)的标准算法,它以线性复杂度做到了这一点。它通过交换元素来工作。

但是,链表 - 与大多数其他序列数据结构不同 - 有另一个工具可以让您以恒定的复杂性旋转它们:拼接。单链表的拼接比双链表的拼接要复杂一些,但它仍然是可能的。这是一个例子:

int size = 5;
int new_first_index = 3;
auto new_first = std::next(list.begin(), new_first_index);
auto inclusive_last = std::next(new_first, size - new_first_index - 1);

// the rotation:
list.splice_after(inclusive_last, list, list.before_begin(), new_first);

重要的是要了解,尽管拼接具有恒定的复杂性并且非常快,但对std::next 的调用具有线性复杂性,因此与std::rotate 渐近相同。因此,为了能够充分利用这一点,最好在您已经拥有相关迭代器而无需搜索的情况下使用它。 这个条件通常适用于几乎所有的列表操作。如果您没有存储迭代器,那么您可能使用了错误的列表,或者您可能应该使用其他数据结构。

即使您确实需要搜索迭代器,如果元素交换成本很高(不适用于 int),这也可能比 std::rotate 快得多(不是渐近的,而是一个常数因子) std::rotate 会做很多事情。

【讨论】:

    【解决方案2】:

    这会将列表向前旋转 3 个元素 (需要#include &lt;algorithm&gt;):

    std::rotate(list.begin(), std::next(list.begin(), 3), list.end());
    

    虽然它不是特别有效,因为 std::next 是 O(n)。

    也许可以考虑使用不同的数据结构,例如 std::vector 如果你真的关心这个。

    【讨论】:

    • std::rotate 本身也是 O(n)。即使计算std::next 的成本,旋转std::vector 也不会越来越快。
    • @eerorika,是的,但是在我的穴居人性能测试中,主导向量的常数因子快 10 倍:用于 - 填充(推送,无保留)5000 万个元素的列表, - 将它们旋转 25百万, - 并总结结果以确保优化器不会触发快乐如下: std::vector: 0.214 s std::vector, without the rotate: 0.199 s std:: deque: 0.159 std::deque without the rotate: 0.131 s std::forward: 1.947 s std::forward: without the rotate 1.751 s --> 只是旋转:向前:196 毫秒矢量:15 毫秒
    • 是的,我试图在不使用内置函数的情况下执行此操作。我最终只是创建了自己的循环链表数据结构,它工作正常,并且是 O(1)。
    • @JamesNewman 同意,如果你真的想要一个列表,某种形式的疯狂跳过列表可能是最好的。但正如我的测试清楚地表明,由于更好的缓存使用率,连续数据结构通常比链表快得多。如果性能实际上是一个问题,我只需将一个起始索引与向量一起存储,然后添加到该索引并执行模数 vector.size()。 O(1)。完毕。如果你真的想要,那就写一个花哨的包装器,这样你就可以继续使用 std algs。
    【解决方案3】:

    我们来看一张图片。你有一个包含五个元素的单链表。

      1 --> 2 --> 3 --> 4 --> 5 --> null
      ^                               ^
      |                               |
    begin                            end
    

    现在您想让列表的开头指向带有4 的节点。

      1 --> 2 --> 3 --> 4 --> 5 --> null
                        ^             ^
                        |             |
                      begin          end
    

    请注意,在这张图片中,不再有任何东西指向带有1 的节点。即使您调整了end 迭代器,也无法按照箭头到达1。节点123 悬而未决,再也找不到了。这就是std::forward_list 不允许您这样做的原因之一。

    没有从51 的路径,除非你做一个。不过,你可以做一个。一种方法需要计算一个迭代器,该迭代器指向带有5 的节点,一个指向带有4 的节点。

    auto slice_begin = list.before_begin();       // For consistent naming
    auto slice_end = std::next(list.begin(), 3);  // Calculate iterator to 4
    auto destination = std::next(slice_end);      // Calculate iterator to 5
    

    这为您提供了以下图片。

               1 --> 2 --> 3 --> 4 --> 5 --> null
         ^                       ^     ^
         |                       |     |
    slice_begin              slice_end |
                                       |
                                   destination
    

    现在的目标是在slice_beginslice_end 之间严格移动节点,以便它们在destination 之后。幸运的是,forward_list 有办法做到这一点。

    list.splice_after(destination, list, slice_begin, slice_end);
    

    现在你有你想要的4 -&gt; 5 -&gt; 1 -&gt; 2 -&gt; 3

    【讨论】:

      猜你喜欢
      • 2021-08-06
      • 1970-01-01
      • 2013-12-03
      • 1970-01-01
      • 1970-01-01
      • 2021-03-10
      • 1970-01-01
      • 2012-04-17
      • 2013-10-25
      相关资源
      最近更新 更多