如何创建向量的笛卡尔积?

30

我有一个二维向量 vector<vector<int> > items ,其大小不同,例如如下所示

1,2,3
4,5
6,7,8

我想要创建这些向量的笛卡尔积组合,例如:

1,4,6
1,4,7
1,4,8
and so on till
3,5,8

我该如何做到这一点?我查阅了几个链接,将它们列在了文章末尾,但由于不太熟悉这门语言,我无法理解。是否有人能帮我一下。

#include <iostream>
#include <iomanip>
#include <vector>

using namespace std;

int main()
{
    vector<vector<int> > items;
    int k = 0;

    for ( int i = 0; i < 5; i++ ) {
        items.push_back ( vector<int>() );

        for ( int j = 0; j < 5; j++ )
            items[i].push_back ( k++ );
    }

    cartesian ( items ); // I want some function here to do this.
}

这个程序有等长的向量,我这样做是为了更容易理解我的数据结构。即使有人使用其他链接中的答案并与此集成以获得结果,这也将非常有帮助。非常感谢。

我查看了几个链接: 一个 两个 程序来源:程序


1
可能是几个向量的笛卡尔积的重复问题。 - BlueRaja - Danny Pflughoeft
@up,这个更年轻但回答更好。 - Kos
回答请求:现有的答案是使用C++03编写的,也许可以使用C++11编写更简洁的代码。此外,已经要求通过堆栈对象递归,而不是通过调用堆栈递归。 - M.M
相关问题,但需要固定数量的向量。 - M.M
@DannyPflughoeft 在我看来不是重复的问题,因为那个问题有一个固定数量的向量,而这个问题是要求一个在运行时不知道向量数量的解决方案。 - M.M
9个回答

19

首先,我会展示给你一个递归版本。

// Cartesion product of vector of vectors

#include <vector>
#include <iostream>
#include <iterator>

// Types to hold vector-of-ints (Vi) and vector-of-vector-of-ints (Vvi)
typedef std::vector<int> Vi;
typedef std::vector<Vi> Vvi;

// Just for the sample -- populate the intput data set
Vvi build_input() {
   Vvi vvi;

   for(int i = 0; i < 3; i++) {
      Vi vi;
      for(int j = 0; j < 3; j++) {
         vi.push_back(i*10+j);
      }
      vvi.push_back(vi);
   }
   return vvi;
}

// just for the sample -- print the data sets
std::ostream&
operator<<(std::ostream& os, const Vi& vi)
{
  os << "(";
  std::copy(vi.begin(), vi.end(), std::ostream_iterator<int>(os, ", "));
  os << ")";
  return os;
}
std::ostream&
operator<<(std::ostream& os, const Vvi& vvi)
{
  os << "(\n";
  for(Vvi::const_iterator it = vvi.begin();
      it != vvi.end();
      it++) {
      os << "  " << *it << "\n";
  }
  os << ")";
  return os;
}

// recursive algorithm to to produce cart. prod.
// At any given moment, "me" points to some Vi in the middle of the
// input data set. 
//   for int i in *me:
//      add i to current result
//      recurse on next "me"
// 
void cart_product(
    Vvi& rvvi,  // final result
    Vi&  rvi,   // current result 
    Vvi::const_iterator me, // current input
    Vvi::const_iterator end) // final input
{
    if(me == end) {
        // terminal condition of the recursion. We no longer have
        // any input vectors to manipulate. Add the current result (rvi)
        // to the total set of results (rvvvi).
        rvvi.push_back(rvi);
        return;
    }

    // need an easy name for my vector-of-ints
    const Vi& mevi = *me;
    for(Vi::const_iterator it = mevi.begin();
        it != mevi.end();
        it++) {
        // final rvi will look like "a, b, c, ME, d, e, f"
        // At the moment, rvi already has "a, b, c"
        rvi.push_back(*it);  // add ME
        cart_product(rvvi, rvi, me+1, end); add "d, e, f"
        rvi.pop_back(); // clean ME off for next round
    }
}

