20

我有两个向量:

矢量1 = [1 2 3 4 5 6 7 8 9]

矢量2 = [1 2 3 4 5 6 7 8 9]

我想确保,当我使用random_shuffle洗牌时,它们应该以相同的相应顺序洗牌。例如:

洗牌后的输出应该是这样的:

矢量1 = [1 9 3 4 2 7 8 5 6]

矢量2 = [1 9 3 4 2 7 8 5 6]

但我得到的输出如下:

矢量1 = [5 1 7 4 2 3 9 8 6]

矢量2 = [3 4 1 9 8 2 5 7 6]

这是我的代码:

int main () 
{
  std::srand ( unsigned ( std::time(0) ) );
  std::vector<int> vector1, vector2;

  // set some values:
  for (int i=1; i<10; ++i)
  {
    vector1.push_back(i);
    vector2.push_back(i);
  }

  // using built-in random generator:
  std::random_shuffle ( vector1.begin(), vector1.end() );
  std::random_shuffle ( vector2.begin(), vector2.end() );

  // print out content:
  std::cout << "vector1 contains:";
  for ( std::vector<int>::iterator it1 = vector1.begin(); it1 != vector1.end(); ++it1 )
    std::cout << ' ' << *it1;

  std::cout << '\n';
  std::cout << '\n';

  std::cout << "vector2 contains:";
  for ( std::vector<int>::iterator it2 = vector2.begin(); it2 != vector2.end(); ++it2 )
    std::cout << ' ' << *it2;

  std::cout << '\n';
  std::cout << '\n';

  return 0;
}

编辑这是我尝试实现的示例案例。在实践中,我有一个图像向量和一个相应标签的向量。我需要他们以同样的方式洗牌。任何人都可以请帮助......非常感谢!

4

8 回答 8

30

与其打乱向量本身,不如将索引向量打乱到其他向量中。由于您将对两者使用相同的索引,因此可以保证它们的顺序相同。

std::vector<int> indexes;
indexes.reserve(vector1.size());
for (int i = 0; i < vector1.size(); ++i)
    indexes.push_back(i);
std::random_shuffle(indexes.begin(), indexes.end());

std::cout << "vector1 contains:";
for ( std::vector<int>::iterator it1 = indexes.begin(); it1 != indexes.end(); ++it1 )
    std::cout << ' ' << vector1[*it1];
于 2013-06-06T17:07:34.047 回答
17

确保对两个调用使用相同的种子random_shuffle()

auto seed = unsigned ( std::time(0) );

// ...

std::srand ( seed );
std::random_shuffle ( vector1.begin(), vector1.end() );

std::srand ( seed );
std::random_shuffle ( vector2.begin(), vector2.end() );

但是请注意,标准没有指定random_shuffle()应该使用该rand()函数来生成随机排列 - 这是实现定义的。因此,srand()不会影响random_shuffle()不使用rand().

C++11 标准的第 25.3.12/4 段random_shuffle()规定:

备注:在这些函数的实现使用随机数的范围内,实现应使用以下随机源:

第一种形式的函数的随机数的底层来源是实现定义的。实现可以使用rand标准 C 库中的函数。[...]

因此,如果您想确保编写可移植代码,请使用random_shuffle()接受随机数生成器作为第三个参数的版本,以便您可以控制播种。

于 2013-06-06T16:56:27.523 回答
11

正如其他人所展示的那样,使用相同的种子重新播种应该允许您多次复制相同的洗牌。但是,如果您可以使用 C++11,我建议您在不使用srand()and 的情况下实现它random_shuffle();相反,您应该将<random>库与std::shuffle.

首先,如果可能的话rand应该避免。除了它通常不是一个很好的 pRNG 之外,由于共享状态,它还存在线程安全问题。该<random>库通过为程序员提供对 pRNG 状态的显式控制并通过提供几个具有保证性能、大小和质量特征的选项来解决这两个问题。

其次,random_shuffle实际上并没有指定使用rand,所以理论上重新播种是合法的,使用srand不会产生你想要的效果。要获得有保证的结果,random_shuffle您必须编写自己的生成器。转向shuffle修复该问题,因为您可以直接使用标准引擎。

#include <algorithm> // shuffle, copy
#include <iostream>  // cout
#include <iterator>  // begin, end, ostream_iterator
#include <numeric>   // iota
#include <random>    // default_random_engine, random_device
#include <vector>    // vector

int main() {
  std::vector<int> v1(10);
  std::iota(begin(v1), end(v1), 1);
  auto v2 = v1;

  std::random_device r;
  std::seed_seq seed{r(), r(), r(), r(), r(), r(), r(), r()};

  // create two random engines with the same state
  std::mt19937 eng1(seed);
  auto eng2 = eng1;

  std::shuffle(begin(v1), end(v1), eng1);
  std::shuffle(begin(v2), end(v2), eng2);

  std::copy(begin(v1), end(v1), std::ostream_iterator<int>(std::cout, " "));
  std::cout << "\n\n";
  std::copy(begin(v2), end(v2), std::ostream_iterator<int>(std::cout, " "));
  std::cout << "\n\n";
}
于 2013-06-06T18:16:49.287 回答
4

您可以创建一个随机访问迭代器,如果它的取消引用返回一个 std::tuple 到相应向量的元素的引用。因此,您可以将它们随机播放。或者你看看boost版本。所以它应该看起来像这样:

std::random_shuffle(
  boost::make_zip_iterator(
    boost::make_tuple(vector1.begin(), vector2.begin())
  ),
  boost::make_zip_iterator(
    boost::make_tuple(vector1.end(), vector2.end()
  ),

);

这会就地打乱您的数据,使用两个以上的向量,并且如果您知道做什么,则可以自我记录make_zip_iterator。当然它应该比洗牌两次或使用第三个向量更快。

于 2013-06-06T17:16:15.200 回答
2

在每次洗牌之前,为伪随机数生成器播种一个可重复的值。

std::srand ( 42 );
std::random_shuffle ( vector1.begin(), vector1.end() );
std::srand ( 42 );
std::random_shuffle ( vector2.begin(), vector2.end() );
于 2013-06-06T16:55:24.007 回答
2

如果两者必须具有相同的顺序,为什么它们是单独的向量?合乎逻辑的解决方案类似于:

struct ImageData
{
    Image myImage;
    std::string myLabel;
    //  ...
};

然后你有一个ImageData你洗牌的向量。

于 2013-06-06T17:21:16.870 回答
0

不幸的是,如果我们使用 srand,我们会改变内部种子值。我的意思是,下一个随机数将是预先确定的。而且,第一个决定:

std::srand ( 42 );
std::random_shuffle ( vector1.begin(), vector1.end() );
std::srand ( 42 );
std::random_shuffle ( vector2.begin(), vector2.end() );
std::srand ( unsigned ( std::time(0) ) );
// Post-code.

为邮政编码节省兰特。

第二个决定 - 它是 Mark Ransom 解决方案 - 它根本不调用 std::srand (而且,我只是注意到,它具有更高的性能)。

于 2013-06-06T17:22:17.167 回答
-1

你为什么不写你自己的洗牌:

for( size_t i = 0 ; i < numitems; ++i )
{
    size_t next = random() % numitems ;
    swap( v1[i], v1[next] );
    swap( v2[i], v2[next] );
}
于 2013-06-06T17:08:41.973 回答