CHEETAH.W

静心积累

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

八数码问题构造数学模型:
每个状态作为节点,则可以进行规则内一步移动的两个节点互相连通,最终构成图。但并非所有节点都连通,根据经验,所有节点构成两个互不连通的子图。儿问题的最终结果只有一个,也就是说,不是所有的情况都能求解得结果。

本文采用C++程序设计的手段构造问题的解决方案,附vs2008工程:Problem_of_Missionary_and_Savage_0430.zip(包括过河(传教士和野人)问题和八数码(九宫格)问题)。程序可以进行任意八数码问题的模拟动画解过程。

解八数码问题的程序核心在于go函数和go_sub函数。

在设计求解之初,考虑完全遍历的方法。9!=362880种情况使简单的完全遍历效果十分不理想,复杂情况下无法求解。必须把问题细分。因此,设计了上面说的两个函数(原来只有一个go函数)。go函数负责把情况分为小块,使用go_sub进行遍历。go函数把情况数组1,利用数组2,变为数组3,传给go_sub计算,求的数组4(0不一定在什么位置,不影响下一轮)。记录求解过程为过程A。

| 2 5 7 |  | 1 1 1 |  | 2 9 9 |  | 1 2 3 |
| 3 4 6 |  | 0 0 0 |  | 3 9 9 |  | 9 9 9 |
| 8 1 0 |  | 0 0 0 |  | 9 1 0 |  | 9 9 0 |

那么下一轮就由下面的数组1(后两行不一定如此,取决于上面一轮的求解过程),利用数组2(为了美观,_表示-1),变为3,传给go_sub变为4,而此时求解过程记为过程B。按照过程A,B,初始情况就变为数组5。

| 1 2 3 |  | _ _ _ |  | 9 9 9 |  | 9 9 9 |  | 1 2 3 |
| 5 4 7 |  | 1 0 0 |  | 9 4 7 |  | 4 9 9 |  | 4 x x |
| 8 0 6 |  | 1 0 0 |  | 9 0 9 |  | 7 9 9 |  | 7 x x |

而并非所有情况都可解,但所有情况都可变为数组5。

接下来同理,利用数组:

| _ _ _ |
| _ 1 1 |
| _ 1 0 |

尝试求最终结果。求解过程构成过程C。综合过程A,B,C,就是可解问题的解。

下面列出核心函数声明代码:

1 /*
2 * 核心运算步骤
3 * sta:输入初始状态向量
4 * q_his:输入空历史状态列表(设计并不合理,输入空向量list即可)
5 * lim:输入需要排序的位置和可以移动的部分(限制活动范围)
6 * 0:可以移动
7 * 1:需要排序
8 * -1:不能移动
9 * return:方法list
10  */
11 list<int> go(vector<int> sta, list< vector<int> >* q_his, vector<int> lim);
12  /*
13 * 核心运算步骤
14 * sta:输入初始状态向量
15 * q_his:输入空历史状态列表(设计并不合理,输入空向量list即可)
16  */
17 list<int> go_sub(vector<int> sta, list< vector<int> >* q_his, vector<int> lim);

下面列出核心函数定义代码(其中的输出语句并非必要,调试用,在实际版本中还有另外一些注释代码没有贴出):

1 /*
2 * 核心运算步骤
3 * sta:输入初始状态向量
4 * q_his:输入空历史状态列表(设计并不合理,输入空向量list即可)
5 * lim:输入需要排序的位置和可以移动的部分(限制活动范围)
6 * 0:可以移动
7 * 1:需要排序
8 * -1:不能移动
9 * return:方法list
10  */
11 list<int> go(vector<int> sta, list< vector<int> >* q_his, vector<int> lim){
12 vector<int> sta_part;
13 sta_part.push_back(0);
14 for(int n=1;n<(signed int)sta.size();n++){
15
16 if(sta[n] == 0){
17 sta_part.push_back(0);
18 }else if(lim[sta[n]-1] == 1){
19 sta_part.push_back(sta[n]);
20 }else if(lim[sta[n]-1] == 0){
21 sta_part.push_back(9);
22 }else{
23 sta_part.push_back(9);
24 }
25 }
26 return go_sub(sta_part,q_his,lim);
27 }
28
29  /*
30 * 核心运算步骤
31 * sta:输入初始状态向量
32 * q_his:输入空历史状态列表(设计并不合理,输入空向量list即可)
33 */
34 list<int> go_sub(vector<int> sta, list< vector<int> >* q_his, vector<int> lim){
35 // 保存要计算的状态队列
36 list< vector<int> > l_sta;
37 // 对应于状态队列,保存移动方案
38 // l_way 中的数据表示依次移动的是第几个位置的元素,并非元素号
39 list< list<int> > l_way;
40 list<int> way;
41
42 l_sta.push_back(sta);
43 l_way.push_back(way);
44 // 当前状态
45 vector<int> sta_now;
46 list<int> way_now;
47
48 while(l_sta.size() != 0){
49 sta_now = l_sta.front(); // 取出最后一个状态
50 l_sta.pop_front();
51 way_now = l_way.front();
52 l_way.pop_front();
53
54 if(true/*l_sta.size()%10 == 0*/){ // 显示一下,便于调试
55 show_sta(&sta_now);
56 cout << " ";
57 cout << l_sta.size();
58 cout << " ";
59 cout << q_his->size();
60 cout << endl;
61 }
62
63 // 检测是否已经得到最终结果
64 if(isOver(&sta_now,&lim)){
65 show_sta(&sta_now);
66 cout << " ";
67 cout << l_sta.size();
68 cout << " ";
69 cout << q_his->size();
70 show_way(&way_now);
71
72 q_his->push_back(sta_now); // 为了求下一个解,这次的结果状态
73
74 return way_now;
75 }
76 // 检测当前状态是否合法,不合法就放弃这个状态,继续拿下一个状态
77 if(check_sta(&sta_now,q_his)){
78 continue;
79 }
80
81 // 移动当前状态得到后继状态
82 move(sta_now,&l_sta,way_now,&l_way,lim);
83
84 }//end while
85 list<int> no_way;
86 return no_way;
87 }

详细过程查阅工程文件。

宿舍的ggm同学提出建立搜索表,这样大大加快运算速度。这么做是可行的。比如对于求解数码1时,其它数码的位置完全不起作用,也就使得求解1的过程完全只跟1自己与0的位置相关(如果每次规定把0移动到右下角,那么与0的位置也无关),这样求解1可以建立8个固定过程,这就是搜索表中数码1的行。同理,数码2需要建立7个过程,数码3需要6个……因此,总搜索表存放了8+7+6+5+4+3+2+1个过程,复杂度很低,是o(n)级别的吧。

不过其中也需要解决一些小问题,比如区分最后是否有解,无解的处理等等。

posted on 2011-05-15 12:15  Ethan.Wong  阅读(1298)  评论(0编辑  收藏  举报