八数码问题的三种算法解答(C#源代码)

本文介绍了一种解决八数码问题的高效方法,重点在于状态编码技巧。通过将状态压缩为int值并利用特定规则表示空格位置,文章提供了一个简洁且高效的算法实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

详细工程文件的下载地址: http://download.youkuaiyun.com/source/195320

/*----------------------------------------------------------------
          // Copyright (C) 2007 Fengart
          // 版权所有。 
          // 开发者:Fengart
          // 文件名:FengartAI.cs
          // 文件功能描述:这是一个能够解决八数码问题的高效方法,主要体现在对状态编码上。
 /*----------------------------------------------------------------
    //主页:
http://fengart.9126.com
    //blog: 
http://blog.youkuaiyun.com/fengart
    //Emial: fengart@126.com
//----------------------------------------------------------------
*/

using  System;
using  System.Collections.Generic;

namespace  Eight_Num_Fengart
{
    
/// <summary>
    
///  八数码问题的算法
    
/// </summary>

    class FengartAI
    
{
        
代码说明 

        
/// <summary>
        
/// ten[i]代表10的i次方
        
/// </summary>

        private static readonly long[] tens =1101001000100001000001000000100000001000000001000000000 };

        
/// <summary>
        
/// 不是合理的编码
        
/// </summary>

        private const int OutOfCode = -1;

        
/// <summary>
        
/// 标志是否找到目标状态的编码
        
/// </summary>

        public const int WinnerCode = 1;

        
private Direction[][] dirs;

        
private int startBoard;
        
private static int endBoard;
        
private static int[] endBoardArray;

        
private int MaxDepth;

        
private SortedList<long,StateMsg> openList;
        
private Stack<State> openStack;
        
private Queue<State> openQueue;
        
private Dictionary<long,StateMsg> boardtable;

        
private const int maxNodes = 362880//至多搜索的结点数=最大局面状态数量:(9!)=362880;

        
private int nodes;
        
private int same;
        
private float time;
        
private Direction[] result;

        
/// <summary>
        
/// 已经访问的结点数量
        
/// </summary>

        public int Nodes
        
{
            
get return nodes; }
        }


        
/// <summary>
        
/// 重复访问相同结点的数量
        
/// </summary>

        public int Same
        
{
            
get return same; }
        }


        
public float Time
        
{
            
get return time; }
        }


        
/// <summary>
        
/// 最终结果
        
/// </summary>

        public Direction[] Result
        
{
            
get return result; }
        }


        
public FengartAI()
        
