看得見的算法——走迷宮

文件地址:鏈接:https://pan.baidu.com/s/13YTq7nGLH5PCi88bXkYLsw

提取碼:vz77

本節我們來完成一個很有意思的事情那就是走迷宮關於這個問題一個迷宮我們把其抽象爲一個圖,那麼走迷宮無非就是遍歷,我們知道圖的遍歷無非兩種形式廣度優先遍歷和深度優先遍歷以及深度優先遍歷的非遞歸方式,走迷宮我們要採用四聯通區域這樣一個圖形學思想無非就是對一個單元的左上右下四個方向進行搜索,深度和廣度優先遍歷搜索的順序不同但最後都會給我們一個解

我們迷宮的構造的要素無非就是路(road)和牆(wall),用上述我們所說的方法進行搜索終止條件無非就是當遇到出口時終止,我們的迷宮都是有解的所以我們不考慮無解的情況

還有一個要點就是我們在搜索迷宮的過程中我們訪問過的單元不能被再次訪問這樣我們保證路徑回溯的不重複性

數據層

本節我們採用讀的方式構建迷宮我們事先準備好一個迷宮的txt用#表示牆用空格來代表路我們txt的第一行是我們的迷宮的行和列

1.首先我們需要一個char maze[][] 來保存我們的迷宮數據,我們需要一個boolean visited[][] 來記錄我們是否訪問過座標對應單元,boolean[][] path用來記錄當前路徑和最後的解

2.int[][] distance={{0,-1},{1,0},{0,1},{-1,0}} 用來表示上下右左的四個方向

3.int entranceX,entranceY用來表示迷宮入口

4.int exitX,exitY用來表示迷宮出口


···package com.lipengge.dfsmaze.data;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

public class MazeData {
	//迷宮是由字符數組組成的我們可以使用char數組
     private  char[][] maze;
     private int entranceX,entranceY;
     private int exitX,exitY;
     public boolean[][] path;
     public boolean[][] visited;
     public int[][] distance={{0,-1},{1,0},{0,1},{-1,0}};
	public int getEntranceX() {
		return entranceX;
	}
	public void setEntranceX(int entranceX) {
		this.entranceX = entranceX;
	}
	public int getEntranceY() {
		return entranceY;
	}
	public void setEntranceY(int entranceY) {
		this.entranceY = entranceY;
	}
	public int getExitX() {
		return exitX;
	}
	public void setExitX(int exitX) {
		this.exitX = exitX;
	}
	public int getExitY() {
		return exitY;
	}
	public void setExitY(int exitY) {
		this.exitY = exitY;
	}
	public MazeData(String fileName) {
		readFile(fileName);
	}
    //獲取數組的行
	public int getMazeL(){
		return maze.length;
	}
   //獲取數組的列
	public int getMazeW(){
		return maze[getMazeL()-1].length;
	}
	//獲取數組的元素
	public char getMazeElement(int x,int y){
		if(isIllegal(x,y)){
			throw new IllegalArgumentException("數組越界");
		}
		return maze[x][y];
	}
	//判斷數組是否越界
	public boolean isIllegal(int x,int y){
		return !(x>=0&&x<getMazeL()&&y>=0&&y<getMazeW());
	}
	//讀取文件
	public void readFile(String fileName){
		BufferedReader buf=null;
		try{
		File file=new File(fileName);
		if(!file.exists()){
			throw new IllegalArgumentException("文件不存在");
		}
		buf=new BufferedReader(new InputStreamReader(new BufferedInputStream(new FileInputStream(file))));
		String[] num=buf.readLine().split(" ");//獲取第一行
		int r=Integer.parseInt(num[0]);
		int c=Integer.parseInt(num[1]);
		maze=new char[r][c];
		path=new boolean[r][c];
		visited=new boolean[r][c];
		for(int i=0;i<r;i++){//讀取迷宮數據
		    maze[i]=buf.readLine().toCharArray();//讀取的每行轉爲字符數組
		}
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			if( buf!=null){
				try {
					 buf.close();
				} catch (IOException e) {
					
				}
			}
		}
	}
	//輸出數組
	public void print(){
		System.out.println(getMazeL()+" "+getMazeW());
		for(int i=0;i<getMazeL();i++){
			for(int j=0;j<getMazeW();j++){
				System.out.print(getMazeElement(i, j));
			}
			System.out.println();
		}
	}
//	public static void main(String[] args) {
//		MazeData maze=new MazeData("maze_101_101.txt");
//		maze.print();
//	}
} 

