对连连看一种算法的分析与思考
最近在用JAVA做一个经典游戏连连看,而连连看游戏的难点是在连线的判断上。下面是在在老师的提点下自己所写的一个连通判定算法的实现和问题分析:
约定:
// 本次代码中所操纵的数组,字符串型 private Sting data[][] // 数组中某格子为空格的条件 data[x][y].equals(“”)== true // 连线点堆栈这个是用来保存连接线路节点的堆栈,理解算法时可以先忽略它的操作 private Stack<java.awt.Point> linePointStack = new Stack<Point>(); linePointStack.push(checkP); |
分析:
我们知道连连看的连线规则有3种
第一种,直线链接(单线链接),即同行或同列的情况
这种联通判定比较好实现,即只要判断
两个相同元素之间的空格数 =(他们的横/纵坐标之差-1)
是否相等即可。
具体代码:
/** * 同行同列情况消除方法 原理:如果两个相同的被消除元素之间的 空格数spaceCount等于他们的(行/列差-1)则 两者可以联通消除 * x代表行,y代表列 * * @param p1 * 第一个点 * @param p2 * 第二个点 */ public boolean lineCheck(Point p1, Point p2) { int absDistance = 0; int spaceCount = 0; // 同行同列的情况吗? if (p1.x == p2.x || p1.y == p2.y) { System.out.println("同行同列的情况------"); // 同行的情况 if (p1.x == p2.x && p1.y != p2.y) { System.out.println("同行的情况"); // 绝对距离(中间隔着的空格数) absDistance = Math.abs(p1.y - p2.y) - 1; // 正负值 int zf = (p1.y - p2.y) > 0 ? -1 : 1; for (int i = 1; i <= absDistance; i++) { if (data[p1.x][p1.y + i * zf].equals("")) { // 空格数加1 spaceCount += 1; } else break;// 遇到阻碍就不用再探测了 } } // 同列的情况 else if (p1.y == p2.y && p1.x != p2.x) { System.out.println("同列的情况"); absDistance = Math.abs(p1.x - p2.x) - 1; int zf = (p1.x - p2.x) > 0 ? -1 : 1; for (int i = 1; i <= absDistance; i++) { if (data[p1.x + i * zf][p1.y].equals("")) { spaceCount += 1; } else break; } } if (spaceCount == absDistance) { // 可联通 return true; } else { System.out.println("行/列不能消除!"); return false; } } else { // 不是同行同列的情况所以直接返回false; return false; } } |
第二种,直角链接(双线链接)
如图所示,直角链接判定,可理解为,若两点要直角联通,则直角拐点checkP与p1、p2要直线联通。
而这样的直角拐点有两个,所以要将这两个拐点都进行这要的判定,其中只要有一个拐点判定成功则代表p1、p2可联通。即:
lineCheck(p1, checkP) && lineCheck(checkP, p2)为真
或者
lineCheck(p1, checkP2) && lineCheck(checkP2, p2)为真
具体代码:
/** * 直角连接,即X,Y坐标都不同的,可以用这个方法尝试连接 * * @param first * @param second */ public boolean secendLine(Point p1, Point p2) { // 第一个直角检查点,如果这里为空则赋予相同值供检查 Point checkP = new Point(p1.x, p2.y); // 第二个直角检查点,如果这里为空则赋予相同值供检查 Point checkP2 = new Point(p2.x, p1.y); // 第一个直角点检测 if (data[checkP.x][checkP.y].equals("")) { if (this.lineCheck(p1, checkP) && this.lineCheck(checkP, p2)) { linePointStack.push(checkP); System.out.println("直角消除ok" + "data.length= " + data.length); return true; } } // 第二个直角点检测 if (data[checkP2.x][checkP2.y].equals("")) { if (this.lineCheck(p1, checkP2) && this.lineCheck(checkP2, p2)) { linePointStack.push(checkP2); System.out.println("直角消除ok" + "data[checkP.x].length= " + data[checkP.x].length); return true; } }
return false;
} |
第三种,双直角(三线链接)
双直角联通判定可分两步走:
1. 在p1点周围4个方向寻找空格checkP
2. 调用secondLine(checkP, p2)
3. 即遍历p1 4个方向的空格,使之成为checkP,然后调用secondLine(checkP, p2)判定是否为真,如果为真则可以双直角连同,否则当所有的空格都遍历完而没有找到一个checkP使secondLine(checkP, p2)为真,则两点不能连同
具体代码:
/** * 两直角连接方法 以secendLine为基础 * * @param first * @param second * @return */ public boolean triLine(Point p1, Point p2) { int i; Point checkP = new Point(p1.x, p1.y);
// 四向探测开始 for (i = 4 - 1; i >= 0; i--) { checkP.x = p1.x; checkP.y = p1.y; // 向右 if (i == 3) { while ((++checkP.y < data[checkP.x].length) && data[checkP.x][checkP.y].equals("")) { linePointStack.push(checkP); if (secendLine(checkP, p2)) { System.out.println("右探测OK"); return true; } else { linePointStack.pop(); } } }// 向下 else if (i == 2) { while ((++checkP.x < data.length) && data[checkP.x][checkP.y].equals("")) { linePointStack.push(checkP); if (secendLine(checkP, p2)) { System.out.println("下探测OK"); return true; } else { linePointStack.pop(); } } }// 向左 else if (i == 1) { while ((--checkP.y >= 0) && data[checkP.x][checkP.y].equals("")) { linePointStack.push(checkP); if (secendLine(checkP, p2)) { System.out.println("左探测OK"); return true; } else { linePointStack.pop(); } } }// 向上 else if (i == 0) { while ((--checkP.x >= 0) && data[checkP.x][checkP.y].equals("")) { linePointStack.push(checkP); if (secendLine(checkP, p2)) { System.out.println("上探测OK"); return true; } else { linePointStack.pop(); } } } } // 四个方向寻完都没找到可行的checkP点,所以只好返回false return false;
} |
算法效率分析:
算法的时间复杂度几乎只集中在第三种连线方法上,即它的checkP点的寻找和匹配过程中,所以总的效率还是比较高的。
问题扩展:
下面我们来讨论这样一个问题:
如果游戏规则允许如图中紫红色连线的方式,那么我们改如何实现它呢?
我的想法是:
1. 在p2周围4个方向任意一个空格checkP2,
2. 进行第三种连线方法中的判断,triLine(p1, checkP2)看结果的真假值
如图所示,途中p1、checkP2进行triLine(p1,checkP2)的值为真,所以,p1,p2可以联通。
总结:通过上述分析,可以看出这个连连看算法的实现过程:
lineCheck()-àsecondLine()=【lineCheck()*4】à triLine(Point p1, Point p2)=【移位+secondLine()】
可见,复杂的联通判定可有先前稍简单的算法复合使用后实现,即将大问题细分成原子问题来解决,这样的好处,不仅可以降低初期的分析难度,又可避免陷入编码时的混乱。另外这样,使得逻辑合理,结构清晰,便于理解。