// sample only, to drive the cart_product routine.
int main() {
  Vvi input(build_input());
  std::cout << input << "\n";

  Vvi output;
  Vi outputTemp;
  cart_product(output, outputTemp, input.begin(), input.end());
  std::cout << output << "\n";
}

现在,我将展示从@John那里无耻地窃取的迭代版本,替换了原本的递归版本:

程序的其余部分基本相同,只显示cart_product函数。

// Seems like you'd want a vector of iterators
// which iterate over your individual vector<int>s.
struct Digits {
    Vi::const_iterator begin;
    Vi::const_iterator end;
    Vi::const_iterator me;
};
typedef std::vector<Digits> Vd;
void cart_product(
    Vvi& out,  // final result
    Vvi& in)  // final result

{
    Vd vd;

    // Start all of the iterators at the beginning.
    for(Vvi::const_iterator it = in.begin();
        it != in.end();
        ++it) {
        Digits d = {(*it).begin(), (*it).end(), (*it).begin()};
        vd.push_back(d);
    }


    while(1) {

        // Construct your first product vector by pulling 
        // out the element of each vector via the iterator.
        Vi result;
        for(Vd::const_iterator it = vd.begin();
            it != vd.end();
            it++) {
            result.push_back(*(it->me));
        }
        out.push_back(result);

        // Increment the rightmost one, and repeat.

        // When you reach the end, reset that one to the beginning and
        // increment the next-to-last one. You can get the "next-to-last"
        // iterator by pulling it out of the neighboring element in your
        // vector of iterators.
        for(Vd::iterator it = vd.begin(); ; ) {
            // okay, I started at the left instead. sue me
            ++(it->me);
            if(it->me == it->end) {
                if(it+1 == vd.end()) {
                    // I'm the last digit, and I'm about to roll
                    return;
                } else {
                    // cascade
                    it->me = it->begin;
                    ++it;
                }
            } else {
                // normal
                break;
            }
        }
    }
}

1
仔细看看 main 函数。所有的结果都在 output 变量中。它恰好是一个 std::vector<std::vector<int>>,但你可以很容易地修改它成为一个 std::set<std::vector<int>>。你需要将 rvvi.push_back() 改为 rvvi.insert() - Robᵩ
1
感谢你把我的算法用代码表达出来。别担心,我不会起诉你的。 ;) - John
你所称之为“struct Digits”的实际上只是一个“Digit”(单数)。我知道这似乎微不足道,但已经很难分辨什么是向量,什么不是(以及什么是向量的向量)。 - Arthur Tacca

18

以下是 C++11 的解决方案。

利用模数算术,可以优雅地对变量大小的数组进行索引。

输出中的总行数是输入向量大小的乘积。即:

N = v[0].size() * v[1].size() * v[2].size()

因此,主循环的迭代变量为n,从0N-1。原则上,每个n的值都编码了足够的信息,可以在该迭代中提取v的每个索引。这是通过使用重复模算术的子循环来完成的:
#include <cstdlib>
#include <iostream>
#include <numeric>
#include <vector>
using namespace std;

void cartesian( vector<vector<int> >& v ) {
  auto product = []( long long a, vector<int>& b ) { return a*b.size(); };
  const long long N = accumulate( v.begin(), v.end(), 1LL, product );
  vector<int> u(v.size());
  for( long long n=0 ; n<N ; ++n ) {
    lldiv_t q { n, 0 };
    for( long long i=v.size()-1 ; 0<=i ; --i ) {
      q = div( q.quot, v[i].size() );
      u[i] = v[i][q.rem];
    }
    // Do what you want here with u.
    for( int x : u ) cout << x << ' ';
    cout << '\n';
  }
}

int main() {
  vector<vector<int> > v { { 1, 2, 3 },
                           { 4, 5 },
                           { 6, 7, 8 } };
  cartesian(v);
  return 0;
}

