我试图解决以下问题:
给出一个3x3网格,数字为1-9,例如:
2 8 3
1 4 5
7 9 6
我必须通过顺时针或逆时针旋转2x2子网格来对网格进行排序。上面的例子可以像这样解决:
顺时针旋转左上方的部分:
2 8 3 1 2 3
1 4 5 => 4 8 5
7 9 6 7 9 6
逆时针旋转右下方:
1 2 3 1 2 3
4 8 5 => 4 5 6
7 9 6 7 8 9
网格现已排序'。
这是一个功课,但我还没有得到这个。蛮力没有工作;我必须能够在< = 2000ms内解决所有给定的网格。我尝试过的一件事是尝试计算所有8个可能的下一个动作的值(在两个方向上旋转所有4个棋子),然后旋转具有最佳值的棋子。该值是通过将所有数字的所有距离与其正确位置相加来计算的。
这适用于上面的例子,但更难的是不行。
有人能指出我正确的方向吗?我应该从哪里开始?这个问题有名字吗?
所有网格均为3x3,旋转部分始终为2x2。
提前致谢。
编辑: 忘记提到最重要的事情:我必须找到对网格进行分类的最小可能的转弯量。
EDIT2:
我实施了一个工作算法,其中包含了我收到的所有建议中的一些建议。令人讨厌的是,在我的机器上它以1,5s运行最坏的情况(987654321),但在测试程序的服务器上运行> 2s,这意味着我仍然需要优化。我的工作代码现在是
int find(int[][] grid) {
Queue q = new ArrayDeque<String>();
Map<String, Boolean> visited = new HashMap<String, Boolean>();
Object[] str = new Object[] { "", 0 };
for(int[] row : grid)
for(int num : row)
str[0] += Integer.toString(num);
q.add(str);
while(!q.isEmpty()) {
str = (Object[])q.poll();
while(visited.containsKey((String)str[0])) str = (Object[])q.poll();
if(((String)str[0]).equals("123456789")) break;
visited.put((String)str[0], Boolean.TRUE);
for(int i = 0; i < 4; i++) {
String[] kaannetut = kaanna((String)str[0], i);
Object[] str1 = new Object[] { (String)kaannetut[0], (Integer)str[1]+1 };
Object[] str2 = new Object[] { (String)kaannetut[1], (Integer)str[1]+1 };
if(!visited.containsKey((String)str1[0])) q.add((Object[])str1);
if(!visited.containsKey((String)str2[0])) q.add((Object[])str2);
}
}
return (Integer)str[1];
}
任何人都可以提出任何优化提示吗?
EDIT3:
Sirko的查找表构思的实现,正如我所理解的那样:
import java.util.*;
class Permutation {
String str;
int stage;
public Permutation(String str, int stage) {
this.str = str;
this.stage = stage;
}
}
public class Kiertopeli {
// initialize the look-up table
public static Map<String, Integer> lookUp = createLookup();
public static int etsiLyhin(int[][] grid) {
String finale = "";
for(int[] row : grid)
for(int num : row)
finale += Integer.toString(num);
// fetch the wanted situation from the look-up table
return lookUp.get(finale);
}
public static Map<String, Integer> createLookup() {
// will hold the list of permutations we have already visited.
Map<String, Integer> visited = new HashMap<String, Integer>();
Queue<Permutation> q = new ArrayDeque<Permutation>();
q.add(new Permutation("123456789", 0));
// just for counting. should always result in 362880.
int permutations = 0;
Permutation permutation;
creation:
while(!q.isEmpty())
{
permutation = q.poll();
// pick the next non-visited permutation.
while(visited.containsKey(permutation.str)) {
if(q.isEmpty()) break creation;
permutation = q.poll();
}
// mark the permutation as visited.
visited.put(permutation.str, permutation.stage);
// loop through all the rotations.
for(int i = 0; i < 4; i++) {
// get a String array with arr[0] being the permutation with clockwise rotation,
// and arr[1] with counter-clockwise rotation.
String[] rotated = rotate(permutation.str, i);
// if the generated permutations haven't been created before, add them to the queue.
if(!visited.containsKey(rotated[0])) q.add(new Permutation(rotated[0], permutation.stage+1));
if(!visited.containsKey(rotated[1])) q.add(new Permutation(rotated[1], permutation.stage+1));
}
permutations++;
}
System.out.println(permutations);
return visited;
}
public static String[] rotate(String string, int place) {
StringBuilder str1 = new StringBuilder(string);
StringBuilder str2 = new StringBuilder(string);
if(place == 0) { // top left piece
str1.setCharAt(0, string.charAt(3));
str1.setCharAt(1, string.charAt(0)); // clockwise rotation
str1.setCharAt(4, string.charAt(1)); //
str1.setCharAt(3, string.charAt(4));
str2.setCharAt(3, string.charAt(0));
str2.setCharAt(0, string.charAt(1)); // counter-clockwise
str2.setCharAt(1, string.charAt(4)); //
str2.setCharAt(4, string.charAt(3));
}
if(place == 1) { // top right
str1.setCharAt(1, string.charAt(4));
str1.setCharAt(2, string.charAt(1));
str1.setCharAt(5, string.charAt(2));
str1.setCharAt(4, string.charAt(5));
str2.setCharAt(4, string.charAt(1));
str2.setCharAt(1, string.charAt(2));
str2.setCharAt(2, string.charAt(5));
str2.setCharAt(5, string.charAt(4));
}
if(place == 2) { // bottom left
str1.setCharAt(3, string.charAt(6));
str1.setCharAt(4, string.charAt(3));
str1.setCharAt(7, string.charAt(4));
str1.setCharAt(6, string.charAt(7));
str2.setCharAt(6, string.charAt(3));
str2.setCharAt(3, string.charAt(4));
str2.setCharAt(4, string.charAt(7));
str2.setCharAt(7, string.charAt(6));
}
if(place == 3) { // bottom left
str1.setCharAt(4, string.charAt(7));
str1.setCharAt(5, string.charAt(4));
str1.setCharAt(8, string.charAt(5));
str1.setCharAt(7, string.charAt(8));
str2.setCharAt(7, string.charAt(4));
str2.setCharAt(4, string.charAt(5));
str2.setCharAt(5, string.charAt(8));
str2.setCharAt(8, string.charAt(7));
}
return new String[] { str1.toString(), str2.toString() };
}
public static void main(String[] args) {
String grids = "2 8 3 1 4 5 7 9 6 "
+ "1 6 5 8 7 2 3 4 9 "
+ "1 6 5 8 7 2 3 4 9 "
+ "1 7 6 8 2 5 3 4 9 "
+ "8 1 5 7 4 6 3 9 2 "
+ "9 8 7 6 5 4 3 2 1 ";
Scanner reader = new Scanner(grids);
System.out.println();
while (reader.hasNext()) {
System.out.println("Enter grid:");
int[][] grid = new int[3][3];
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
grid[i][j] = reader.nextInt();
}
}
System.out.println(" Smallest: " + etsiLyhin(grid));
}
}
}
每次运行大约1500-1600毫秒。
EDIT4:按照Sirko的建议,我可以将执行时间缩短到600毫秒。这是现在的代码:
import java.util.*;
class Permutation {
Byte[] value;
int stage;
public Permutation(Byte[] value, int stage) {
this.value = value;
this.stage = stage;
}
public Byte[][] rotate(int place) {
Byte[] code1 = value.clone();
Byte[] code2 = value.clone();
if(place == 0) { // top left piece
code1[0] = value[3];
code1[1] = value[0];
code1[4] = value[1];
code1[3] = value[4];
code2[3] = value[0];
code2[0] = value[1];
code2[1] = value[4];
code2[4] = value[3];
}
if(place == 1) { // top right
code1[1] = value[4];
code1[2] = value[1];
code1[5] = value[2];
code1[4] = value[5];
code2[4] = value[1];
code2[1] = value[2];
code2[2] = value[5];
code2[5] = value[4];
}
if(place == 2) { // bottom left
code1[3] = value[6];
code1[4] = value[3];
code1[7] = value[4];
code1[6] = value[7];
code2[6] = value[3];
code2[3] = value[4];
code2[4] = value[7];
code2[7] = value[6];
}
if(place == 3) { // bottom left
code1[4] = value[7];
code1[5] = value[4];
code1[8] = value[5];
code1[7] = value[8];
code2[7] = value[4];
code2[4] = value[5];
code2[5] = value[8];
code2[8] = value[7];
}
return new Byte[][] { code1, code2 };
}
public Integer toInt() {
Integer ival = value[8] * 1 +
value[7] * 10 +
value[6] * 100 +
value[5] * 1000 +
value[4] * 10000 +
value[3] * 100000 +
value[2] * 1000000 +
value[1] * 10000000 +
value[0] * 100000000;
return ival;
}
}
public class Kiertopeli {
// initialize the look-up table
public static Map<Integer, Integer> lookUp = createLookup();
public static int etsiLyhin(int[][] grid) {
Integer finale = toInt(grid);
// fetch the wanted situation from the look-up table
return lookUp.get(finale);
}
public static Map<Integer, Integer> createLookup() {
// will hold the list of permutations we have already visited.
Map<Integer, Integer> visited = new HashMap<Integer, Integer>();
Map<Integer, Boolean> queued = new HashMap<Integer, Boolean>();
Queue<Permutation> q = new ArrayDeque<Permutation>();
q.add(new Permutation(new Byte[] { 1,2,3,4,5,6,7,8,9 }, 0));
queued.put(123456789, true);
// just for counting. should always result in 362880.
int permutations = 0;
Permutation permutation;
creation:
while(!q.isEmpty())
{
permutation = q.poll();
// pick the next non-visited permutation.
while(visited.containsKey(permutation.toInt())) {
if(q.isEmpty()) break creation;
permutation = q.poll();
}
// mark the permutation as visited.
visited.put(permutation.toInt(), permutation.stage);
// loop through all the rotations.
for(int i = 0; i < 4; i++) {
// get a String array with arr[0] being the permutation with clockwise rotation,
// and arr[1] with counter-clockwise rotation.
Byte[][] rotated = permutation.rotate(i);
// if the generated permutations haven't been created before, add them to the queue.
if(!visited.containsKey(toInt(rotated[0])) && !queued.containsKey(toInt(rotated[0]))) {
q.add(new Permutation(rotated[0], permutation.stage+1));
queued.put(toInt(rotated[0]), true);
}
if(!visited.containsKey(toInt(rotated[1])) && !queued.containsKey(toInt(rotated[1]))) {
q.add(new Permutation(rotated[1], permutation.stage+1));
queued.put(toInt(rotated[1]), true);
}
}
permutations++;
}
System.out.println(permutations);
return visited;
}
public static Integer toInt(Byte[] value) {
Integer ival = value[8] * 1 +
value[7] * 10 +
value[6] * 100 +
value[5] * 1000 +
value[4] * 10000 +
value[3] * 100000 +
value[2] * 1000000 +
value[1] * 10000000 +
value[0] * 100000000;
return ival;
}
public static Integer toInt(int[][] value) {
Integer ival = value[2][2] * 1 +
value[2][1] * 10 +
value[2][0] * 100 +
value[1][2] * 1000 +
value[1][1] * 10000 +
value[1][0] * 100000 +
value[0][2] * 1000000 +
value[0][1] * 10000000 +
value[0][0] * 100000000;
return ival;
}
public static void main(String[] args) {
String grids = "2 8 3 1 4 5 7 9 6 "
+ "1 6 5 8 7 2 3 4 9 "
+ "1 6 5 8 7 2 3 4 9 "
+ "1 7 6 8 2 5 3 4 9 "
+ "8 1 5 7 4 6 3 9 2 "
+ "9 8 7 6 5 4 3 2 1 ";
Scanner reader = new Scanner(grids);
System.out.println();
while (reader.hasNext()) {
System.out.println("Enter grid:");
int[][] grid = new int[3][3];
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
grid[i][j] = reader.nextInt();
}
}
System.out.println(" Smallest: " + etsiLyhin(grid));
}
}
}
非常感谢Sirko和其他所有给我建议的人!
答案 0 :(得分:4)
此问题可以简化为无向未加权图表中的最短路径问题。
数字1 ... 9可以安排在9个9个方格中!方法。因此最多可以有9个阶乘状态。图的顶点将是这9个!状态。对于3x3网格的每种配置,您可以应用8种不同的旋转。因此,从每个顶点,将有8个边到8个其他状态。
现在,您将获得图形的起始顶点,并且您希望找到到达目标顶点的最短路径(数字已排序)。因此,只需从此源顶点应用breadth first search即可找到到目标顶点的最短路径。
运行时间:对于每个查询,最短路径将在最差情况O(E + V)
时间内找到。在上述图表中,
V = 9! = 362880
E = 9! * 8 = 2903040。
但是图形不会包含大多数这些顶点,因为并非所有状态都可以从给定的起始状态开始。因此,使用在1秒钟内在计算机上运行1000万次计算的标准,每个查询可以在不到1秒的时间内得到答案。
答案 1 :(得分:3)
已经提出的解决方案的一个变体是生成查找表。
如前所述,最多只有
9! = 362880
矩阵的可能排列。
每个排列可以使用9位数字表示,其中包含1到9之间的每个数字恰好一次。我们通过逐行读取矩阵来做到这一点,例如:
1 2 3
4 5 6 => 123456789
7 8 9
4 8 6
1 5 3 => 486153729
7 2 9
从这开始,我们可以使用一个简单的递归程序,通过从解决方案开始并生成所有排列来预先计算每个矩阵所需的排列数。在这样做的同时,我们记得我们花了多少轮换来达到特定的排列。我们使用查找表来存储结果和堆栈来存储节点,我们仍然需要处理。在堆栈中,我们使用对{ node, distance to solution }
并使用对{ 123456789, 0 }
初始化它,它描述了已排序的起始节点。
lookup = [];
queue = [ { 123456789, 0 } ]:
while( there is a node in the queue ) {
take the first node out of the queue
// skip nodes we already processed
if( !(node in lookup) ) {
generate all permutations possible by using 1 rotation starting form node
push all generated nodes to the queue using a distance + 1
}
}
之后我们可以通过在结果中进行查找来回答所有给定的矩阵。
通过使队列始终按解决方案的距离排序,我们确保找到最短路径。如果没有这样的排序,我们可能会先找到一条较长的路径,然后再找到较短的路径。
可以进一步优化算法:
答案 2 :(得分:2)
我为写一个新的答案而道歉,但我无法对invin的答案发表评论(由于没有50个声誉)所以我必须在这里做。 < / p>
此BFS算法可以通过确保您不从任何已访问状态扩展来进一步优化。
使用factoradic,您可以表示任何可能的状态,其数字介于1到9之间! (362880)。如果您之前已经访问过BFS中的状态,则可以使用简单的bool数组(大小为362880)来跟踪。您只能扩展到非访问状态,我认为这会在步数较大的情况下大幅缩短操作时间。