不使用数组的Magic square的所有解决方案

时间:2018-03-04 07:33:44

标签: c++ for-loop if-statement nested magic-square

是的,这是作业作业。但是,我不希望得到答案。

我应该编写一个程序来输出所有可能的解决方案,如下所示:

+-+-+-+
|2|7|6|
+-+-+-+    
|9|5|1|    
+-+-+-+    
|4|3|8|    
+-+-+-+ 

之前

+-+-+-+    
|2|9|4|    
+-+-+-+    
|7|5|3|    
+-+-+-+    
|6|1|8|    
+-+-+-+

因为276951438小于294753618。

我可以使用for循环(不是嵌套)和if else。解决方案必须按升序排列。我还需要知道这些东西有时看起来更有趣 //比睡觉。

目前,我有:

// generate possible solution (x)
int a, b, c, d, e, f, g, h, i, x;
x = rand() % 987654322 + 864197532;

// set the for loop to list possible values of x.
// This part needs revison
for (x = 123456788; ((x < 987654322) && (sol == true)); ++x)
{
// split into integers to evaluate
    a = x / 100000000;
    b = x % 100000000 / 10000000;
    c = x % 10000000 / 1000000;
    d = x % 1000000 / 100000;
    e = x % 100000 / 10000;
    f = x % 10000 / 1000;
    g = x % 1000 / 100;
    h = x % 100 / 10;
    i = x % 10;

// Could this be condensed somehow?
    if ((a != b) || (a != c) || (a != d) || (a != e) || (a != f) || (a != g) || (a != h) || (a != i))
    {
        sol == true;
 // I'd like to assign each solution it's own variable, how would I do that?
        std::cout << x;
    }
}
How would I output in ascending order?

我之前编写过一个程序,它将用户输入的九位数字放在指定的表中,并验证它是否符合条件(如果每行的总和= 15, n 是幻方解,每个col = 15的总和,每个对角线的总和= 15)所以我可以处理那个部分。我只是不确定如何使用for循环生成九个数字整数的完整列表。有人可以告诉我如何做到这一点以及如何改进我目前的工作?

1 个答案:

答案 0 :(得分:0)

这个问题在我不久前回答SO: magic square wrong placement of some numbers时引起了我的注意。

  

// I'd like to assign each solution it's own variable, how would I do that?

我不会考虑这个。每个找到的解决方案都可以立即打印(而不是存储)。向上计数循环授予输出按顺序

  

我只是不确定如何使用for循环生成九个数字整数的完整列表。

答案是Permutation

在OP的情况下,这是一组9个不同的元素,所有这些元素都具有不同顺序的所有序列。

9位数的可能解决方案数量由factorial计算:

9! = 9·8·7·6·5·4·3·2·1 = 362880

从字面上看,如果要检查9位数的所有可能顺序,则循环必须进行362880次迭代。

谷歌搜索一个准备好的算法(或至少是一些灵感)我发现(令我惊讶的是)C ++ std Algorithms library实际上为此准备好了:

std::next_permutation()

  

将范围[first, last)转换为相对于operator<comp按字典顺序排列的所有排列集合中的下一个排列。如果存在这样的排列,则返回true,否则将范围转换为第一个排列(如std::sort(first, last)所示)并返回false

让事情变得更棘手的是关于禁止数组的限制。假设阵列禁止也禁止std::vectorstd::string,我调查了OP使用一个整数的想法。

32位int涵盖[-2147483648,2147483647]的范围,足以存储数字1 ... 9:987654321的最大排列。(可能是,std::int32_t将是更好的选择。)

提取具有除法和10的模幂的个别数字有点单调乏味。将该集合存储为基数16的数字简化了事情。单个元素(也称为数字)的隔离现在变为按位运算(&|~<<>>)的组合。反向抽取是32位不再足以满足九位数 - 我使用了std::uint64_t

我在class Set16中填充了内容。我考虑提供引用类型和双向迭代器。在摆弄了一段时间之后,我得出的结论是,这并不容易(如果不是不可能的话)。根据cppreference.com上提供的示例代码重新实现std::next_permutation()是我更容易的选择。

362880行的输出对于演示来说有点多。因此,我的样本为3个较小的3位数组做了! (= 6)解决方案:

#include <iostream>
#include <cassert>
#include <cstdint>

// convenience types
typedef unsigned uint;
typedef std::uint64_t uint64;

// number of elements 2 <= N < 16
enum { N = 3 }; 

// class to store a set of digits in one uint64
class Set16 {
  public:
    enum { size = N };

  private:
    uint64 _store; // storage

  public:
    // initializes the set in ascending order.
    // (This is a premise to start permutation at first result.)
    Set16(): _store()
    {
      for (uint i = 0; i < N; ++i) elem(i, i + 1);
    }

    // get element with a certain index.
    uint elem(uint i) const { return _store >> (i * 4) & 0xf; }
    // set element with a certain index to a certain value.
    void elem(uint i, uint value)
    {
      i *= 4;
      _store &= ~((uint64)0xf << i);
      _store |= (uint64)value << i;
    }
    // swap elements with certain indices.
    void swap(uint i1, uint i2)
    {
      uint temp = elem(i1);
      elem(i1, elem(i2));
      elem(i2, temp);
    }
    // reverse order of elements in range [i1, i2)
    void reverse(uint i1, uint i2)
    {
      while (i1 < i2) swap(i1++, --i2);
    }
};

// re-orders set to provide next permutation of set.
// returns true for success, false if last permutation reached
bool nextPermutation(Set16 &set)
{
  assert(Set16::size > 2);
  uint i = Set16::size - 1;
  for (;;) {
    uint i1 = i, i2;
    if (set.elem(--i) < set.elem(i1)) {
      i2 = Set16::size;
      while (set.elem(i) >= set.elem(--i2));
      set.swap(i, i2);
      set.reverse(i1, Set16::size);
      return true;
    }
    if (!i) {
      set.reverse(0, Set16::size);
      return false;
    }
  }
}

// pretty-printing of Set16
std::ostream& operator<<(std::ostream &out, const Set16 &set)
{
  const char *sep = "";
  for (uint i = 0; i < Set16::size; ++i, sep = ", ") out << sep << set.elem(i);
  return out;
}

// main
int main()
{
  Set16 set;
  // output all permutations of sample
  unsigned n = 0; // permutation counter
  do {
#if 1 // for demo:
    std::cout << set << std::endl;
#else // the OP wants instead:
    /* @todo check whether sample builds a magic square
     * something like this:
     * if (
     *   // first row
     *   set.elem(0) + set.elem(1) + set.elem(2) == 15
     * etc.
     */
#endif // 1
    ++n;
  } while(nextPermutation(set));
  std::cout << n << " permutations found." << std::endl;
  // done
  return 0;
}

输出:

1, 2, 3
1, 3, 2
2, 1, 3
2, 3, 1
3, 1, 2
3, 2, 1
6 permutations found.

Life demo on ideone

所以,我在这里:排列没有数组

最后,另一个想法打击了我。可能是,任务的目的是教导“从外面看”......这可能值得再次研究Magic Squares的描述:

  

等效魔方

     

任何魔术方块都可以旋转和反射,以产生8个平凡的不同方块。在魔方理论中,所有这些都被认为是等价的,据说八个这样的方格组成一个等价类。

     

给定订单的幻方数

     

不包括旋转和反射,只有一个3×3魔方......

但是,我不知道如何将其与按升序排序解决方案的要求结合起来。