我正在用PHP编写一个算法来解决给定的数独谜题。我已经建立了一个有点面向对象的实现,它有两个类:9x9板上每个单独瓦片的Square
类,以及Sudoku
类,其矩阵为Square
代表董事会。
我正在使用的算法的实现是一种三层方法。第一步,只解决最基本的谜题(但效率最高),是填写任何只能根据电路板的初始设置获取单个值的方块,并相应地调整其他方面的约束。未解决的方块。
通常,这种“持续传播”的过程并不能完全解决电路板问题,但它确实解决了相当大的问题。然后第二层将启动。这解析每个单元(或9个正方形,其必须全部具有唯一的编号分配,例如行或列),用于每个未解决的正方形的“可能”值。此可能值列表在Square
类中表示为字符串:
class Square {
private $name; // 00, 01, 02, ... , 86, 87, 88
private $peers; // All squares in same row, col, and box
private $number; // Assigned value (0 if not assigned)
private $possibles; // String of possible numbers (1-9)
public function __construct($name, $p = 0) {
$this->name = $name;
$this->setNumber($p);
if ($p == 0) {
$this->possibles = "123456789";
}
}
// ... other functions
给定一个单元中的一系列未解决的正方形(如上面第二层所述),第二层将所有“可能”的字符串连接成一个字符串。然后,它将在该单个字符串中搜索任何唯一的字符值 - 不重复的值。这将表明,在正方形单位内,只有一个正方形可以承担该特定值。
我的问题是:为了实现第二层,如何解析单元中所有可能值的字符串并轻松检测唯一值?我知道我可以创建一个数组,其中每个索引由数字1-9表示,我可以将相应索引处的值递增1,找到我找到的那个数字的每个可能值,然后再次扫描数组值为1,但这似乎非常低效,每个单元需要对阵列进行两次线性扫描,而在Sudoku难题中则有27个单位。
答案 0 :(得分:3)
这有点像你已经排除的“非常低效”,但内置函数所以它可能非常有效:
$all_possibilities = "1234567891234";
$unique = array();
foreach (count_chars($all_possibilities, 1) as $c => $occurrences) {
if ($occurrences == 1)
$unique[] = chr($c);
}
print join("", $unique) . "\n";
版画:“56789”
答案 1 :(得分:1)
考虑使用二进制数代表你的“可能”,因为像AND,OR,XOR这样的二进制运算往往比字符串运算快得多。
E.g。如果正方形可以使用“2”和“3”,则使用二进制数000000110来表示该正方形的可能性。
以下是您可以找到唯一身份的方式:
$seenonce = 0;
$seenmore = 0;
foreach(all_possibles_for_this_unit as $possibles) {
$seenmore |= ($possibles & $seenonce);
$seenonce |= $possibles;
}
$seenonce ^= $seenmore;
if ($seenonce) {
//something was seen once - now it must be located
}
我不确定这种方法是否会更快地运行,但值得研究。
答案 2 :(得分:0)
function singletonsInString($instring) {
$results = array();
for($i = 1; $i < 10; $i++) {
$first_pos = strpos($instring, str($i));
$last_pos = strrpos($instring, str($i));
if ( $first_pos !== FALSE and $first_pos == $last_pos )
$results[] = $i;
}
return $results;
}
那会给你每一个单身人士。获取该数组中数字的第一个和最后一个位置,如果它们匹配且不是FALSE(严格比较,如果在开始时有一个单例),那么该数组中只有一个这样的数字。
如果你在这里超级担心速度,你可以用
替换那个循环的内部 $istr = str($i);
if ( ($first = strpos($instring, $istr)) !== FALSE
and $first == $strrpos($instring, $istr) ) $results[] = $i;
用于最小数量的计算。好吧,假设PHP的原生strpos是处理这些事情的最佳方式,据我所知这并非不合理。
答案 3 :(得分:0)
我最后一次骗过Sudoku解决,我有一个名为“Run”的第三个类。为每行col和3x3 square创建一个Run实例。每个方块都有三个与之相关的运行。 Run类包含尚未放置在运行中的数字集。然后解决板然后迭代地在每个方格处交叉集合。这样可以处理80%的大多数中型板和60%的大多数硬板。一旦你完成整个电路板而没有任何变化,你就可以继续使用更高级别的逻辑。每当你的高级逻辑填充一个正方形时,你再次穿过正方形。
关于此设置的好处是您可以轻松地向变换器添加变体。假设您使用两个对角线也是唯一的变体。你只需要为这18个方格添加第4轮。
答案 4 :(得分:0)
我会做的,实际上是使用二进制位来存储实际值作为建议的另一个答案。这允许进行有效的检查,并且通常可以将Sudoku本身借给更多数学(=有效且更短)的解决方案(仅仅是我的印象,我还没有研究过这个)。
基本上,你用正方形表示数字而不是数字,但功率为2
"1" = 2^0 = 1 = 000000001
"2" = 2^1 = 2 = 000000010
"3" = 2^2 = 4 = 000000100
"4" = 2^3 = 8 = 000001000
... etc up to
"9" = 2^8 = 256= 100000000
通过这种方式,您可以简单地添加单个方块的内容,以找出3x3或行中的数字或数据的任何其他子集,如下所示:
// shows the possibles for 3x3 square number 1 (00-22)
$sum=0;
for ($i=0; $i< 3; $i++)
for ($j=0; $j < 3; $j++)
$sum += $square["${i}${j}"]->number
$possibles = $sum ^ 511 // ^ stands for bitwise XOR and 511 is binary 11111111
现在$ possibles在此方块中可能包含的数字位位置包含“1”,您可以对其他方块的结果进行按位运算以将它们匹配在一起,如下所示:
例如。让我们说:
$possibles1 = 146 // is binary 100100101,
//indicating that this row or 3x3 square has place for "9", "6", "3" and "1"
$possibles2 = 7 // is binary 000000111, indicating it has place for "3", "2" and "1".
// so:
$possibles1 & $possibles2
// bitwise AND, will show binary 101 saying that "3" and "1" is unfilled in both bloces
$possibles1 | $possibles2
// bitwise OR will give that in total it is possible to use "9", "6", "3", "2" and "1" in those two squares together
答案 5 :(得分:0)
这是一种仅使用PHP内置函数的方法,它应该非常快。
function getUniques($sNumbers)
{
return join(array_keys(array_count_values(str_split($sNumbers)),1));
}
echo getUniques("1234567891234"); // return 56789;