C++ 将单个元素移动到向量中的新位置的最简单最有效的方法

Posted

技术标签:

【中文标题】C++ 将单个元素移动到向量中的新位置的最简单最有效的方法【英文标题】:C++ Easiest most efficient way to move a single element to a new position within a vector 【发布时间】:2014-10-03 08:52:41 【问题描述】:

对我潜在的 nOOb'ness 感到抱歉,但我已经尝试了好几个小时,但似乎无法为 c++ 98 找到一个优雅的解决方案。

我的问题是,假设我有一个字符串向量 a,b,c,d,e,f ,我想将 'e' 移动到第二个元素,我该怎么做?显然,预期的输出现在将打印出 a,e,b,c,d,f

理想情况下,为了提高效率,我希望找到一个让我这样做的单一操作,但很想听听一些关于如何实现这一目标的建议。

谢谢。

【问题讨论】:

如果您经常需要做这种事情,那么std::vector 可能不是容器的最佳选择。 不要使用向量。改用 std::list - cplusplus.com/reference/list/list “单一操作”这个词非常松散。单个 API 调用?见std::rotate。恒定时间插入?完全不同的事情。 Straustrup 建议避免使用 std::list youtube.com/watch?v=YQs6IC-vgmo 相当有趣的讲座。 在接受任何人的建议以切换到std::list 之前,请分析您的代码。我认为在大多数用例中,您会发现 std::vector 的性能优于 std::list,即使由于 std::vector 存储其数据的方式而导致更高复杂性的操作也是如此。 【参考方案1】:

使用std::vector<> 无法“有效”地执行此操作,因为它存储在连续内存中,因此您必须将旧位置和新位置之间的所有内容移动一个元素。所以它是向量长度(或至少移动距离)的线性时间。

天真的解决方案是先到insert(),然后到erase(),但这需要将所有内容移动到您修改的最右边位置之后,两次!因此,您可以“手动”完成,方法是将b 通过d 复制到右侧一个位置(例如,使用std::copy(),然后覆盖b。至少这样您就可以避免在修改范围之外移动任何内容。正如@WhozCraig 在评论中提到的那样,您似乎可以让std::rotate() 这样做。

【讨论】:

谢谢约翰,我想过这样做,但没有追求,因为感觉将所有内容复制 1 会效率低下...? @JoshuaBatty 你会感到惊讶。取决于你正在谈论的序列有多大(更重要的是:)在缓存中可以变得非常有效。一个简单的例子(使用数组而不是向量,只是因为我懒得输入迭代器 =P)can be found here。它是一种选择,但对于大型序列,我可能会使用不同的数据结构,除非这是一个 rare 操作。如果这种情况很少见,并且您大部分时间都在线性枚举该序列,那么向量+旋转可能仍然是必要的。【参考方案2】:

我会先尝试使用std::rotate,如果效率不够高,则只尝试其他手动操作(或矢量以外的容器):

#include <vector>
#include <iostream>
#include <algorithm>

int main()

    // move 5 from 4th to 1st index

    std::vector<int> v 1,2,3,4,5,6;
    // position:        0 1 2 3 4 5

    std::size_t i_old = 4;
    std::size_t i_new = 1;
    auto it = v.begin();

    std::rotate( it + i_new, it + i_old, it + i_old + 1);
    for (int i : v) std::cout << i << ' ';

Live demo.

【讨论】:

【参考方案3】:

编辑正如 cmets 中所述,以下代码实际上模仿了 std::rotate,在所有情况下,这当然比我的手卷代码更受欢迎。


您可以通过 K 次交换来完成此操作,其中 K 是元素之间的距离:

#include <iostream>
#include <string>

using namespace std;

int main()

  string v = "abcdef"; // use string here so output is trivial

  string::size_type insert_index = 1; // at the location of 'b'
  string::size_type move_index = 4; // at the location of 'e'

  while(move_index > insert_index)
  
    std::swap(v[move_index], v[move_index-1]);
    --move_index;
  
  std::cout << v;

Live demo here。注意我使用了std::string,但std::vector 的算法保持不变。 The same can be done with iterators,所以可以推广到没有operator[]的容器。

【讨论】:

感谢@rubenvb!这正是我所追求的。 std::rotate 也可以这样做,它具有距离复杂性。 See it live @WhozCraig 确实如此。我开始写这篇文章时考虑了另一个结果,但实际上,你的方式会更惯用,因为我刚刚重新实现了std::rotate【参考方案4】:

扩展 jrok 的答案,这里是 std::rotate() 的包装器,用于移动单个元素。这比 jrok 的示例更通用,因为它也支持在向量中向前移动元素(而不仅仅是向后移动)。

查看rotate_single() 中的 cmets,解释在前后移动元素时如何交换逻辑。

#include <vector>
#include <stdexcept> // for std::domain_error in range-checking assertion
#include <algorithm> // for std::rotate()

template<class ContiguousContainer>
void assert_valid_idx(ContiguousContainer & v, size_t index)

    // You probably have a preferred assertion mechanism in your code base... 
    // This is just a sample.
    if(index >= v.size())
    
        throw std::domain_error("Invalid index");
    


template<class ContiguousContainer>
void rotate_single(ContiguousContainer & v, size_t from_index, size_t to_index)

    assert_valid_idx(v, from_index);
    assert_valid_idx(v, to_index);

    const auto from_it = v.begin() + from_index;
    const auto to_it   = v.begin() + to_index;
    if(from_index < to_index)
    
        // We're rotating the element toward the back, so we want the new
        // front of our range to be the element just after the "from" iterator
        // (thereby making our "from" iterator the new end of the range).
        std::rotate(from_it, from_it + 1, to_it + 1);
    
    else if(to_index < from_index)
    
        // We're rotating the element toward the front, 
        // so we want the new front of the range to be the "from" iterator.
        std::rotate(to_it, from_it, from_it + 1);
    
    // else the indices were equal, no rotate necessary

你可以play with this in Compiler Explorer——那里有(广泛的)单元测试,但这里有一个说明性示例:

TEST_CASE("Handful of elements in the vector")

    std::vector<int> v1, 2, 3, 4, 5, 6; // Note: this gets recreated for each SECTION() below
    // position:       0  1  2  3  4  5
    
    SECTION("Interior moves")
    
        SECTION("Move 5 from 4th to 1st index")
        
            rotate_single(v, 4, 1);
            CHECK(v == std::vector<int>1, 5, 2, 3, 4, 6);
        

        SECTION("Move 2 from 1st to 4th index")
        
            rotate_single(v, 1, 4);
            CHECK(v == std::vector<int>1, 3, 4, 5, 2, 6);
        
    

    SECTION("Swap adjacent")
    
        rotate_single(v, 4, 5);
        rotate_single(v, 0, 1);
        CHECK(v == std::vector<int>2, 1, 3, 4, 6, 5);
    

【讨论】:

以上是关于C++ 将单个元素移动到向量中的新位置的最简单最有效的方法的主要内容,如果未能解决你的问题,请参考以下文章

C++ 将元素从一个向量移动到另一个向量

C++简单实现vector

如何将列中的所有数据移动到单个列(不合并),然后拆分为R中的新列?

将文本文件中的单个字符存储到向量 C++ 中时的 std::bad_alloc

C++ 将对象向量中的元素复制到具有此元素的向量中

C++ - 在自定义数据类型向量中按值匹配和替换元素