{
            dirs 
= new Direction[9][];
            dirs[
0= new Direction[] { Direction.Right, Direction.Down };
            dirs[
1= new Direction[] { Direction.Left, Direction.Right, Direction.Down };
            dirs[
2= new Direction[] { Direction.Left, Direction.Down };
            dirs[
3= new Direction[] { Direction.Up, Direction.Right, Direction.Down };
            dirs[
4= new Direction[] { Direction.Up, Direction.Left, Direction.Right, Direction.Down };
            dirs[
5= new Direction[] { Direction.Up, Direction.Left, Direction.Down };
            dirs[
6= new Direction[] { Direction.Up, Direction.Right };
            dirs[
7= new Direction[] { Direction.Left, Direction.Right, Direction.Up };
            dirs[
8= new Direction[] { Direction.Up, Direction.Left };

        }


        
/// <summary>
        
/// 求与目标位置不同的个数(不计空格,因此返回值0~8)
        
/// </summary>
        
/// <param name="curboard"></param>
        
/// <returns></returns>

        public static int Different(int curboard)
        
{
            
int t_start = curboard;
            
int emp_start = curboard % 10;
            
int ev = 0;
            
//写2个for是为了减少9个if
            for (int i = 9; i > emp_start; i--)
            
{
                t_start 
/= 10;
                
if (t_start % 10 != endBoardArray[i])
                    ev
++;
            }

            
for (int i = emp_start - 1; i >= 1; i--)
            
{
                t_start 
/= 10;
                
if (t_start % 10 != endBoardArray[i])
                    ev
++;
            }

            
return ev;
        }


        
public static int getBoard(long code)
        
{
            
return (int)(code % tens[9]);
        }


        
private static int getEval(long code)
        
{
            
return (int)(code / tens[9]);
        }


        
private static int getEmpIndex(long code)
        
{
            
return (int)(code % 10);
        }


        
private static long combinCode(int board, int eval)
        
{
            
long codehead = eval * tens[9];
            
return codehead + board;
        }


        
/// <summary>
        
/// 改变局面(移动空格)
        
/// </summary>
        
/// <param name="code"></param>
        
/// <param name="dir"></param>

        public static long change(long code, Direction dir)
        
{
            
int newboard;
            
int eval;
            
int num;
            
int t0;
            
long t1;
            
long t2;
            
switch (dir)
            
{
                
case Direction.Left:
                    newboard 
= getBoard(code) - 1;
                    
if (newboard == endBoard)
                        
return WinnerCode;
                    eval 
= Different(newboard);
                    
return combinCode(newboard, eval);
                
case Direction.Right:
                    newboard 
= getBoard(code) + 1;
                    
if (newboard == endBoard)
                        
return WinnerCode;
                    eval 
= Different(newboard);
                    
return combinCode(newboard, eval);
                
case Direction.Up:
                    num 
= getBoard(code);
                    t0 
= 9 - num % 10 + 1;
                    t1 
= num / tens[t0];
                    t2 
= t1 % 1000;
                    t1 
= t1 - t2 + (t2 % 100* 10 + t2 / 100;
                    t1 
*= tens[t0];
                    newboard 
= (int)(t1 + ((num % tens[t0]) - 3));
                    
if (newboard == endBoard)
                        
return WinnerCode;
                    eval 
= Different(newboard);
                    
return combinCode(newboard, eval);
                
case Direction.Down:
                    num 
= getBoard(code);
                    t0 
= 9 - num % 10 + 1 - 3;//跟Up不同的地方
                    t1 = num / tens[t0];
                    t2 
= t1 % 1000;
                    t1 
= t1 - t2 + (t2 % 10* 100 + t2 / 10;//跟Up不同的地方
                    t1 *= tens[t0];
                    newboard 
= (int)(t1 + ((num % tens[t0]) + 3));//跟Up不同的地方
                    if (newboard == endBoard)
                        
return WinnerCode;
                    eval 
= Different(newboard);
                    
return combinCode(newboard, eval);
            }

            
return OutOfCode;
        }


        
/// <summary>
        
/// 恢复上一步的局面
        
/// </summary>
        
/// <param name="code"></param>
        
/// <param name="dir"></param>

        public long unchange(long code, Direction dir)
        
{
            
return change(code, (Direction)(5 - dir));
        }


        
/// <summary>
        
/// 当找到目标时,从哈希表里找原来的路径
        
/// </summary>
        
/// <param name="endCode"></param>
        
/// <param name="depth"></param>

        private void setResult(long endCode, Direction curDir, Direction lastDir, int depth)
        
{
            
long lastCode = endCode;
            result 
= new Direction[depth];
            result[depth 
- 1= curDir;
            
for (int i = depth - 2; i >= 0; i--)
            
{
                
if (boardtable.ContainsKey(lastCode))
                
{
                    result[i] 
= boardtable[lastCode].Dir;
                    lastCode 
= unchange(lastCode, result[i]);
                }

                
else
                    
return;
            }

        }


        
//本算法的核心部分

        
带Open表和HashTable的最好优先搜索(每次扩展Open表后都对Open表排序)

        
带Open表和HashTable的深度优先搜索(排序后才插入Open表)

        
带Open表和HashTable的广度优先搜索(排序后才插入Open表)

        
/// <summary>
        
/// 把一维数组的局面编码成一个整数的表示形式
        
/// </summary>
        
/// <param name="genBoard"></param>
        
/// <returns></returns>

        public int ToIntBoard(int[] genBoard)
        
{
            
int board = 0;
            
int emp = 0;
            
for (int i = 0; i < genBoard.Length; i++)
            
{
                
if (genBoard[i] != 0)
                    board 
= 10 * board + genBoard[i];
                
else
                    emp 
= i + 1;
            }

            
return 10 * board + emp;
        }


        
/// <summary>
        
/// 判断是否可以从初始状态到达目标状态(计算两个状态的逆序列,奇偶性相同的返回true)
        
/// </summary>
        
/// <param name="start"></param>
        
/// <param name="end"></param>
        
/// <returns></returns>

        private bool ExistAns(int[] start, int[] end)
        
{
            
int sequence_start = 0, sequence_end = 0;
            
for (int i = 0; i < start.Length; i++)
            
{
                
if (start[i] != 0)
                    
for (int j = i + 1; j < start.Length; j++)
                    
{
                        
if (start[j] != 0 && start[j] < start[i])
                            sequence_start
++;
                    }

                
if (end[i] != 0)
                    
for (int j = i + 1; j < start.Length; j++)
                    
{
                        
if (end[j] != 0 && end[j] < end[i])
                            sequence_end
++;
                    }

            }

            
return (sequence_start + sequence_end) % 2 == 0;
        }


        
/// <summary>
        
/// 初始化求解
        
/// </summary>
        
/// <param name="start"></param>
        
/// <param name="end"></param>
        
/// <param name="maxDepth"></param>

        private void InitComp(int[] start, int[] end, int maxDepth)
        
{
            nodes 
= 0;
            same 
= 0;
            MaxDepth 
= maxDepth;
            result 
= null;
            boardtable 
= new Dictionary<long, StateMsg>();
            openList 
= new  SortedList<long,StateMsg>();
            openStack 
= new Stack<State>();
            openQueue 
= new Queue<State>();

            
this.startBoard = ToIntBoard(start);
            endBoard 
= ToIntBoard(end);
            
int t_end = endBoard;
            
int emp_end = endBoard % 10;
            endBoardArray 
= new int[10];
            endBoardArray[
0= emp_end;
            endBoardArray[emp_end] 
= 0;
            
for (int i = 9; i > emp_end; i--)
            
{
                t_end 
/= 10;
                endBoardArray[i] 
= t_end % 10;
            }

            
for (int i = emp_end - 1; i >= 1; i--)
            
{
                t_end 
/= 10;
                endBoardArray[i] 
= t_end % 10;
            }

        }


        
/// <summary>
        
/// 求解8数码问题
        
/// </summary>
        
/// <param name="start"></param>
        
/// <param name="end"></param>

        public Answer Compute(int[] start, int[] end, int maxDepth, int mode)
        
{
            
if (!ExistAns(start, end))
                
return Answer.NotExist;
            InitComp(start, end, maxDepth);
            
if (startBoard == endBoard)
                
return Answer.Exist;
            
long oldtime = System.DateTime.Now.Ticks;
            
int eval = 0;
            
switch (mode)
            
{
                
case 0:
                    eval 
= DepthFirstSearch();
                    
break;
                
case 1:
                    eval 
= BreadthFirstSearch();
                    
break;
                
case 2:
                    eval 
= BestFirstSearch();
                    
break;
                
default:
                    eval 
= BestFirstSearch();
                    
break;
            }

            time 
= (System.DateTime.Now.Ticks - oldtime) / 10000000.0f;
            
if (eval == WinnerCode)
                
return Answer.Exist;
            
return Answer.NotExistInDepth;
        }


    }

}

 
A*算法求解八数码问题 1、A*算法基本思想: 1)建立一个队列,计算初始结点的估价函数f,并将初始结点入队,设置队列头和尾指针。 2)取出队列头(队列头指针所指)的结点,如果该结点是目标结点,则输出路径,程序结束。否则对结点进行扩展。 3)检查扩展出的新结点是否与队列中的结点重复,若与不能再扩展的结点重复(位于队列头指针之前),则将它抛弃;若新结点与待扩展的结点重复(位于队列头指针之后),则比较两个结点的估价函数中g的大小,保留较小g值的结点。跳至第五步。 4)如果扩展出的新结点与队列中的结点不重复,则按照它的估价函数f大小将它插入队列中的头结点后待扩展结点的适当位置,使它们按从小到大的顺序排列,最后更新队列尾指针。 5)如果队列头的结点还可以扩展,直接返回第二步。否则将队列头指针指向下一结点,再返回第二步。 2、程序运行基本环境: 源程序所使用编程语言:C# 编译环境:VS2010,.net framework 4.0 运行环境:.net framework 4.0 3、程序运行界面 可使用程序中的test来随机生成源状态与目标状态 此停顿过程中按Enter即可使程序开始运行W(n)部分; 此停顿部分按Enter后程序退出; 4、无解问题运行情况 这里源程序中是先计算源状态与目标状态的逆序对的奇偶性是否一致来判断是否有解的。下面是无解时的运行画面: 输入无解的一组源状态到目标状态,例如: 1 2 3 4 5 6 7 8 0 1 2 3 4 5 6 8 7 0 运行画面如下: 5、性能比较 对于任一给定可解初始状态,状态空间有9!/2=181440个状态;当采用不在位棋子数作为启发函数时,深度超过20时,算法求解速度较慢; 其中启发函数P(n)与W(n)的含义如下: P(n): 任意节点与目标结点之间的距离; W(n): 不在位的将牌数; 源状态 目标状态 P(n) 生成节点数 W(n) 生成节点数 P(n) 扩展节点数 W(n) 扩展节点数 2 8 3 1 6 4 7 0 5 1 2 3 8 0 4 7 6 5 11 13 5 6 1 2 3 8 0 4 7 6 5 0 1 3 8 2 4 7 6 5 6 6 2 2 4 8 2 5 1 6 7 0 3 7 4 2 8 5 6 1 3 0 41 79 22 46 6 2 5 8 7 0 3 1 4 0 3 6 7 1 8 4 5 2 359 10530 220 6769 7 6 3 1 0 4 8 5 2 2 8 7 1 3 4 6 5 0 486 8138 312 5295 下图是解决随机生成的100中状态中,P(n)生成函数的生成节点与扩展节点统计图: 由上图可知,P(n)作为启发函数,平均生成节点数大约在1000左右,平均扩展节点数大约在600左右; 下图是解决随机生成的100中状态中,W(n)生成函数的生成节点与扩展节点统计图: 由上图可知,W (n)作为启发函数,平均生成节点数大约在15000左右,是P(n)作为启发函数时的平均生成节点的15倍;W (n)作为启发函数,平均扩展节点数大约在10000左右,是P(n)作为启发函数时的平均扩展节点的15倍; 下图是解决随机生成的100中状态中,两个生成函数的生成节点与扩展节点统计图: 由上述图表可以看到,将P(n)作为启发函数比将W(n)作为启发函数时,生成节点数与扩展节点数更稳定,相比较来说,采用P(n)作为启发函数的性能比采用W(n)作为启发函数的性能好。 6、源代码说明 1)AStar-EightDigital-Statistics文件夹:用来随机生成100个状态,并对这100个状态分别用P(n)与W(n)分别作为启发函数算出生成节点以及扩展节点,以供生成图表使用;运行界面如下: 2)Test文件夹:将0-8这9个数字随机排序,用来随机生成源状态以及目标状态的;运行界面如下: 3)AStar-EightDigital文件夹:输入源状态和目标状态,程序搜索出P(n)与W(n)分别作为启发函数时的生成节点数以及扩展节点数,并给出从源状态到目标状态的移动步骤;运行界面如下: 提高了运行速度的几处编码思想: 1、 在维护open以及close列表的同时,也维护一个类型为hashtable的open以及close列表,主要用来提高判断当前节点是否在open列表以及close列表中出现时的性能; 2、 对于每个状态,按照从左到右,从上到下,依次将数字拼接起来,形成一个唯一标识identify,通过该标识,可以直接判断两个状态是否是同一个状态,而不需要循环判断每个位置上的数字是否相等 3、 在生成每个状态的唯一标识identify时,同时计算了该状态的空格所在位置,通过空格所在位置,可以直接判断能否进行上移、下移、左移、右移等动作; 4、 只计算初始节点的h值,其它生成的节点的h值是根据当前状态的h值、移动的操作等计算后得出的,规则如下: a) 采用W(n)这种方式,不在位置的将牌数,共有以下3中情况: i. 该数字原不在最终位置上,移动后,在其最终位置上 这种情况下,生成的子节点的h值= 父节点的h值-1 ii. 该数字原在最终位置上,移动后,不在其最终位置上 这种情况下,生成的子节点的h值= 父节点的h值 +1 iii. 该数字原不在最终位置上,移动后,还是不在其最终位置上 这种情况下,生成的子节点的h值= 父节点的h值 iv. 该数字原在最终位置上,移动后,还在其最终位置 这种情况不存在 b) 采用P(n)这种方式,节点与目标距离,可通过下面3步完成 i. 首先计算在原位置时,与目标位置的距离,命名为Distance1 ii. 移动后,计算当前位置与目标位置的距离,命名为Distance2 iii. 计算子节点的h值: 子节点的h值 = 父节点的h值- Distance1+ Distance2 5、 在任意状态中的每个数字和目标状态中同一数字的相对距离就有9*9种,可以先将这些相对距离算出来,用一个矩阵存储,这样只要知道两个状态中同一个数字的位置,就可查出它们的相对距离,也就是该数字的偏移距离;例如在一个状态中,数字8的位置是3,在另一状态中位置是7,那么从矩阵的3行7列可找到2,它就是8在两个状态中的偏移距离。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值