输出:

1 4 6 
1 4 7 
1 4 8 
...
3 5 8

why #include <cstdlib>? - 7kemZmani
现在在g++中,它可以正常工作,而无需显式地#include <cstdlib>头文件! - 7kemZmani
这是因为头文件会包含其他头文件。如果您尝试编译 int main() { return div(12,3).rem; } 而没有包含任何头文件,那么您将收到类似于 error: ‘div’ was not declared in this scope 的消息。 - Matt

17

代码更短:

vector<vector<int>> cart_product (const vector<vector<int>>& v) {
    vector<vector<int>> s = {{}};
    for (const auto& u : v) {
        vector<vector<int>> r;
        for (const auto& x : s) {
            for (const auto y : u) {
                r.push_back(x);
                r.back().push_back(y);
            }
        }
        s = move(r);
    }
    return s;
}

非常好!有两个小的清理:const auto& us = std::move(r); - Vlad Shcherbina
@VladShcherbina:完成。 - anumi
简短、干净,非常容易转换为模板函数。 - Kjeld Schmidt

4
看起来你需要一个迭代器的vector,它可以遍历您单独的vector。
将所有迭代器从开始处开始。通过迭代器提取每个vector的元素,构造第一个乘积向量。
递增最右边的迭代器,并重复此过程。
当到达结尾时,将该迭代器重置为开始,并递增次右边的迭代器。通过从迭代器的相邻元素中取出它,可以获得“次右边”迭代器。
继续循环,直到最后和次右边的迭代器都在结尾处。然后,将它们两个重置,递增第三个迭代器。通常,这可以级联。
就像里程表一样,但每个不同的数字位于不同的基数中。

你能提供一个循环的例子吗? - 0x0
我可以解释原理,但编码可能需要一些时间,因为我还不是STL的高手。 :) - John

3
这是我的解决方案。也是迭代的,但比上面的略短一些...
void xp(const vector<vector<int>*>& vecs, vector<vector<int>*> *result) {
  vector<vector<int>*>* rslts;
  for (int ii = 0; ii < vecs.size(); ++ii) {
    const vector<int>& vec = *vecs[ii];
    if (ii == 0) {
      // vecs=[[1,2],...] ==> rslts=[[1],[2]]
      rslts = new vector<vector<int>*>;
      for (int jj = 0; jj < vec.size(); ++jj) {
        vector<int>* v = new vector<int>;
        v->push_back(vec[jj]);
        rslts->push_back(v);
      }
    } else {
      // vecs=[[1,2],[3,4],...] ==> rslts=[[1,3],[1,4],[2,3],[2,4]]
      vector<vector<int>*>* tmp = new vector<vector<int>*>;
      for (int jj = 0; jj < vec.size(); ++jj) {  // vec[jj]=3 (first iter jj=0)
        for (vector<vector<int>*>::const_iterator it = rslts->begin();
             it != rslts->end(); ++it) {
          vector<int>* v = new vector<int>(**it);       // v=[1]
          v->push_back(vec[jj]);                        // v=[1,3]
          tmp->push_back(v);                            // tmp=[[1,3]]
        }
      }
      for (int kk = 0; kk < rslts->size(); ++kk) {
        delete (*rslts)[kk];
      }
      delete rslts;
      rslts = tmp;
    }
  }
  result->insert(result->end(), rslts->begin(), rslts->end());
  delete rslts;
}

我费尽心思从我写的Haskell版本中推导出来:

xp :: [[a]] -> [[a]]
xp [] = []
xp [l] = map (:[]) l
xp (h:t) = foldr (\x acc -> foldr (\l acc -> (x:l):acc) acc (xp t)) [] h

感谢您的努力。我非常感激您的帮助! :-) - 0x0
2
在 Haskell 中,我会写成 xp = sequence - Matt W-D

1

由于我需要相同的功能,因此我实现了一个迭代器,可以根据需要动态计算笛卡尔积并遍历它。

