农夫过河问题
问题描述:
农夫要把狼、白菜、羊带到河的对岸,但每次最多只能带一个,当农夫不在时,狼会吃羊,羊会吃白菜,因此狼和羊在同一岸时农夫必须也在,羊和白菜在同一岸时农夫也必须在。问:农夫应该怎样把他们安全带过去?
我们可以想到这样一个解决方案:
1、都在左岸
2、农夫把羊带到右岸
3、农夫独自回到左岸
4、农夫把白菜带到右岸
5、农夫把羊带回左岸
6、农夫把狼带到右岸
7、农夫独自回到左岸
8、农夫把羊带到右岸
9、都到了右岸
1、Java实现代码:
import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Queue; /** * 农夫过河问题 * <p> * JDK: 1.6 * * @author wuqianling * */ public class FarmerProblem { /** * 判断农夫的位置(1000) <br> * 0表示在左岸,1表示在右岸 * * @param location * 当前位置状态 * @return false 左岸,true 右岸 */ public static boolean farmerLocation(int location) { return (0 != (location & 0x08)); // &是按位与,0x08表示十六进制数8(二进制为00001000) } /** * 判断狼的位置(0100) <br> * 0表示在左岸,1表示在右岸 * * @param location * 当前位置状态 * @return false 左岸,true 右岸 */ public static boolean wolfLocation(int location) { return (0 != (location & 0x04)); // 0x04表示十六进制数4(二进制为00000100) } /** * 判断白菜的位置(0010) <br> * 0表示在左岸,1表示在右岸 * * @param location * 当前位置状态 * @return false 左岸,true 右岸 */ public static boolean cabbagaLocation(int location) { return (0 != (location & 0x02)); // 0x02表示十六进制数2(二进制为00000010) } /** * 判断羊的位置(0001) <br> * 0表示在左岸,1表示在右岸 * * @param location * 当前位置状态 * @return false 左岸,true 右岸 */ public static boolean goatLocation(int location) { return (0 != (location & 0x01)); // 0x01表示十六进制数1(二进制为00000001) } /** * 判断安全状态 * <p> * 1、羊与白菜在同岸,且羊与农夫不在同岸,羊会吃白菜,不安全<br> * 2、羊与狼在同岸,且羊与农夫不在同岸,狼会吃羊,不安全<br> * 3、其他状态安全 * * @param location * 当前位置状态 * @return 返回true为安全,false为不安全 */ public boolean isSafe(int location) { // 羊与白菜在同岸,且羊与农夫不在同岸,羊会吃白菜,不安全 if ((goatLocation(location) == cabbagaLocation(location)) && (goatLocation(location) != farmerLocation(location))) return false; // 羊与狼在同岸,且羊与农夫不在同岸,狼会吃羊,不安全 if ((goatLocation(location) == wolfLocation(location)) && (goatLocation(location) != farmerLocation(location))) return false; // 其他状态均安全 return true; } /** * 农夫过河问题 * <p> * 问题描述:农夫要把狼、白菜、羊带到对岸,但每次最多只能带一个,当农夫不在时,狼会吃羊,羊会吃白菜。 * * @return */ public List<Integer> farmerProblem() { Queue<Integer> moveTO = new LinkedList<Integer>(); // 创建空队列,用于记录可以安全到达的中间状态 moveTO.add(new Integer(0));// 初始状态进队列 int[] route = new int[16]; // 用于记录已考虑的状态路径 // 初始化数组route for (int i = 0; i < 16; i++) { route[i] = -1; } route[0] = 0; while (!moveTO.isEmpty() && (route[15] == -1)) { int location = moveTO.poll(); // 取队顶状态为当前状态,并移除队顶元素 for (int movers = 1; movers <= 8; movers <<= 1) { // 考虑各种物品移动。 if ((0 != (location & 0x8)) == (0 != (location & movers))) { // 农夫与移动的物品在同一岸 int newlocation = location ^ (0x08 | movers); // 计算新状态,^为按位异或,相同为0,不同为1 if (isSafe(newlocation) && (route[newlocation] == -1)) { // 新状态安全且未处理 route[newlocation] = location; // 记录新状态的前驱 moveTO.add(new Integer(newlocation)); // 新状态入队 } } } } // 保存状态 List<Integer> resultList = new ArrayList<Integer>(); if (route[15] != -1) { // 到达最终状态 for (int location = 15; location >= 0; location = route[location]) { resultList.add(location); if (location == 0) { break; } } } Collections.reverse(resultList); // 反转顺序 return resultList; } /** 记录上次位置 */ private int oldLoca = 0; /** * 中文解释 * * @param location */ private String explainToChinese(int location) { String str = ""; if (farmerLocation(location) != farmerLocation(oldLoca)) str += "农夫"; // 判断农夫的位置是否改变 if (wolfLocation(location) != wolfLocation(oldLoca)) str += "把狼带到了"; // 判断狼的位置是否改变 else if (cabbagaLocation(location) != cabbagaLocation(oldLoca)) str += "把白菜带到了"; // 判断白菜的位置是否改变 else if (goatLocation(location) != goatLocation(oldLoca)) str += "把羊带到了"; // 判断羊的位置是否改变 else str += "独自回到了"; // 狼、白菜、羊的位置都未改变 if (farmerLocation(location)) str += "右岸"; // 判断下一步农夫是在哪一岸,若农夫当前在左岸,则他下一步必在右岸 else str += "左岸"; oldLoca = location; // 记录本次位置,为下次作准备 return str; } /** * 获得整型数转换为四位二进制的字符串<br> * 例如:9 = 1001 、 2 = 0010 * * @param n * 0到15的十进制整数 * @return 一个四位的二进制字符串 */ private String getIntToFourBinaryStr(int n) { char[] bnum = Integer.toBinaryString(n).toCharArray(); // 转为二进制,但是缺少前导0 char[] bit = { '0', '0', '0', '0' }; for (int i = bit.length - 1, j = bnum.length - 1; i >= 0 && j >= 0; i--, j--) { bit[i] = bnum[j]; } return new String(bit); } /** * 输出结果 * * @param list * 一个解集合 */ private void outputResult(List<Integer> list) { System.out.println("0代表在左岸,1代表在右岸"); System.out.println("0000 每位数分别代表农夫、狼、白菜、羊"); System.out.println(); System.out.println("[位置]\t[二进制]\t[中文解释]"); for (int i = 0; i < list.size(); i++) { int location = list.get(i); String str = "位置:" + location + "\t"; str += getIntToFourBinaryStr(location); // 为了利于观察,转换为二进制输出 if (location == 0) { str += "\t都在左岸"; } else { str += "\t" + explainToChinese(location); // 中文解释 } System.out.println(str); if (location == 15) { System.out.println("\t\t都在右岸"); } } } /** * 主方法 */ public static void main(String[] args) { FarmerProblem fp = new FarmerProblem(); List<Integer> list = fp.farmerProblem(); fp.outputResult(list); } }
运行结果:
2、为了更直观的模拟过程,我们使用图形界面动画来实现,下面是图形界面的代码:
import java.awt.BorderLayout; import java.awt.Color; import java.awt.Graphics; import java.awt.TextField; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.List; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JTextField; /** * 窗口类 * * @author wuqianling * */ public class MyWindow extends JFrame { private List<Integer> list = null; private DrawPanel drawPanel; private TextField textField; public MyWindow() { this.setTitle("农夫过河"); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 设置窗口关闭时退出程序 this.setSize(460, 320); this.setResizable(false); // 固定窗口大小 this.setLocationRelativeTo(null); // 居中 // 顶部面板容器 JPanel top = new JPanel(); top.setLayout(new BorderLayout()); // 设置边界布局管理器 textField = new TextField("0000 每位数分别代表农夫、狼、白菜、羊"); textField.setEditable(false); // 设置不可编辑 top.add(textField, BorderLayout.CENTER); JPanel topRight = new JPanel(); JButton calbtn = new JButton("计算方案"); JButton simbtn = new JButton("模拟方案"); topRight.add(calbtn); topRight.add(simbtn); top.add(topRight, BorderLayout.EAST); this.getContentPane().add(top, BorderLayout.NORTH); // 添加到窗口中 // 添加模拟动画面板 drawPanel = new DrawPanel(); this.getContentPane().add(drawPanel); // 添加按钮事件监听 calbtn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { calculate(); } }); simbtn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if (list == null) calculate(); drawPanel.startDraw(); } }); } /** * 计算方案 */ private void calculate() { FarmerProblem nf = new FarmerProblem(); list = nf.farmerProblem(); String str = "一个解决方案:"; int i = 0; for (; i < list.size() - 1; i++) { str += (list.get(i) + ","); } str += list.get(i); textField.setText(str); System.out.println("计算得" + str); } /** * 主方法 * * @param args */ public static void main(String[] args) { MyWindow win = new MyWindow(); win.setVisible(true); } /** * 绘制模拟的动画,实现Runnable接口开启线程 * * @author wuqianling * */ final class DrawPanel extends JPanel implements Runnable { private boolean isRun = false; // 是否正在进行模拟 private final static int INITVALUE = 4;// 默认初始坐标值 private int nx = INITVALUE; // 农夫的x轴坐标 private int lx = INITVALUE; // 狼的x轴坐标 private int bx = INITVALUE; // 白菜的x轴坐标 private int yx = INITVALUE; // 羊的x轴坐标 @Override public void paint(Graphics g) { // g.clearRect(0, 0, getWidth(), getHeight()); Color c = g.getColor(); // 画背景 g.setColor(Color.lightGray); g.fillRect(0, 0, getWidth(), getHeight()); // 画左、右岸 g.setColor(new Color(208, 238, 239)); g.fillRect(100, 0, this.getWidth() - 200, this.getHeight()); // 画河道 // 画河道说明文字 g.setColor(Color.blue); g.drawString("左岸", 60, this.getHeight() / 2 - 6); g.drawString("右岸", this.getWidth() - 80, this.getHeight() / 2 - 6); g.drawString("河道", this.getWidth() / 2 - 10, this.getHeight() / 2 - 6); g.setColor(Color.red); g.fillRect(nx, 4, 40, 50); // 画农夫 g.fillRect(lx, 64, 40, 50); // 画狼 g.fillRect(bx, 124, 40, 50); // 画白菜 g.fillRect(yx, 184, 40, 50); // 画羊 // 画相应文字 g.setColor(Color.yellow); g.drawString("农夫", nx + 8, 30); g.drawString("狼", lx + 15, 90); g.drawString("白菜", bx + 8, 150); g.drawString("羊", yx + 15, 210); g.setColor(c); // 还原颜色 } /** * 开始模拟绘制动画 * * @return */ public boolean startDraw() { if (isRun) // 是否有正在模拟的线程 return false; isRun = true; // 设置模拟开始标识 // 初始化坐标 nx = INITVALUE; lx = INITVALUE; bx = INITVALUE; yx = INITVALUE; // 开启线程 Thread th = new Thread(this); th.start(); return true; } /** * 绘制一个过程的模拟动画 * * @param loca1 * 原状态 * @param loca2 * 新状态 */ private void drawSim(int loca1, int loca2) { int distance = 2; // 每次移动的像素 int endRight = this.getWidth() - 45; // 右边结束坐标 // 判断状态是否发生改变 boolean nxb = FarmerProblem.farmerLocation(loca1) != FarmerProblem .farmerLocation(loca2); boolean lxb = FarmerProblem.wolfLocation(loca1) != FarmerProblem.wolfLocation(loca2); boolean bxb = FarmerProblem.cabbagaLocation(loca1) != FarmerProblem .cabbagaLocation(loca2); boolean yxb = FarmerProblem.goatLocation(loca1) != FarmerProblem.goatLocation(loca2); while (isRun) { // 判断农夫在左岸还是右岸,来判断应该加或减坐标 if (FarmerProblem.farmerLocation(loca2)) { // 农夫在左岸往右岸移动 nx += distance; if (lxb) lx += distance; if (bxb) bx += distance; if (yxb) yx += distance; if (nx > endRight) // 右结束边界坐标 break; } else { // 农夫在右岸往左岸移动 nx -= distance; if (lxb) lx -= distance; if (bxb) bx -= distance; if (yxb) yx -= distance; if (nx <= INITVALUE) // 左结束边界坐标 break; } repaint(); // 重绘 try { Thread.sleep(15); } catch (InterruptedException e) { e.printStackTrace(); } } } public void run() { System.out.println("模拟开始..."); // 逐个取出状态进行模拟 for (int i = 1; isRun && i < list.size(); i++) { int loca1 = list.get(i - 1); int loca2 = list.get(i); System.out.print(loca1 + ","); drawSim(loca1, loca2); } System.out.println(list.get(list.size() - 1)); System.out.println("模拟结束..."); isRun = false; // 设置模拟结束标识 } } }
运行结果: