下载演示应用程序 - 86 KB下载Java小程序源 - 67 KB简介
技术,解决困难的问题,使用一个计算系统之一, 是运用蛮力搜索。这意味着穷举搜索 所有可能的组合,直到找到一个解决方案。在本文中,我 将目前实施蛮力搜索算法,可以 适用于各种各样的问题。对这篇文章的想法是采取 由
; 在CodeProject上的文章,其中提出,解决了一个C程序 爱因斯坦的谜。这篇文章的功能扩展, 为了利用面向对象编程的好处,并&
#160; 产生一个通用的求解器类,可以从多个基类 特定问题的求解器类。许多问题的求解器类的例子 将提交,其中包括解决爱因斯坦的里德尔。
注意:执行的语言是C。虽然有些 销售先进的语言功能的使用(模板类,STL),。 在这篇文章中的技术细节都保持在最低限度,使连 经验不足的程序员可以按照蛮力的逻辑 算法,例如拼图的乐趣。
八皇后之谜
我将介绍蛮力搜索开始算法的思想 从八皇后之谜。这是众所周知的配售8问题 非攻击棋盘上的皇后。为解决这一难题的源代码 可以发现在许多软件书籍。在下载一节中,你会发现,
一个Java applet()实现了这个谜团。
计算系统可以帮助我们迅速解决这个谜团。此外,它
; 可以帮助我们找到所有可能的解决方案。为了这样做,我们必须指示我们 8皇后棋盘上的所有可能的展示位置的程序来搜索。 有一个共64!63!62!61!60!59!58!57!不同 配售。尝试搜索所有这些,这将是非常低效 不同的组合。它要解决的问题要好得多 电感。这是首先要解决的问题放置一个皇后在的 一个的1x8的棋盘上,然后放置在一个2X8的两位皇后的问题 棋盘和完整的解决方案继续下去,直到找到。在这种方式 大集团显然是错误的组合的早期发现在 过程不需要进行测试。如果有一对攻击 皇后在棋盘的第n列,则无论身在何处 其余8 - N皇后放置,组合不能构成 解决方案。
;
感性的搜索算法的工作原理如下。我们开始放置
60; 在A1的第一个皇后。第二个皇后被放置在B3(B1和B2
60; 第一皇后的攻击)。然后,我们来看看unattacked平方米 第三列。我们将继续以同样的方式,直到我们到达一列, 没有unattacked广场。在这种情况下,我们返回到上 列和我们尝试将女王摆在那里的另一个unattacked 平方米。我们只审查比以前更高的行广场 之一。如果这是不可能的,我们将另一列,我们尝试 这样做。如果发现,我们在上一篇专栏文章中的一个新的unattacked平方米
0;准备再次向前移动。每次我们都能够放置一个皇后 &
#160; 第八行,发现一个新的解决方案。我们增加一个变量,存储
0; 表中的皇后的位置,我们返回到第7列找到 一个新的unattacked平方米。当我们需要返回,搜索完成 从第2列第1列和王后是摆在A8。一个可视化 该算法可以点击"解决"按钮 以前的小程序。 泛化
感性的搜索算法的概念,可以用在更多的
160; 一般情况。为了能够重新使用的功能
算法,我们将定义一个"抽象"之谜。这种"抽象"之谜 代表一个N细胞的表,必须由充满未来元素 从一个有序列表(见图1)。
{S0}
图1:一般之谜
为了解决这个谜团电感我们先填写1细胞,然后细胞2等
160; 直到所有的细胞都充满了。该算法的流程图
60; 如下图: {S1}
图2:流程图
,对于这种技术的工作,我们必须定义两个功能:
check_placement(M):如果检查的第一分谜语 M细胞是解决
get_next_element(E):它给出一个元素e,返回
160; 紧随其后的元素的有序列表。
160;
C类riddle_solverlt; move_tgt;实现
60; "抽象"之谜。
template <class move_t>
class riddle_solver
{
public:
/** Constructor. */
riddle_solver<move_t>();
/** Destructor. */
riddle_solver<move_t>();
/**
Solves the riddle using inductive searching algorithm.
If not instructed otherwise
(by restricting the number of solutions to search for),
all possible combinations
are searched.
@result Returns true if one or more solutions have been found.
*/
bool solve();
/**
Prints all solutions found.
*/
void print_all();
/**
Sets the maximum number of solutions to search for.
If 0 is given, solve function
will scan all possible combinations.
@param n Maximum number of solutions to search for.
*/
void set_solutions_count(int n);
/**
Sets the size of the riddle, that is the size
of the table it must be filled
to solve the riddle.
@param s Riddle's size.
*/
void set_size(int s);
/**
Sets null element. When null element is given as input
to get_next_element function,
the first element in the ordered element list will
be returned. Null element cannot
be used to fill a cell.
@param mn Null element.
*/
void set_null_element(move_t mn);
private:
/**
tries to fill the next cell in the table.
If this is impossible it will return
false. The algorithm must then return to a previous cell.
It must not be overridden.
@result Returns true if it was possible to make a new move.
*/
bool make_next_move();
/**
It tries to find another valid element in a previous cell.
It will go back as many
cells as required. If this impossible,
false will be returned and the searching
will stop. It must not be overridden.
@result Returns true if a new valid element in
a previous cell has been found.
*/
bool go_back();
protected: // virtual functions
/**
Checks if the subriddle of size moves_count is solved.
It may be overridden. It is
declared virtual, because it is called from solve function
of the base class.
When called in the context of a subclass,
solve function must call check_placement
of the subclass and not this version.
@result Returns moves_count if the subriddle is solved, 0 otherwise.
*/
virtual int check_placement();
/**
Gets next element from the elements ordered list.
@param pos Position of cell that contains the
element to be replaced by the
next one found. A new cell will contain null
element, so that we start the
searching from the first element in the list.
It must be overridden.
@result Returns next element.
*/
virtual move_t get_next_element(int pos);
/**
Registers move at specified position.
In its simplest form, it just inserts
element move at cell pos. It may be overridden.
@param pos Position of cell to place the element.
@param move Element to be placed.
*/
virtual void register_move(int pos, move_t move);
/**
Undoes last move (at position moves_count - 1).
In its simplest form, it just
removes the element placed at the last cell and
fills it with null element. It may
be overridden.
*/
virtual void unregister_last_move();
/**
Prints solution in the standard output.
@param sol Vector containing the solution.
*/
virtual void print_solution(vector<move_t> sol) {;};
protected: // Data members
/// Table storing the moves made.
vector<move_t> moves_array;
/// Riddle's size (moves_array size).
int size;
/// Number of moves made so far.
int moves_count;
private:
/// Null element.
move_t move_null;
/// Maximum number of solutions to look for.
int sol_number_max;
/// Vector for storing all solutions found.
vector<vector<move_t> > sol_v;
};
注:riddle_solverlt; move_tgt;是一个模板 类。 move_t类型定义的元素的类型 填写表中的细胞。在本文的所有例子 riddle_solver将被实例化作为move_t INT。 不过类可用于与任何其他原生类型(如float或double) 或用户定义的类,重载=运算符。
riddle_solverlt; intgt;作为基类,八 一类是可以解决的皇后之谜那样简单: {C}
在下面的段落将被用于riddle_solver类 为解决更多的难题。
爱因斯坦的里德尔
这是谜语,最初给我这篇文章的动机。这 是怎么一回事呢:
0; 五种不同的颜色有5个房子。
; 在每间房子,生活与不同国籍的人。
160;这五个业主喝了某种类型的饮料,烟一定
0; 品牌的雪茄,并保持一定的宠物。 没有业主有相同的宠物,吸烟的同一品牌的雪茄或饮料 &
#160; 相同的饮料。
,对于这一离奇的邻里,我们知道以下内容:
英国人住在红房子。 瑞典人保持作为宠物狗。
60; 丹麦人喝茶。 绿色的房子是在白宫的左侧。 绿房子的主人喝咖啡。 抽Pall Mall香烟养鸟的人。 黄色房子主人抽Dunhill香烟。 该名男子居住在该中心的房子喝牛奶。 挪威人住在第一间房子的。 旁边的人抽烟的混合生活保持猫。 保持马的人住抽Dunhill香烟的人。 吸烟BlueMaster喝啤酒的所有者。 德国王子抽烟。 挪威人住蓝色房子旁边。 的人吸烟的混合有一个喝水的邻居是谁。
现在的问题是:谁拥有的鱼吗?要回答这个问题,必须找到 国籍,肤色,宠物,饮料和烟对应 所有的房子。 {S2}
图3:爱因斯坦的谜语附近
,为了适应谜语我们的抽象谜语的形式,我们定义一个表 大小25。前5个细胞中含有业主的国籍,在未来五年 房子的颜色,那么宠物的饮料和抽烟。这 意味着,我们构建了一个解决方案,同时,我们将首先发现的国籍 所有业主,然后将房屋等的颜色。元素 列表中包含数字1到5,具有明显的顺序。我们有5财产
160; 每5个属性值类型(国籍,肤色,宠物,饮料和烟)。 我们每个属性值映射为一个数字,从1到5。表细胞 充满了从1到5的数字,但是有限制,两院 不能包含任何给定的属性相同的值。实现的细节 出现在文件eisteins_riddle.cpp和einsteins_riddle.hpp。
160; 魔方
一阶幻方ñ是NXN表,其中包含的所有号码从1 nsup2;以这样的方式,所有的行,列和主的总和 对角线上是相同的。为构建一个幻方的算法 任意顺序存在。此外,还有一个 CodeProject上,将做的工作。枚举的问题 幻方的一个特定的顺序听起来更具挑战性 ()。我们将尝试使用riddle_solver 类枚举3阶,4和5的幻方。
在上述抽象谜语的概念,我们要
60; 构造幻方的细胞,通过细胞来访逐一 下面的顺序排列。 {S3}
{A8}图4:访问幻方的顺序
下面列出了实施check_placement 常规。每当一个细胞充满了一个数字,例行 如果某行的检查,一列或主对角线完成。如果这 发生的事情,它计算的总结和测试,如果它是正确的。总和 所有的行,列和对角线幻方NXN的主应 N [nsup2; 1 / 2。
int
magic_square_solver::check_placement()
{
int r, c;
if (moves_count == 0)
return(0);
r = (moves_count-1) / square_size;
c = (moves_count-1) % square_size;
// A row has just been completed
if (c == square_size-1)
{
if (!test_row(r))
return(0);
}
if (r == square_size - 1)
{
// Reverse diagonal has been completed
if (c == 0)
{
if (!test_reverse_diagonal())
return(0);
}
// Diagonal has been completed
if (c == square_size - 1)
{
if (!test_diagonal())
return(0);
}
// A column has just been completed
if (!test_column(c))
return(0);
}
return(moves_count);
}
虽然这段代码可以很好地用于3x3的正方形,你会发现,它 太长任何使用4x4的正方形。使其更快 我们要加强check_placement常规,以便及时发现 早期错误的组合。我们通过修改check_placement实现这一点, 所以,它使每一个新的填充单元测试,不仅为细胞
0; 完成一行,一列或主对角线。对于每一个新填充
; 的细胞,我们计算的部分填充的行和列的款项,
属于。如果它是一个初级对角线的一部分,我们也计算 对角线总和至今。然后我们检查,如有其中部分款项 已经是所需金额较大的,或如果它是比它至少少 应。如果是这样的情况下,我们回到一个细胞。最低金额 部分填充的行/列/对角线的计算公式如下。 如果纳米细胞填充,局部的总和应等于或更大的 ñ [nsup2; 1] / 2 - nsup2; - (nsup2; -1) - ... - (nsup2; - M 1)。
&
#160;
int
magic_square_solver::check_placement()
{
int r, c;
if (moves_count == 0)
return(0);
r = (moves_count-1) / square_size;
c = (moves_count-1) % square_size;
// A row has just been completed
if (c == square_size-1)
{
if (!test_row(r))
return(0);
}
if (r == square_size - 1)
{
// Reverse diagonal has been completed
if (c == 0)
{
if (!test_reverse_diagonal())
return(0);
}
// Diagonal has been completed
if (c == square_size - 1)
{
if (!test_diagonal())
return(0);
}
// A column has just been completed
if (!test_column(c))
return(0);
}
else
{
// Test partial row, column, diagonal
if (c != square_size-1 && !test_partial_row(r, c))
return(0);
if (!test_partial_column(c, r))
return(0);
if (c == r && !test_partial_diagonal(r))
return(0);
if ((c == square_size - r - 1) && !test_partial_reverse_diagonal(r))
return(0);
}
return(moves_count);
}
随着增强check_placement程序,您将能够枚举
0; 几分钟4x4的正方形。比较的结果,你会发现与, 提出在。请注意,如果我们有一个解决方案, 我们不难发现,通过反射和旋转7。如果这些 在同一个解决方案的变化不计,我们必须划分我们的结果 与8。
当我们转移到5x5的正方形,我们注意到,即使是增强版
效率不高。其实有275305224 * 8 5阶幻方。如果
该算法能够找到一个解决方案,每1ms,它仍然
160; 需要25天半来枚举所有的解决方案!无需得到
0; 气馁虽然。一个简单的技巧,我们可以提高的速度
0; 算法。我们要做的是改变顺序,我们走访了细胞:
{S4}的
{A10}图5:访问5x5的幻方的顺序
magic_square_5类解决了5x5的幻方的问题 以下上图中的访问顺序。类只执行
60; 基本的测试,当一排,一个单元或一个主对角线完成。在我 机,它能够找到解决方案,每一个或两个第二。这是 仍然很慢,但我相信,它可以与几个优化 调整列举在一个体面的时期的5阶幻方 时间。
从这个例子可以得出结论,是非常重要的追赶无效
0;尽可能早的组合,即使这意味着check_placement
0; 日常必须作出更复杂,因而更费时。这样做的好处 必须测试的组合数量限制可 在check_placement度过了时间的重要性远远大于。 可以实现相同的效果(限制组合的数量) 改变填充表的顺序。陀螺接龙
这是一个经典的棋盘游戏。要赢得比赛,你必须删除所有的钉子 断板,除了最后一个。一个挂钩可以移动到任意位置,两个 在左,右,同比增长或下降的位置,在另一个PEG跳跃。 " 跃过PEG被删除。你可以玩游戏的Java applet
0;你会发现在下载部分。
为了解决这个谜riddle_solver类,谜语
; 必须定义表和元素的列表。谜语是解决时31 &
#160; 移动,因此应之谜的表大小为31,并包含 赢得比赛所需的所有动作。我们还必须找到一种方法来表示 一招。这样做是基于PEG,是移动和位置 移动的方向(见图4)。 {五}
{A11}图6:移动代表
的举动责令其数值的基础上,这是一个一招 较高的数值是尝试后,移动具有较低的数值。 实现的细节,可以发现在solo.cpp和solo.hpp文件。链接
160; MathWorld - 从MathWorld幻方的信息。
0; 幻方的枚举 - 一个关于枚举的法宝页 正方形。
; 我的主页 - Java的难题。 历史 2004年6月3日。初始发行