营救公主

最近在刷公司的OJ算法题,发现好多算法都不会,像什么动态规划、背包算法、回溯法、线段树、稀疏存储balabala,这些算法随便一个,自己查资料看懂然后把程序编写出来,然后再归纳总结,感觉至少一天的时间。本来的打算是,赶紧把题目刷到10000分,然后再开始干活,但昨天做了一道营救公主,发现自己代码写的一团糟,虽然算法思路都看懂了,可是自己写起来特别的繁琐,深深感受到自己的基本功还得好好磨练一下,觉得不能只当成一项任务去应付了,不能急于求成,要保质保量。

题目:

    500年前,Jesse是我国最卓越的剑客。他英俊潇洒,而且机智过人^_^。
突然有一天,Jesse心爱的公主被魔王困在了一个巨大的迷宫中。Jesse听说这个消息已经是两天以后了,他知道公主在迷宫中还能坚持T天,他急忙赶到迷宫,开始到处寻找公主的下落。 时间一点一点的过去,Jesse还是无法找到公主。最后当他找到公主的时候,美丽的公主已经死了。从此Jesse郁郁寡欢,茶饭不思,一年后追随公主而去了。T_T 500年后的今天,Jesse托梦给你,希望你帮他判断一下当年他是否有机会在给定的时间内找到公主。
他会为你提供迷宫的地图以及所剩的时间T。请你判断他是否能救出心爱的公主。

    题目包括多组测试数据。 每组测试数据以三个整数N,M,T(0<n, m<=20,0<t<=100000)开头,分别代表迷宫的长和高,以及公主能坚持的天数。 紧接着有M行,N列字符,由".","*","P","S"组成。其中 "." 代表能够行走的空地。 "*" 代表墙壁,Jesse不能从此通过。 "P" 是公主所在的位置。 "S" 是Jesse的起始位置。 每个时间段里Jesse只能选择“上、下、左、右”任意一方向走一步。 输入以0 0 0结束。

样例输入:
m = 4, n = 4, t = 10
迷宫图如下
....
....
....
S**P

样例输出:

0

描述:

/*
  * 每组测试数据包括三个整数t,n,m(00),分别代表公主能坚持的天数,迷宫的长和高。紧接着有m行,n列字符,由".","*","P","S"组成。
  * 其中 "." 代表能够行走的空地。 "*" 代表墙壁,Jesse不能从此通过。
  * "P" 是公主所在的位置。 "S" 是Jesse的起始位置。 每个时间段里Jesse
  * 只能选择“上、下、左、右”任意一方向走一步。
  * 迷宫布局(这里用二维数组实现布局) m 迷宫(数组)行数 n 迷宫(数组)列数 
  * T 公主能坚持的天数
  * Return Value 0 可以救出公主 -1 不可以救出公主
  */

 public int SSavep(char[][] visited, int t, int n, int m) {
  // 这里面添加函数功能
  return 0;
 }

解题:

拿到这道题目的时候,感觉其实还好,不是很难,稍微思考了一下,记得之前看过的算法A*算法,http://www.blueidea.com/tech/multimedia/2005/3121_3.asp,里面算法讲解的非常详细,翻出来再看了一遍,便开始自己写代码了。

