爱因斯坦谜题:谁养鱼的回溯算法解决

      前几天看过该题目的文章:http://www.cnblogs.com/yefanqiu/archive/2009/09/27/1575326.html,当时一时兴趣还在纸上画了好一阵子。此问题从常规算法的角度来说至少可以使用穷举法、回溯法和递归三种算法进行解决。正好现在猪流感闹得厉害,出门不太方便,国庆在家没事的时候正好可以用此题练练手。
      当然,上述三种算法实现的难易程度不同,效果也不同。毫无疑问,使用穷举法实现最容易,但效果也最差。递归算法个人觉得太复杂,因此本人在此使用相对比较容易实现的回溯法予以解决。至于题目的内容本人就不再介绍,首先介绍一下本人的算法思想。

算法思想

      其实算法思想很简单,当然要实现还真不是易事,特别在逻辑排错调试上。
1、首先就是把各个类别信息进行分组(例如国籍放在一组,房子颜色放到一组等等)
2、然后依次从各个类别的分组中取出一个元素放到存放最终结果的数组中。其中对应的元素只能根据其所在的分组而存放到指定的某行中(例如国籍只能存放到结果数组的首行,房子颜色只能存放到次行等等)。存放之前需测试其是否满足条件。
3、如果满足条件则把该元素保存到结果数组中,否则取该元素所在分组余下尚未测试的下一个元素进行测试。
4、如果该元素所在分组余下的所有元素都不满足条件则返回到上一个数组单元继续测试,直到满足条件为止,如果都不满足条件则证明此题无解。
      备注:在本人的解决办法中使用的是按先列后行的次序对结果数组进行操作。

具体实现

1、为方便程序的编写,首先定义一些常量和类

Helper
    #region FieldName
    
internal static class FieldName
    {
        
public static readonly string Brit = "----英国----";
        
public static readonly string Swede = "----瑞典----";
        
public static readonly string Dane = "----丹麦----";
        
public static readonly string Norwegian = "----挪威----";
        
public static readonly string German = "----德国----";
        
public static readonly string Red = "-----红-----";
        
public static readonly string Green = "-----绿-----";
        
public static readonly string White = "-----白-----";
        
public static readonly string Yellow = "-----黄-----";
        
public static readonly string Blue = "-----蓝-----";
        
public static readonly string Tea = "-----茶-----";
        
public static readonly string Coffee = "----咖啡----";
        
public static readonly string Milk = "----牛奶----";
        
public static readonly string Beer = "----啤酒----";
        
public static readonly string Water = "-----水-----";
        
public static readonly string PallMall = "--PallMall--";
        
public static readonly string Dunhill = "--Dunhill--";
        
public static readonly string Blends = "---Blends---";
        
public static readonly string BlueMaster = "-BlueMaster-";
        
public static readonly string Prince = "---Prince---";
        
public static readonly string Dog = "-----狗-----";
        
public static readonly string Bird = "-----鸟-----";
        
public static readonly string Cat = "-----猫-----";
        
public static readonly string Horse = "-----马-----";
        
public static readonly string Fish = "-----鱼-----";
    }
    
#endregion

    
/// <summary>
    
/// 监哨兵类
    
/// </summary>
    internal class Guide
    {
        
//哨兵值,用于检测是否过标,防止无限循环;
        public string Value{get;set;}
        
//表示Value的值是否已经测试过,如果该值为True表示已经过标
        public bool IsChecked { getset; }
    }
    
/// <summary>
    
/// 行(组)索引枚举声明
    
/// </summary>
    internal enum GroupIndex
    {
        People,
        House,
        Drink,
        Smoke,
        Pet
    }
2、然后声明相关的字段并进行初始化,为操作的方便性考虑本人使用了一个队列数组来保存所有尚未分配的元素
初始化
    #region static field

        
static readonly int rowLength = 5;
        
static readonly int columnLength = 5;
        