使用方法如下。

#include <forward_list>
#include <iostream>
#include <vector>
#include "cartesian.hpp"

int main()
{
    // Works with a vector of vectors
    std::vector<std::vector<int>> test{{1,2,3}, {4,5,6}, {8,9}};
    CartesianProduct<decltype(test)> cp(test);
    for(auto const& val: cp) {
        std::cout << val.at(0) << ", " << val.at(1) << ", " << val.at(2) << "\n";
    }

    // Also works with something much less, like a forward_list of forward_lists
    std::forward_list<std::forward_list<std::string>> foo{{"boo", "far", "zab"}, {"zoo", "moo"}, {"yohoo", "bohoo", "whoot", "noo"}};
    CartesianProduct<decltype(foo)> bar(foo);
    for(auto const& val: bar) {
        std::cout << val.at(0) << ", " << val.at(1) << ", " << val.at(2) << "\n";
    }
}

文件cartesian.hpp的内容如下:
#include <cassert>

#include <limits>
#include <stdexcept>
#include <vector>

#include <boost/iterator/iterator_facade.hpp>

//! Class iterating over the Cartesian product of a forward iterable container of forward iterable containers
template<typename T>
class CartesianProductIterator : public boost::iterator_facade<CartesianProductIterator<T>, std::vector<typename T::value_type::value_type> const, boost::forward_traversal_tag>
{
    public:
        //! Delete default constructor
        CartesianProductIterator() = delete;

        //! Constructor setting the underlying iterator and position
        /*!
         * \param[in] structure The underlying structure
         * \param[in] pos The position the iterator should be initialized to.  std::numeric_limits<std::size_t>::max()stands for the end, the position after the last element.
         */
        explicit CartesianProductIterator(T const& structure, std::size_t pos);

    private:
        //! Give types more descriptive names
        // \{
        typedef T OuterContainer;
        typedef typename T::value_type Container;
        typedef typename T::value_type::value_type Content;
        // \}

        //! Grant access to boost::iterator_facade
        friend class boost::iterator_core_access;

        //! Increment iterator
        void increment();

        //! Check for equality
        bool equal(CartesianProductIterator<T> const& other) const;

        //! Dereference iterator
        std::vector<Content> const& dereference() const;

        //! The part we are iterating over
        OuterContainer const& structure_;

        //! The position in the Cartesian product
        /*!
         * For each element of structure_, give the position in it.
         * The empty vector represents the end position.
         * Note that this vector has a size equal to structure->size(), or is empty.
         */
        std::vector<typename Container::const_iterator> position_;

        //! The position just indexed by an integer
        std::size_t absolutePosition_ = 0;

        //! The begin iterators, saved for convenience and performance
        std::vector<typename Container::const_iterator> cbegins_;

        //! The end iterators, saved for convenience and performance
        std::vector<typename Container::const_iterator> cends_;

        //! Used for returning references
        /*!
         * We initialize with one empty element, so that we only need to add more elements in increment().
         */
        mutable std::vector<std::vector<Content>> result_{std::vector<Content>()};

        //! The size of the instance of OuterContainer
        std::size_t size_ = 0;
};

template<typename T>
CartesianProductIterator<T>::CartesianProductIterator(OuterContainer const& structure, std::size_t pos) : structure_(structure)
{
    for(auto & entry: structure_) {
        cbegins_.push_back(entry.cbegin());
        cends_.push_back(entry.cend());
        ++size_;
    }

    if(pos == std::numeric_limits<std::size_t>::max() || size_ == 0) {
        absolutePosition_ = std::numeric_limits<std::size_t>::max();
        return;
    }

    // Initialize with all cbegin() position
    position_.reserve(size_);
    for(std::size_t i = 0; i != size_; ++i) {
        position_.push_back(cbegins_[i]);
        if(cbegins_[i] == cends_[i]) {
            // Empty member, so Cartesian product is empty
            absolutePosition_ = std::numeric_limits<std::size_t>::max();
            return;
        }
    }

    // Increment to wanted position
    for(std::size_t i = 0; i < pos; ++i) {
        increment();
    }
}