package huawei;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Demo {
    //  开启节点列表
    //  通过反复遍历开启列表并选择具有最低F值得方格生成
    List<Node> openList = new ArrayList<>();
    //  关闭列表
    List<Node> closeList = new ArrayList<>();
    Node startNode = null;
    Node endNode = null;
    int N , M;
    char[][] c = null;
    //  用来判断目标节点是否添加进关闭列表
    boolean isEndNodeInCloseList = false;

	/*
	 * 每组测试数据以三个整数N,M,T(0<n, m≤20, t>0)开头,分别代表迷宫的长和高,
	 * 以及公主能坚持的天数。紧接着有M行,N列字符,由".","*","P","S"组成。
	 * 其中 "." 代表能够行走的空地。 "*" 代表墙壁,Jesse不能从此通过。
	 * "P" 是公主所在的位置。 "S" 是Jesse的起始位置。 每个时间段里Jesse
	 * 只能选择“上、下、左、右”任意一方向走一步。
	 * 迷宫布局(这里用二维数组实现布局) M 迷宫(数组)行数 N 迷宫(数组)列数 
	 * T 公主能坚持的天数
	 * Return Value 0 可以救出公主 -1 不可以救出公主
	 */
	public int SSavep(char[][] visited, int t, int n, int m) {

        //  存放当前操作的节点,也就是从开启列表中找到的权重最小的格子
        Node currentNode = null;
        N = n;
        M =m;
        c = visited;

        // 获取开始,目标节点
        for(int i=0; i<m; i++) {
            for(int j=0; j<n; j++) {
                if(visited[i][j] == 'S') {
                    startNode = new Node(i, j);
                    openList.add(startNode);
                }
                if(visited[i][j] == 'P') {
                    endNode = new Node(i, j);
                }
                if(startNode != null && endNode != null) {
                    startNode.setToOriginStep(0);
                    int startToEndX = Math.abs(endNode.getX() - startNode.getX());
                    int startToEndY = Math.abs(endNode.getY() - startNode.getY());
                    startNode.setToTerminalStep(startToEndX + startToEndY);
                    break;
                }
            }
        }

        while (!isEndNodeInCloseList && openList != null) {
            if(isEndNodeInCloseList) {
                break;
            }
            //  在开启列表中寻找权重最小的节点作为当前节点
            currentNode = searchNode(openList, closeList);
            doNeighborNode(currentNode, visited);
        }

        if(isEndNodeInCloseList) {
            if(t >= endNode.getWeight()) {
                return 0;
            }else {
                return -1;
            }
        }else {
            return -1;
        }
	}

	//  在开启列表中寻找权重最小的节点
    public Node searchNode(List list1, List list2) {
	    Node node = null;
        Iterator<Node> it = list1.iterator();
        while(it.hasNext()) {
            Node n = it.next();
            if(node == null || node.getWeight() > n.getWeight()) {
                node = n;
            }
        }
        list1.remove(node);
        list2.add(node);
        return node;
    }

    //  处理当前节点上下左右相邻的节点
    void doNeighborNode(Node currentNode,  char[][] visited) {
	    int x = currentNode.getX();
	    int y = currentNode.getY();
        int s = currentNode.getToOriginStep() + 1;

        if(x - 1 >=0 ) {
            addInStartList(x-1, y, s, currentNode);
        }

        if(x + 1 < N) {
            addInStartList(x+1, y, s, currentNode);
        }

        if(y - 1 >= 0) {
            addInStartList(x, y-1, s, currentNode);
        }

        if(y + 1 < M) {
            addInStartList(x, y+1, s, currentNode);
        }


    }

    // 判断是否已经在开启列表中
    boolean isInStartList(int x, int y, int s, Node currentNode) {
	    Iterator<Node> it = openList.iterator();
	    while(it.hasNext()) {
	        Node node = it.next();
	        if(node.getX() == x && node.getY() == y) {
	            if(node.getToOriginStep() > s) {
	                node.setParent(currentNode);
	                node.setToOriginStep(s);
	                node.setWeight();
                }
	            return true;
            }
        }
        return false;
    }

    // 将相邻节点进行放入开启列表的处理
    void addInStartList(int x, int y, int s, Node currentNode) {
        if(isInStartList(x, y, s, currentNode)) {
        }else {
            if('*' != c[x][y] && !isContain(closeList,  x,  y)) {
                if(x == endNode.getX() && y == endNode.getY()) {
                    endNode.setToOriginStep(s);
                    endNode.setToTerminalStep(0);
                    endNode.setWeight();
                    openList.add(endNode);
                    isEndNodeInCloseList = true;
                }else {
                    Node node = new Node(x, y);
                    node.setToOriginStep(s);
                    node.setToTerminalStep(Math.abs(x - endNode.getX()) + Math.abs(y - endNode.getY()));
                    node.setWeight();
                    node.setParent(currentNode);
                    openList.add(node);
                }
            }
        }
    }

   boolean isContain(List list, int x, int y) {
	    Iterator<Node> it = list.iterator();
	    while(it.hasNext()) {
	        Node node = it.next();
	        if(node.getX() == x && node.getY() == y) {
	            return true;
            }
        }
        return false;
    }

}
package huawei;

public class Node {

   private int x;
   private int y;
   private int weight;
   private int toOriginStep;
   private int toTerminalStep;
    private Node parent;

    public Node(int x, int y) {
        this.x = x;
        this.y = y;
    }




    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

    public int getWeight() {
        return weight;
    }

    public void setWeight() {
        this.weight = this.toOriginStep + this.toTerminalStep;
    }

    public int getToOriginStep() {
        return toOriginStep;
    }

    public void setToOriginStep(int toOriginStep) {
        this.toOriginStep = toOriginStep;
    }

    public int getToTerminalStep() {
        return toTerminalStep;
    }

    public void setToTerminalStep(int toTerminalStep) {
        this.toTerminalStep = toTerminalStep;
    }

    public Node getParent() {
        return parent;
    }

    public void setParent(Node parent) {
        this.parent = parent;
    }

}

