这是我的动态编程解决方案,最多可以在 O(K^N) 步骤中找到最佳的移动顺序,K = 5、N = 8 的运行时间不到一秒。由于懒惰,我对输入数据进行了硬编码。
它是一个通过状态空间的 BFS,永远不会访问同一个状态两次。然后从头到尾回溯得到实际路径(这部分与最优序列的长度成线性关系)。基本上,它是“穿过迷宫的最短路径”算法,但“迷宫”是问题的状态空间,起始“位置”是初始状态,结束“位置”是期望状态。
许多类似的问题都可以通过这种方式解决,只要您可以定义一个有限的状态空间,您的目标是最小化两个状态之间的“距离”,以及计算可以从当前状态移动到哪些状态的方法。
例如,具有任意数量的“传教士和食人者”问题可以使用相同的算法来解决。
另外,如果您需要“所有最优解”而不是“任何最优解”,则可以轻松修改算法来提供它们。
class Program
{
static int N = 8;
static int K = 5;
static List<int> StartState = new List<int> { 3, 3, 2, 1, 4, 1, 5, 2 };
static List<int> EndState = new List<int> { 1, 4, 2, 2, 3, 4, 4, 3 };
static LinkedList<int> StateQueue = new LinkedList<int>();
static int[] MovesToState = new int[(int)Math.Pow(K, N)];
static void Main(string[] args)
{
for (int i = 0; i < StartState.Count; i++)
{
StartState[i]--;
EndState[i]--;
}
int startStateIndex = StateToNum(StartState);
int endStateIndex = StateToNum(EndState);
for (int i = 0; i < MovesToState.Length; i++)
MovesToState[i] = -1;
MovesToState[startStateIndex] = 0;
StateQueue.AddFirst(startStateIndex);
while (StateQueue.Count > 0 && MovesToState[endStateIndex] == -1)
{
var legalMoves = LegalMoves(StateQueue.Last.Value);
foreach (var newStateIndex in legalMoves)
{
int currMoves = MovesToState[StateQueue.Last.Value];
if (MovesToState[newStateIndex] == -1)
{
MovesToState[newStateIndex] = currMoves + 1;
StateQueue.AddFirst(newStateIndex);
}
}
StateQueue.RemoveLast();
}
var currStateIndex = endStateIndex;
var moves = new List<Tuple<int, int>>();
while (currStateIndex != startStateIndex)
{
var legalMoves = LegalMoves(currStateIndex);
int currMoves = MovesToState[currStateIndex];
foreach (var prevStateIndex in legalMoves)
{
if (MovesToState[prevStateIndex] == MovesToState[currStateIndex] - 1)
{
var currState = NumToState(currStateIndex);
var prevState = NumToState(prevStateIndex);
for (int i = 0; i < N; i++)
{
if (currState[i] != prevState[i])
{
moves.Add(new Tuple<int, int>(prevState[i] + 1, currState[i] + 1));
currStateIndex = prevStateIndex;
break;
}
}
}
}
}
Console.WriteLine(MovesToState[endStateIndex]);
moves.Reverse();
foreach (var move in moves)
{
Console.WriteLine("{0} {1}", move.Item1, move.Item2);
}
Console.Read();
}
static List<int> LegalMoves(int stateIndex)
{
var legalMoves = new List<int>();
var state = NumToState(stateIndex);
int[] minOnPeg = new int[K];
for (int i = 0; i < minOnPeg.Length; i++)
minOnPeg[i] = N;
for (int i = 0; i < N; i++)
for (int j = 0; j < K; j++)
if (state[i] == j && i < minOnPeg[j])
minOnPeg[j] = i;
bool[] isTop = new bool[N];
for (int i = 0; i < isTop.Length; i++)
isTop[i] = false;
for (int i = 0; i < K; i++)
if (minOnPeg[i] < N)
isTop[minOnPeg[i]] = true;
for (int i = 0; i < N; i++)
{
if (!isTop[i])
continue;
for (int j = 0; j < K; j++)
{
if (minOnPeg[j] <= i)
continue;
var tmp = state[i];
state[i] = j;
var newStateIndex = StateToNum(state);
legalMoves.Add(newStateIndex);
state[i] = tmp;
}
}
return legalMoves;
}
static int StateToNum(List<int> state)
{
int r = 0;
int f = 1;
foreach (var peg in state)
{
r += f * peg;
f *= K;
}
return r;
}
static List<int> NumToState(int num)
{
var r = new List<int>();
for (int i = 0; i < N; i++)
{
r.Add(num % K);
num = num / K;
}
return r;
}
}