/// <summary>
        
/// 尚未分配的元素的队列数组
        
/// </summary>
        static Queue<string>[] queueSource;
        
static string[][] arySource = new string[rowLength][];
        
/// <summary>
        
/// 存放符合条件的结果数组
        
/// </summary>
        static string[][] aryResult = new string[rowLength][];
        
//检哨兵数组,对结果数组中的每一个元素进行监视,防止队列过标所造成的死循环。此处也可以使用堆栈类进行替换
        static Guide[][] aryGuarder = new Guide[rowLength][];

        
#endregion

        
#region Initialize
        
private static void Initialize()
        {
            arySource[(
int)GroupIndex.People] = new string[] { FieldName.Brit, FieldName.Swede, FieldName.Dane, FieldName.Norwegian, FieldName.German };
            arySource[(
int)GroupIndex.House] = new string[] { FieldName.Red, FieldName.Green, FieldName.White, FieldName.Yellow, FieldName.Blue };
            arySource[(
int)GroupIndex.Drink] = new string[] { FieldName.Tea, FieldName.Coffee, FieldName.Milk, FieldName.Beer, FieldName.Water };
            arySource[(
int)GroupIndex.Smoke] = new string[] { FieldName.PallMall, FieldName.Dunhill, FieldName.Blends, FieldName.BlueMaster, FieldName.Prince };
            arySource[(
int)GroupIndex.Pet] = new string[] { FieldName.Dog, FieldName.Bird, FieldName.Cat, FieldName.Horse, FieldName.Fish };
            
//对保存结果的数组元素进行初始化
            for (int i = 0; i < aryResult.Length; i++)
            {
                aryResult[i] 
= new string[] { """""""""" };
            }
            

            
//初始化队列
            queueSource = new Queue<string>[arySource.Length];

            
//全部入队并对检哨兵进行初始化
            for (int i = 0; i < arySource.Length; i++)
            {
                queueSource[i] 
= new Queue<string>(arySource[i].Length+1);
                aryGuarder[i] 
= new Guide[arySource.Length];
                
for (int j = 0; j < arySource[i].Length; j++)
                {
                    queueSource[i].Enqueue(arySource[i][j]);
                    aryGuarder[i][j]
=new Guide();
                    aryGuarder[i][j].Value 
= string.Empty;
                    aryGuarder[i][j].IsChecked 
= false;
                }
            }

            
//设置第一个哨兵
            aryGuarder[(int)GroupIndex.People][0].Value = queueSource[(int)GroupIndex.People].Peek();
            aryGuarder[(
int)GroupIndex.People][0].IsChecked = false;
        }
        
#endregion
3、数组越界检测方法
数组越界检测
    #region 数组边界检测与行列索引设置
        
/// <summary>
        
/// 测试是否能设置前一数组单元的索引值,防止数组越界操作
        
/// </summary>
        
/// <param name="groupIndex">新的组(行)索引</param>
        
/// <param name="columnIndex">新的列索引</param>
        
/// <returns></returns>
        private static bool CanSetPriorUnitIndex(ref int groupIndex, ref int columnIndex)
        {
            
bool isSuccessful = true;
            
if (groupIndex == 0 && columnIndex == 0)
                isSuccessful 
= false;
            
else if (groupIndex > 0)
                groupIndex
--;
            
else if (columnIndex > 0)
            {
                columnIndex
--;
                groupIndex 
= rowLength-1;
            }
            
return isSuccessful;
        }
        
/// <summary>
        
/// 测试是否能设置后一数组单元的索引值,防止数组越界操作
        
/// </summary>
        
/// <param name="groupIndex">新的组(行)索引</param>
        
/// <param name="columnIndex">新的列索引</param>
        
/// <returns></returns>
        private static bool CanSetNextUnitIndex(ref int groupIndex,ref int columnIndex)
        {
            
bool isSuccessful = true;
            
if (groupIndex == rowLength-1 && columnIndex == columnLength-1)
                isSuccessful 
= false;
            
else if (groupIndex < rowLength - 1)
                groupIndex
++;

            
else if (columnIndex < columnLength - 1)
            {
                columnIndex
++;
                groupIndex 
= 0;
            }
            
return isSuccessful;
        }
        
#endregion
4、队列过标检测方法与设置数组单元所对应的监哨兵的值的方法
队列过标检测
    /// <summary>
        
/// 测试是否过标,防止死循环,如果过标则返回上一单元的状态继续分析
        
/// </summary>
        
/// <param name="currentGroupIndex"></param>
        
/// <param name="currenColumnIndex"></param>
        
/// <param name="value"></param>
        
/// <returns></returns>
        private static bool IsOverGuard(int currentGroupIndex,int currenColumnIndex,string value)
        {
            
if (aryGuarder[currentGroupIndex][currenColumnIndex].Value == value)
            {
                
if (aryGuarder[currentGroupIndex][currenColumnIndex].IsChecked) //已经过标,返回上一单元
                {
                    ReturnPriorUnit(currentGroupIndex, currenColumnIndex);
                    
return true;
                }
                aryGuarder[currentGroupIndex][currenColumnIndex].IsChecked 
= true;
            }
            
return false;
        }

        
/// <summary>
        
/// 根据当前行列索引设置下一数组单元的哨兵值
        
/// </summary>
        
/// <param name="currentGroupIndex">组(行)索引</param>
        
/// <param name="currentColumnIndex">列索引</param>
        private static void SetNextGuard(int currentGroupIndex,int currentColumnIndex)
        {
            aryGuarder[currentGroupIndex][currentColumnIndex].Value 
= queueSource[currentGroupIndex].Peek();
            aryGuarder[currentGroupIndex][currentColumnIndex].IsChecked 
= false;
        }
5、当前组中所有未分配的元素都不满足条件时回到前一个数组单元的状态的方法
返回前一数组单元继续测试
    /// <summary>
        
/// 当前所能放入到结果数组中的元素都不能满足预期要求时对上一个单元的内容进行清空,以便回到上一个数组单元重新开始测试
        
/// </summary>
        
/// <param name="currentGroupIndex"></param>
        
/// <param name="currentColumnIndex"></param>
        private static void ReturnPriorUnit(int currentGroupIndex, int currentColumnIndex)
        {
            
if (CanSetPriorUnitIndex(ref currentGroupIndex, ref currentColumnIndex))
            {
                queueSource[currentGroupIndex].Enqueue(aryResult[currentGroupIndex][currentColumnIndex]);
                aryResult[currentGroupIndex][currentColumnIndex] 
= string.Empty;
            }
            
else
                
throw new Exception("无解");

            
//}
        }
6、对绝大部分规则条件进行测试的辅助方法
规则条件辅助测试方法
    #region  ColumnRuleTest
        
/// <summary>
        
/// 测试某个数组单元中保存的元素是否和当前将保存到结果数组中的元素是否符合相关的规则条件,
        
/// 符合表示可以插入,否则不能插入,例如条件1:英国人住在红房子里
        
/// </summary>
        private static bool ColumnRuleTest(GroupIndex groupIndex, int columnIndex,
            
string expectedRuleValue, string currentExpecteddValue, string currentActualValue)
        {
            
return currentActualValue == currentExpecteddValue && aryResult[(int)groupIndex][columnIndex] == expectedRuleValue
                
|| currentActualValue != currentExpecteddValue && aryResult[(int)groupIndex][columnIndex] != expectedRuleValue;
        }
        
#endregion

        
/// <summary>
        
/// 相邻规则条件检测,例如条件15:抽混合烟的人的邻居喝矿泉水
        
/// </summary>
        
/// <param name="nearGroupIndex"></param>
        
/// <param name="nearExpectedValue"></param>
        
/// <param name="curGroupIndex"></param>
        
/// <param name="curColumnIndex"></param>
        
/// <param name="curExpectedValue"></param>
        
/// <param name="curActualValue"></param>
        
/// <returns></returns>
        static bool NearLocationRuleTest(GroupIndex nearGroupIndex, string nearExpectedValue,
            GroupIndex curGroupIndex, 
int curColumnIndex, string curExpectedValue, string curActualValue)
        {
            
bool canInsert = true;

            
//如果将要测试的值已经分配到结果数组中,则需进行判断
            if (aryResult[(int)nearGroupIndex].Contains(nearExpectedValue))
            {
                
if (curColumnIndex == 0)
                {
                    
//如果当前列索引为首列,表示可能会分配到了同一列
                    if (curExpectedValue == curActualValue)
                        canInsert 
= false;
                }
                
else if (!aryResult[(int)curGroupIndex].Contains(curExpectedValue))
                {
                    canInsert 
= ColumnRuleTest(nearGroupIndex, curColumnIndex - 1, nearExpectedValue, curExpectedValue, curActualValue);
                }
            }
            
return canInsert;
        }
7、每个组的规则条件测试方法,测试是否满足插入条件
组规则测试
     #region TestOfPeople
        
private static bool TestOfPeople(int curColumnIndex, string curValue)
        {
            
bool canInsert = true;
            
//9. 挪威人住在第一间房子
            if (curColumnIndex == 0 && curValue != FieldName.Norwegian
                            
|| curColumnIndex != 0 && curValue == FieldName.Norwegian)
            {
                
//不符条件,不能插入结果数组中 
                canInsert = false;
            }
            
//14.挪威人住在蓝房子旁边
            else if (!NearLocationRuleTest(GroupIndex.House, FieldName.Blue, GroupIndex.People,
                curColumnIndex, FieldName.Norwegian, curValue))
            {
                canInsert 
= false;
            }
            
return canInsert;
        }
        
#endregion

        
#region TestOfHouse
        
private static bool TestOfHouse(int curColumnIndex, string curValue)
        {
            
bool canInsert = true;

            
// 1. 英国人住在红房子里
            if (!ColumnRuleTest(GroupIndex.People, curColumnIndex, FieldName.Brit, FieldName.Red, curValue))
            {
                
//不符条件
                canInsert = false;
            }
            
//4. 绿房子在白房子的左边
            if (curColumnIndex == 0 && curValue == FieldName.White
                
|| curColumnIndex > 0 && !ColumnRuleTest(GroupIndex.House, curColumnIndex - 1, FieldName.Green, FieldName.White, curValue))
            {
                canInsert 
= false;
            }
            
//14.挪威人住在蓝房子旁边
            else if (!NearLocationRuleTest(GroupIndex.People, FieldName.Norwegian, GroupIndex.House, curColumnIndex, FieldName.Blue, curValue))
            {
                canInsert 
= false;
            }
            
return canInsert;
        }
        
#endregion

        
#region TestOfDrink
        
private static bool TestOfDrink(int curColumnIndex, string strTemp)
        {
            
bool canInsert = true;
            
//3. 丹麦人喝茶
            if (!ColumnRuleTest(GroupIndex.People, curColumnIndex, FieldName.Dane, FieldName.Tea, strTemp))
            {
                
//不符条件,重新入队
                canInsert = false;
            }
            
//5. 绿房子主人喝咖啡
            else if (!ColumnRuleTest(GroupIndex.House, curColumnIndex, FieldName.Green, FieldName.Coffee, strTemp))
            {
                
//不符条件,重新入队
                canInsert = false;
            }
            
//8. 住在中间房子的人喝牛奶
            else if (strTemp == FieldName.Milk && curColumnIndex != 2
                
|| strTemp != FieldName.Milk && curColumnIndex == 2)
            {
                
//不符条件,重新入队
                canInsert = false;
            }
            
//15.抽混合烟的人的邻居喝矿泉水
            else if (!NearLocationRuleTest(GroupIndex.Smoke, FieldName.Blends, GroupIndex.Drink, curColumnIndex, FieldName.Water, strTemp))
            {
                canInsert 
= false;
            }
            
return canInsert;
        }
        
#endregion

        
#region TestOfSmoke
        
private static bool TestOfSmoke(int curColumnIndex, string strTemp)
        {
            
bool canInsert = true;
            
//13.德国人抽prince烟
            if (!ColumnRuleTest(GroupIndex.People, curColumnIndex, FieldName.German, FieldName.Prince, strTemp))
            {
                
//不符条件,重新入队
                canInsert = false;
            }
            
//7. 黄房子主人抽dunhill烟
            else if (!ColumnRuleTest(GroupIndex.House, curColumnIndex, FieldName.Yellow, FieldName.Dunhill, strTemp))
            {
                
//不符条件,重新入队
                canInsert = false;
            }
            
//12.抽bluemaster烟的人喝啤酒
            else if (!ColumnRuleTest(GroupIndex.Drink, curColumnIndex, FieldName.Beer, FieldName.BlueMaster, strTemp))
            {
                
//不符条件,重新入队
                canInsert = false;
            }
            
//10.抽混合烟的人住在养猫人的旁边
            else if (!NearLocationRuleTest(GroupIndex.Pet, FieldName.Cat, GroupIndex.Smoke, curColumnIndex, FieldName.Blends, strTemp))
            {
                canInsert 
= false;
            }
            
//11.养马人住在抽dunhill烟人的旁边
            else if (!NearLocationRuleTest(GroupIndex.Pet, FieldName.Horse, GroupIndex.Smoke, curColumnIndex, FieldName.Dunhill, strTemp))
            {
                canInsert 
= false;
            }
            
//15.抽混合烟的人的邻居喝矿泉水
            else if (!NearLocationRuleTest(GroupIndex.Drink, FieldName.White, GroupIndex.Smoke, curColumnIndex, FieldName.Blends, strTemp))
            {
                canInsert 
= false;
            }
            
return canInsert;
        }
        
#endregion

        
#region TestOfPet
        
private static bool TestOfPet(int curColumnIndex, string strTemp)
        {
            
bool canInsert = true;
            
//2. 瑞典人养了一条狗
            if (!ColumnRuleTest(GroupIndex.People, curColumnIndex, FieldName.Swede, FieldName.Dog, strTemp))
            {
                canInsert 
= false;
            }
            
//6. 抽pallmall烟的人养了一只鸟
            else if (!ColumnRuleTest(GroupIndex.Smoke, curColumnIndex, FieldName.PallMall, FieldName.Bird, strTemp))
            {
                
//不符条件,重新入队
                canInsert = false;
            }
            
//10.抽混合烟的人住在养猫人的旁边
            else if (!NearLocationRuleTest(GroupIndex.Smoke, FieldName.Blends, GroupIndex.Pet, curColumnIndex, FieldName.Cat, strTemp))
            {
                canInsert 
= false;
            }
            
//11.养马人住在抽dunhill烟人的旁边
            else if (!NearLocationRuleTest(GroupIndex.Smoke, FieldName.Dunhill, GroupIndex.Pet, curColumnIndex, FieldName.Horse, strTemp))
            {
                canInsert 
= false;
            }
            
return canInsert;
        }
        
#endregion
8、体现算法思想核心的方法
核心算法
    /// <summary>
        
/// 对指定数组单元进行分析处理,如果当前将插入到结果数组中的元素
        
/// 符合相关条件则放入到该数组单元中并返回该元素的值,否则返回空值
        
/// </summary>
        
/// <param name="groupIndex"></param>
        
/// <param name="columnIndex"></param>
        
/// <returns></returns>
        private static string GroupAnalysis(int groupIndex, int columnIndex)
        {
            
string currentValue = aryResult[groupIndex][columnIndex];

            
while (string.IsNullOrEmpty(aryResult[groupIndex][columnIndex]))
            {
                
string strTemp = queueSource[groupIndex].Dequeue();

                
if (IsOverGuard(groupIndex, columnIndex, strTemp)) //过标检测
                {
                    queueSource[groupIndex].Enqueue(strTemp); 
//重新入队
                    break;
                }

                
bool canInsert = true;

                
switch (groupIndex)
                {
                    
case 0:
                        canInsert 
= TestOfPeople(columnIndex, strTemp);
                        
break;
                    
case 1:
                        canInsert 
= TestOfHouse(columnIndex, strTemp);
                        
break;
                    
case 2:
                        canInsert 
= TestOfDrink(columnIndex, strTemp);
                        
break;
                    
case 3:
                        canInsert 
= TestOfSmoke(columnIndex, strTemp);
                        
break;
                    
case 4:
                        canInsert 
= TestOfPet(columnIndex, strTemp);
                        
break;
                    
default:
                        
throw new IndexOutOfRangeException("参数超过索引范围");
                }


                
if (canInsert)
                {
                    aryResult[groupIndex][columnIndex] 
= strTemp;
                    currentValue 
= strTemp;
                    
return currentValue;
                }
                
else
                    queueSource[(
int)groupIndex].Enqueue(strTemp);
            }
            
return currentValue;
        }

        
/// <summary>
        
/// 写最终结果
        
/// </summary>
        private static void WriteResult()
        {
            
for (int i = 0; i < aryResult.Length; i++)
            {
                
for (int j = 0; j < aryResult[i].Length; j++)
                    System.Console.Write(aryResult[i][j] 
+ "\t");
                System.Console.WriteLine();
            }
        }
9、有了上面一系列的方法,对于主程序的实现就相当简单了:
主程序
    /// <summary>
        
/// Mains the specified args.
        
/// </summary>
        
/// <param name="args">The args.</param>
        static void Main(string[] args)
        {
            Initialize();
            
int curgroupIndex = 0;
            
for (int curColumnIndex = 0; curColumnIndex < columnLength; )
            {
                
string returnValue = GroupAnalysis(curgroupIndex, curColumnIndex);
                
if (!string.IsNullOrEmpty(returnValue)) //不为空表示当前值符合插入到数组指定单元的条件并已经插入
                {
                    
if (CanSetNextUnitIndex(ref curgroupIndex, ref curColumnIndex))
                        
//根据下一数组单元的行(组)列索引值设置下一个跟踪哨兵值
                        SetNextGuard(curgroupIndex, curColumnIndex);
                    
else
                    {
                        
//退出循环,表示有满足条件的结果值,对其进行输出
                        WriteResult();
                        
break//本题只有一个结果,如果有多个结果可以在此输出结果后继续测试
                    }

                }
                
else
                {
                    
//为空表示没有符合条件的值,需要重新回到前一个数组单元格重新继续开始测试
                    CanSetPriorUnitIndex(ref curgroupIndex, ref curColumnIndex);
                }
            }
            System.Console.ReadLine();
        }
程序运行结果:
爱因斯坦谜题:谁养鱼的回溯算法解决_第1张图片 

结论

      本题的算法思想虽然简单,但真正实现还真不是容易的事,特别要找出某个逻辑上的错误真的很难。同时在本人的实现方法中只要找到一个解后程序就立即退出,如果要找多个解的话还得对本程序进行很少量的修改,不过本题应该只有唯一解(当然也没有测试过)。此题也应该可以用递归算法实现,不过在纠正逻辑错误上可能还会复杂很多,有兴趣的朋友不妨一试。

源代码:TheEinsteinPuzzle

你可能感兴趣的:(爱因斯坦谜题:谁养鱼的回溯算法解决)