template<typename T>
void CartesianProductIterator<T>::increment()
{
    if(absolutePosition_ == std::numeric_limits<std::size_t>::max()) {
        return;
    }

    std::size_t pos = size_ - 1;

    // Descend as far as necessary
    while(++(position_[pos]) == cends_[pos] && pos != 0) {
        --pos;
    }
    if(position_[pos] == cends_[pos]) {
        assert(pos == 0);
        absolutePosition_ = std::numeric_limits<std::size_t>::max();
        return;
    }
    // Set all to begin behind pos
    for(++pos; pos != size_; ++pos) {
        position_[pos] = cbegins_[pos];
    }
    ++absolutePosition_;
    result_.emplace_back();
}

template<typename T>
std::vector<typename T::value_type::value_type> const& CartesianProductIterator<T>::dereference() const
{
    if(absolutePosition_ == std::numeric_limits<std::size_t>::max()) {
        throw new std::out_of_range("Out of bound dereference in CartesianProductIterator\n");
    }
    auto & result = result_[absolutePosition_];
    if(result.empty()) {
        result.reserve(size_);
        for(auto & iterator: position_) {
            result.push_back(*iterator);
        }
    }

    return result;
}

template<typename T>
bool CartesianProductIterator<T>::equal(CartesianProductIterator<T> const& other) const
{
    return absolutePosition_ == other.absolutePosition_ && structure_ == other.structure_;
}

//! Class that turns a forward iterable container of forward iterable containers into a forward iterable container which iterates over the Cartesian product of the forward iterable containers
template<typename T>
class CartesianProduct
{
    public:
        //! Constructor from type T
        explicit CartesianProduct(T const& t) : t_(t) {}

        //! Iterator to beginning of Cartesian product
        CartesianProductIterator<T> begin() const { return CartesianProductIterator<T>(t_, 0); }

        //! Iterator behind the last element of the Cartesian product
        CartesianProductIterator<T> end() const { return CartesianProductIterator<T>(t_, std::numeric_limits<std::size_t>::max()); }

    private:
        T const& t_;
};

如果有人对如何使其更快或更好有评论,我会非常感激。


我不知道为什么你的答案被忽略了,但至少在我看来,它看起来更有趣,因为它避免了存储笛卡尔积的成本。我还没有尝试过你的代码,但那正是我需要的。 - akim
@akim:不幸的是,在计算时必须将其存储。这是因为它需要返回一个引用。更改这一点并不难,但是据我所见,就不能再使用标准迭代器了,因为它们要求返回一个引用。因此,如果您有巨大的笛卡尔积,您可能希望采用这种方式,而不是像范围基础循环之类的好东西。 - Xoph
是的,我同意,需要一些不那么可爱的解决方案。实际上,因为我需要一个带有std::vectorstd::tuple,所以我现在使用类似于此提案中的for_imp:https://dev59.com/IGYr5IYBdhLWcg3wRoOW,但使用类似于C++14的`index_sequence`。 - akim

0
我刚刚被迫为我正在处理的项目实现这个功能,并编写了下面的代码。它可以放在头文件中,使用非常简单,但它返回从向量的向量中获取的所有组合。它返回的数组仅包含整数。这是一个有意识的决定,因为我只想要索引。通过这种方式,我可以索引到每个向量的向量,然后执行我/任何人需要的计算...最好避免让CartesianProduct本身持有“东西”,它是基于计数的数学概念,而不是数据结构。我对c++还比较新,但这在解密算法中得到了充分的测试。虽然有一些轻微的递归,但总体上这是一个简单的计数概念的简单实现。
// Use of the CartesianProduct class is as follows. Give it the number
// of rows and the sizes of each of the rows. It will output all of the 
// permutations of these numbers in their respective rows.
// 1. call cp.permutation() // need to check all 0s.
// 2. while cp.HasNext() // it knows the exit condition form its inputs.
// 3.   cp.Increment() // Make the next permutation
// 4.   cp.permutation() // get the next permutation