辛辛苦苦写了一上午,自我感觉代码写的非常繁琐。大致的思路是这样:

  1. 把起始格添加到开启列表
  2. 重复一下循环:a)寻找开启列表中weight值最低的元素(weight = toOriginStep + toTerminalStep),作为currentNode; b)把这个元素从开启列表中移除,放入关闭列表中; c)按照可移动的方向,对相邻的元素进行处理:如果这个元素不可通过即为“*”,或者已经在关闭列表中,略过它;反之,如果这个元素不在开启列表中,把它添加进开启列表;反之,如果这个元素已经在开启列表中,用toOriginStep值考察是否新路径更好,toOriginStep值更低则路径更好,如果新路径toOriginStep更低,则更新toOriginStep值和该元素的父节点。d) 循环停止的条件:把目标节点加进了关闭列表,这个时候路径找到;或者,没有找到目标节点,开启列表已经空了,这个时候,路径不存在。
  3. 保存路径,从目标格开始,沿着每一个元素的父节点移动直到回到起始格。

写完后提交,只得了一半的分值,再仔细检查了一遍,没发觉问题在哪,如果有看过我代码的大神,还请评论指导一下。

后面在评论区看到一种非常简洁的方法,就是采用相反的思路:查看从某一个点开始,N步可以最远达到哪里。

如图:
   .   .   .   .
   .   .   .   .
   .   .   .   .
  S   *   *  P

 

初始化图如下:
   .   .   .   .
   .   .   .   .
   .   .   .   .
  0   *   *  P

 

构造1步可以到达的地方:
   .   .   .   .
   .   .   .   .
   1   .   .   .
  0   *   *  P

 

构造2步可以到达的地方:
   .   .   .   .
   2   .   .   .
   1   2   .   .
  0   *   *  P

 

构造3步可以到达的地方:
   3   .   .   .
   2   3   .   .
   1   2   3   .
  0   *   *  P

 

构造4步可以到达的地方:
   3   4   .   .
   2   3   4   .
   1   2   3   4
  0   *   *  P
构造5步可以到达的地方:
   3   4   5   .
   2   3   4   5
   1   2   3   4
  0   *   *  5

 

构造6步可以到达的地方:
   3   4   5   6
   2   3   4   5
   1   2   3   4
  0   *   *  5

可以看到P的位置是5,也就是说最少5步可以到达P的位置。

代码实现:

package huawei;

public class Demo {
    public int SSavep(char[][] visited, int t, int n, int m) {
        int[][] map = new int[visited.length][visited[0].length];

        int startx=0, starty=0, endx=0, endy=0;

        for (int i = 0; i < visited.length; i++) {
            for (int j = 0; j < visited[0].length; j++) {
                map[i][j] = Integer.MAX_VALUE;
                if ('P' == visited[i][j]) {
                    endx = i;
                    endy = j;
                } else if ('S' == visited[i][j]) {
                    map[i][j] = 0;
                    startx = i;
                    starty = j;
                }
            }
        }
        // 从开始位置计算所有表格的值
        mark(visited, map, startx, starty - 1); // 左
        mark(visited, map, startx - 1, starty); // 上
        mark(visited, map, startx, starty + 1); // 右
        mark(visited, map, startx + 1, starty); // 下

        // 获取公主位置的值
        int minStep = map[endx][endy];

        return minStep <= t ? 0 : -1;
    }

    // 根据周边的值计算自己最小的值,如果自己的值改变,则重新计算周边的值
    private void mark(char[][] visited, int[][] map, int x, int y) {
        if (x < 0 || x >= map.length || y < 0 || y >= map[0].length) {
            return;
        }

        if(visited[x][y] == '*') {
            return;
        }

        int newValue = getArroundMin(map, x, y) + 1;
        if (newValue < map[x][y]) {
            map[x][y] = newValue;
            mark(visited, map, x, y - 1); // 左
            mark(visited, map, x - 1, y); // 上
            mark(visited, map, x, y + 1); // 右
            mark(visited, map, x + 1, y); // 下
        }
    }

    // 计算周围最小的值
    private int getArroundMin(int[][] map, int x, int y) {
        int min = Integer.MAX_VALUE;
        // 左
        if (y > 0) {
            min = Math.min(min, map[x][y - 1]);
        }
        // 上
        if (x > 0) {
            min = Math.min(min, map[x - 1][y]);
        }
        // 右
        if (y < map[0].length - 1) {
            min = Math.min(min, map[x][y + 1]);
        }
        // 下
        if (x < map.length - 1) {
            min = Math.min(min, map[x + 1][y]);
        }

        return min;
    }
}

代码很简洁,采用了递归算法,自己写代码的时候,很少会使用递归,得好好学习一下这种思路。

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值