視圖層

我們視圖層一如既往的簡單只需要改變繪製規則

1.int w=canvasWidth/data.getMazeL();畫布的寬/數組的列數 表示窗體一行被分爲多少份

2.int h=canvasHeight/data.getMazeW();畫布的高/數組的行 表示窗體一列被分爲多少份

3.當我們的path[x][y]爲真時表示當前正在探索的路爲藍色

4.不然如果maze[x][y]爲WALL上黑色爲ROAD上白色


```package com.lipengge.dfsmaze.view;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;

import javax.swing.JFrame;
import javax.swing.JPanel;

import com.lipengge.dfsmaze.data.MazeData;
import com.lipengge.dfsmaze.util.Constant;
import com.lipengge.randomquestion.util.AlgoVisHelper;

public class AlgoFrame extends JFrame{
	/**
	 * 
	 */
	private static final long serialVersionUID = -3035088527551930125L;
	private int canvasWidth;//畫布的寬
	private int canvasHeight;//畫布的長
	private MazeData data;
   public int getCanvasWidth() {
		return canvasWidth;
	}
	public int getCanvasHeight() {
		return canvasHeight;
	}
	public void render(MazeData data){
		this.data=data;
		repaint();
	}
   public AlgoFrame(String title,int  canvasWidth, int canvasHeight){
	  super(title);
	  this.canvasWidth=canvasWidth;
	  this.canvasHeight=canvasHeight;
	  setDefaultCloseOperation(EXIT_ON_CLOSE);
	  setResizable(false);//關閉可拖動
	  setVisible(true);
	  AlgoCanvas algoCanvas = new AlgoCanvas();
	  setContentPane(algoCanvas);
	  pack();
   }
   //設置面板JPanel
   class AlgoCanvas extends JPanel{
	/**
	 * 
	 */
	private static final long serialVersionUID = -6056196800598676936L;
    private  AlgoCanvas(){
    	//開啓雙緩存
    	super(true);
    }
	@Override
	//在面板上繪製內容
	protected void paintComponent(Graphics g) {
		super.paintComponent(g);
		Graphics2D g2d=(Graphics2D) g;
		AlgoVisHelper.setRenderingHints(g2d);//設置抗鋸齒
		AlgoVisHelper.setStrokeWidth(g2d, 1);//設置邊的寬度爲5
		int w=canvasWidth/data.getMazeL();
		int h=canvasHeight/data.getMazeW();
	    for(int i=0;i<data.getMazeL();i++){
	    	for(int j=0;j<data.getMazeW();j++){
	    		if(data.path[i][j]){
	    			g2d.setColor(Color.BLUE);
	    		}else if(data.getMazeElement(i, j)==Constant.WALL){
	    			g2d.setColor(Color.BLACK);
	    		}else if(data.getMazeElement(i, j)==Constant.ROAD){
	    			g2d.setColor(Color.WHITE);
	    		}
	    		AlgoVisHelper.fillReactangle(g2d,w*j,h*i,w,h);
	    	}
	    }
	}
	@Override
	public Dimension getPreferredSize() {
		return new Dimension(canvasWidth,canvasHeight);
	}
   }
}

控制層

1.對於我們控制層來說我們只需要傳入要讀取的迷宮文件傳給數據層就可以了,之後就是設置畫布的寬和高我們用迷宮數組的行乘10像素表示高,列乘10像素表示寬

2.這裏我們在一個setData方法裏設置我們的出口並且如果座標合法給path[x][y]賦值

3.重點在我們的go函數裏面傳入入口座標我們採用遞歸的深度優先遍歷我們的座標合法並且沒有被訪問過的話那麼我們給訪問標記置true,判斷如果是出口我們就返回一個true,不然我們將按照四個方向對座標合法,沒有被訪問過並且是ROAD的路徑進行遞歸如果遞歸函數返回true說明座標已經找到返回true否則繼續遞歸如果遇見已經訪問過的返回false

package com.lipengge.dfsmaze.controller;

import java.awt.EventQueue;

import com.lipengge.dfsmaze.data.MazeData;
import com.lipengge.dfsmaze.util.Constant;
import com.lipengge.dfsmaze.view.AlgoFrame;
import com.lipengge.randomquestion.util.AlgoVisHelper;

public class AlgoVisualizer {
	private AlgoFrame frame;
	private MazeData data;
	private int screenWidth;
	private int screenHeight;
	public AlgoVisualizer(String fileName){
	    data=new MazeData(fileName);
		this.screenWidth=data.getMazeL()*Constant.BLOCK;
		this.screenHeight=data.getMazeW()*Constant.BLOCK;
		//初始化視圖
		EventQueue.invokeLater(()->{
			frame=new AlgoFrame("maze",screenWidth,screenHeight);
			new Thread(()->run()).start();
		});
	}
	//播放動畫
	public void run(){
			setData(-1,-1,false);
			go(1,0);
			setData(-1,-1,false);
	}
   private void setData(int x,int y,boolean isPath) {
	   data.setExitX(data.getMazeL()-2);
	   data.setExitY(data.getMazeW()-1);
	   if(!data.isIllegal(x, y)){
	    data.path[x][y]=isPath;
	   }
		frame.render(data);
		AlgoVisHelper.pause(0);
	}
   //走迷宮
   public boolean go(int x,int y){
	   if(data.isIllegal(x, y)){
		   throw new IllegalArgumentException("數組越界");
	   }
	   setData(x,y,true);
	   //標記訪問
	   data.visited[x][y]=true;
	   //如果是出口返回true
	   if(data.getExitX()==x&&data.getExitY()==y){
		   return true;
	   }
	   for(int i=0;i<4;i++){
		   int newX=data.distance[i][0]+x;
		   int newY=data.distance[i][1]+y;
		   if(!data.isIllegal(newX,newY)&&data.getMazeElement(newX,newY)==Constant.ROAD&&!data.visited[newX][newY]){
			  if(go(newX,newY)){
				  return true;
			  }
		   }
	   }
	   setData(x,y,false);
	   return false;
   }
public static void main(String[] args) {
  new AlgoVisualizer("maze_101_101.txt");
  }
}

常量類

package com.lipengge.dfsmaze.util;

public class Constant {
   public static final int BLOCK=10;
   public static final char WALL='#';
   public static final char ROAD=' ';

}

繪製幫助類

package com.lipengge.minesweeper.util;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;

import javax.swing.ImageIcon;

public class AlgoVisHelper {
	private AlgoVisHelper(){}
	//畫圓描邊的輔助類
	public static void strokeCircle(Graphics2D g,int x,int y,int r){
		//圓數據的閉包
		Ellipse2D circle = new Ellipse2D.Double(x-r,y-r,2*r,2*r);
		g.draw(circle);
	}
	//畫圓填充的輔助類
	public static void fillCircle(Graphics2D g,int x,int y,int r){
		Ellipse2D circle = new Ellipse2D.Double(x-r,y-r,2*r,2*r);
		g.fill(circle);
	}
	//畫矩形的輔助類
	public static void strokeReactangle(Graphics2D g,int x,int y,int width,int height){
		Rectangle2D reactangle = new Rectangle2D.Double(x,y,width,height);
		g.draw(reactangle);
	}
	//填充矩形的輔助類
	public static void fillReactangle(Graphics2D g,int x,int y,int width,int height){
		Rectangle2D reactangle = new Rectangle2D.Double(x,y,width,height);
		g.fill(reactangle);
	}
	//設置畫筆顏色
    public static void setColor(Graphics2D g,Color color){
    	g.setColor(color);
    }
    //設置邊的寬度和平滑性
    public static void setStrokeWidth(Graphics2D g,int w){
    	//BasicStroke.CAP_ROUND把定點變爲圓弧半徑爲畫筆一半
    	//BasicStroke.JOIN_ROUND把線條連接點變爲弧形
    	g.setStroke(new BasicStroke(w,BasicStroke.CAP_ROUND,BasicStroke.JOIN_ROUND));
    }
    //設置抗鋸齒
    public static void setRenderingHints(Graphics2D g){
    	RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
    	hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
    	g.addRenderingHints(hints);
    }
    public static void pause(long millsecond){
    	try {
			Thread.sleep(20);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
    }
    //畫圖
    public static void drawImage(Graphics2D g,String imageUrl,int x,int y,int x1,int y1){
    	ImageIcon imageIcon=new ImageIcon(imageUrl);
    	Image image=imageIcon.getImage();
    	g.drawImage(image,x, y,x1,y1,null);
    }
    
}

效果圖

在這裏插入圖片描述

最終效果

在這裏插入圖片描述

下節我們將採用非遞歸的深度優先遍歷

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章