class CartesianProduct{
  public:
  CartesianProduct(int num_rows, vector<int> sizes_of_rows){
    permutation_ = new int[num_rows];
    num_rows_ = num_rows;
    ZeroOutPermutation();
    sizes_of_rows_ = sizes_of_rows;
    num_max_permutations_ = 1;
    for (int i = 0; i < num_rows; ++i){
      num_max_permutations_ *= sizes_of_rows_[i]; 
    }
  }

  ~CartesianProduct(){
    delete permutation_;
  }

  bool HasNext(){
    if(num_permutations_processed_ != num_max_permutations_) {
      return true;
    } else {
      return false;
    }
  }

 void Increment(){
    int row_to_increment = 0;
    ++num_permutations_processed_;
    IncrementAndTest(row_to_increment);
  }

  int* permutation(){
    return permutation_;
  }

  int num_permutations_processed(){
    return num_permutations_processed_;
  }
  void PrintPermutation(){
    cout << "( ";
    for (int i = 0; i < num_rows_; ++i){
      cout << permutation_[i] << ", ";
    }
    cout << " )" << endl;
  }

private:
  int num_permutations_processed_;
  int *permutation_;
  int num_rows_;
  int num_max_permutations_;
  vector<int> sizes_of_rows_;

  // Because CartesianProduct is called first initially with it's values
  // of 0 and because those values are valid and important output
  // of the CartesianProduct we increment the number of permutations
  // processed here when  we populate the permutation_ array with 0s.
  void ZeroOutPermutation(){
    for (int i = 0; i < num_rows_; ++i){
      permutation_[i] = 0;
    }

    num_permutations_processed_ = 1;
  }

  void IncrementAndTest(int row_to_increment){
    permutation_[row_to_increment] += 1;
    int max_index_of_row = sizes_of_rows_[row_to_increment] - 1;
    if (permutation_[row_to_increment] > max_index_of_row){
      permutation_[row_to_increment] = 0;
      IncrementAndTest(row_to_increment + 1);
    }
  }
};

0

这个版本不支持迭代器或范围,但它是一个简单的直接实现,使用乘法运算符表示笛卡尔积,并使用 lambda 执行操作。

接口是根据我需要的特定功能设计的。我需要灵活地选择向量来应用笛卡尔积,以一种不会使代码变得模糊的方式。

int main()
{
    vector< vector<long> > v{ { 1, 2, 3 }, { 4, 5 }, { 6, 7, 8 } };
    (Cartesian<long>(v[0]) * v[1] * v[2]).ForEach(
        [](long p_Depth, long *p_LongList)
        {
            std::cout << p_LongList[0] << " " << p_LongList[1] << " " << p_LongList[2] << std::endl;
        }
    );
}

该实现使用递归来实现嵌套的for循环,遍历每个向量。该算法直接在输入向量上工作,不需要大型临时数组。它易于理解和调试。

使用std::function p_Action而不是void p_Action(long p_Depth, T *p_ParamList)作为lambda参数,如果我想要捕获局部变量,那么就可以了。在上面的调用中,我不需要。

但你知道这一点,不是吗。"function"是一个模板类,它接受函数的类型参数并使其可调用。

#include <vector>
#include <iostream>
#include <functional>
#include <string>
using namespace std;

template <class T>
class Cartesian
{
private:
    vector<T> &m_Vector;
    Cartesian<T> *m_Cartesian;
public:
    Cartesian(vector<T> &p_Vector, Cartesian<T> *p_Cartesian=NULL)
        : m_Vector(p_Vector), m_Cartesian(p_Cartesian)
    {};
    virtual ~Cartesian() {};
    Cartesian<T> *Clone()
    {
        return new Cartesian<T>(m_Vector, m_Cartesian ? m_Cartesian->Clone() : NULL);
    };
    Cartesian<T> &operator *=(vector<T> &p_Vector)
    {
        if (m_Cartesian)
            (*m_Cartesian) *= p_Vector;
        else
            m_Cartesian = new Cartesian(p_Vector);
        return *this;
    };
    Cartesian<T> operator *(vector<T> &p_Vector)
    {
        return (*Clone()) *= p_Vector;
    };
    long Depth()
    {
        return m_Cartesian ? 1 + m_Cartesian->Depth() : 1;
    };
    void ForEach(function<void (long p_Depth, T *p_ParamList)> p_Action)
    {
        Loop(0, new T[Depth()], p_Action);
    };
private:
    void Loop(long p_Depth, T *p_ParamList, function<void (long p_Depth, T *p_ParamList)> p_Action)
    {
        for (T &element : m_Vector)
        {
            p_ParamList[p_Depth] = element;
            if (m_Cartesian)
                m_Cartesian->Loop(p_Depth + 1, p_ParamList, p_Action);
            else
                p_Action(Depth(), p_ParamList);
        }
    };
};

0
#include <iostream>
#include <vector>

void cartesian (std::vector<std::vector<int>> const& items) {
  auto n = items.size();
  auto next = [&](std::vector<int> & x) {
      for ( int i = 0; i < n; ++ i ) 
        if ( ++x[i] == items[i].size() ) x[i] = 0; 
        else return true;
      return false;
  };
  auto print = [&](std::vector<int> const& x) {
    for ( int i = 0; i < n; ++ i ) 
      std::cout << items[i][x[i]] << ",";
    std::cout << "\b \n";
  };
  std::vector<int> x(n);
  do print(x); while (next(x)); // Shazam!
}

int main () {
  std::vector<std::vector<int>> 
    items { { 1, 2, 3 }, { 4, 5 }, { 6, 7, 8 } };
  cartesian(items);
  return 0;
}

这背后的想法如下。

n := items.size()
对于所有 i 属于 {0,1,...,n-1},令 m_i := items[i].size()
M := {0,1,...,m_0-1} x {0,1,...,m_1-1} x ... x {0,1,...,m_{n-1}-1}

我们首先解决更简单的问题,即遍历 M。这可以通过 next lambda 实现。该算法只是小学生用来加 1 的“进位”例程,尽管使用了混合基数数字系统。

我们使用此方法通过公式 items[i][x[i]](其中 i 属于 {0,1,...,n-1})将元组 x 转换为所需的元组之一。我们在 print lambda 中执行此转换。

然后,我们使用 do print(x); while (next(x)); 进行迭代。

现在对复杂度进行一些评论,假设对于所有的im_i > 1

  • 该算法需要O(n)的空间。请注意,显式构造笛卡尔积需要O(m_0 m_1 m_2 ... m_{n-1}) >= O(2^n)的空间。因此,这比任何需要同时在内存中存储所有元组的算法在空间上更具指数级的优势。
  • next函数的平摊时间为O(1)(通过几何级数论证)。
  • print函数需要O(n)的时间。
  • 因此,总体而言,该算法的时间复杂度为O(n|M|),空间复杂度为O(n)(不计算存储items的成本)。
一个有趣的事情要注意的是,如果将print替换为一个函数,该函数平均只检查每个元组中的O(1)个坐标而不是所有坐标,则时间复杂度降至O(|M|),也就是说,它随着笛卡尔积大小的增加呈线性增长。换句话说,在某些情况下避免每次迭代都复制元组可能是有意义的。

啊,最后一个注释为什么相关的例子:假设我们将多维数组作为连续数组存储在内存中。现在假设我们想要迭代这个数组的“切片”,即将每个坐标限制为值的子集。然后我们希望在O(1)时间内计算出切片中每个条目在原始数组中的地址,而不是在迭代过程中花费O(n)时间。 - Shaun Harker

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接