資料結構_棧的應用_迷宮求解問題java實現
阿新 • • 發佈:2019-01-05
這篇文章講述的是資料結構部分的迷宮求解問題的java實現,如有錯誤或者不當之處,還望各位大神批評指正。
問題描述
假設有一個迷宮使用二維陣列,牆使用1表示,路徑使用0表示,可達路徑使用*表示,試寫一演算法計算出從起點到終點的一條可行路徑。
演算法分析
- 使用二維陣列存放初始化的迷宮
- 藉助棧來遍歷迷宮的路徑,棧中儲存的是正確的路徑
- 業務邏輯如下
while(棧不為空){
if(找到終點){
元素全部出棧,且將其標誌位設為*
}else{
若沒不是終點,則判斷上下左右位置若合法則壓進棧,若無路可走則出棧
}
}
程式碼實現
- 迷宮的元素型別
/*迷宮單元的資料結構*/
private class Cellimpl implements Cell{
private int x ; //單元所在行
private int y ; //單元所在列
private boolean visited = false ; //單元是否已被訪問
private char mark ; //單元格的型別,1表示牆,0表示路,*表示可行路徑
/*省略get和set方法*/
- 迷宮的資料結構類及相關方法
/*迷宮的資料結構*/
class Maze {
/*迷宮大小*/
final int LENGTH = 10 ;
/*迷宮資料域*/
private Cell CELLS [][] ;
/*迷宮單元的資料結構*/
private class Cellimpl implements Cell{
private int x ; //單元所在行
private int y ; //單元所在列
private boolean visited = false ; //單元是否已被訪問
private char mark ; //單元格的型別,1表示牆,0表示路,*表示可行路徑
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 boolean isVisited() {
return visited;
}
public void setVisited(boolean visited) {
this.visited = visited;
}
public char getMark() {
return mark;
}
public void setMark(char mark) {
this.mark = mark;
}
}
/**
* @explain createMaze方法: 建立迷宮
* @param maze 傳入表示迷宮的二維陣列
* @throws
* @author 葉清逸
* @date 2018年7月31日 下午8:49:23
*/
public void create(char maze[][]){
/*為迷宮的資料域分配空間*/
CELLS = new Cell[LENGTH][LENGTH] ;
/*將二維陣列型別轉換為迷宮*/
for(int i=0 ; i<maze.length ; i++){
for(int j=0 ; j<maze.length ; j++){
/*獲取陣列元素*/
Cell cell = new Cellimpl() ;
/*初始化該迷宮元素*/
char c = maze[i][j] ;
/*將二維陣列元素轉換為迷宮元素*/
if(c == '1'){
cell.setMark('1') ; //設定標識位1
}else if(c == '0'){
cell.setMark('0') ; //設定標識為0
}
cell.setX(i) ; //設定橫座標
cell.setY(j) ; //設定縱座標
cell.setVisited(false); //設定訪問標識
/*將該元素放入迷宮對應位置*/
CELLS[i][j] = cell ;
}
}
}
/**
* @explain print方法: 列印迷宮
* @throws
* @author 葉清逸
* @date 2018年7月31日 下午9:20:35
*/
public void print(){
/*遍歷整個迷宮的資料域*/
for(int i=0 ; i<LENGTH ; i++){
for(int j=0 ; j<LENGTH ; j++){
/*列印該迷宮單元的標識*/
System.out.print(CELLS[i][j].getMark()+" ");
}
System.out.println();
}
}
public Cell[][] getCELLS() {
return CELLS;
}
}
- 尋路演算法(核心程式碼)
/**
* @explain findPath方法: 計算迷宮起點到終點的路徑
* @param maze 要尋路迷宮
* @param sx 起點的橫座標
* @param sy 起點的縱座標
* @param ex 終點的橫座標
* @param ey 終點的縱座標
* @return boolean 起點到終點間是否存在路徑,若存在返回true,不存在返回false
* @throws
* @author 葉清逸
* @date 2018年7月31日 下午9:29:07
*/
private static boolean findPath(Maze maze , int sx , int sy , int ex , int ey){
boolean flag = false ;
/*獲取迷宮的資料域*/
Cell[][]cells = maze.getCELLS() ;
/*初始化輔助棧*/
Stack stack = new LinkStack() ;
stack.init();
/*獲取起點與終點*/
Cell startCell = cells[sx][sy] ;
Cell endCell = cells[ex][ey] ;
/*將起點加入棧中*/
stack.push(startCell);
startCell.setVisited(true); //設定起點已被訪問
/*窮舉出所有情況*/
while(!stack.isEmpty()){
/*取出棧頂元素,若其是終點,則順序將所訪問的節點標識製為* */
Cell curCell = (Cell)stack.getTop() ;
if(curCell == endCell){
while(!stack.isEmpty()){
Cell cell = (Cell)stack.pop() ;
cell.setMark('*') ;
}
flag = true ;
}else{
/*獲取該點的橫座標和縱座標*/
int x = curCell.getX() ;
int y = curCell.getY() ;
/*若棧頂元素不為終點,判斷上下左右位置是否合法,若合法壓入棧*/
if(cells[x+1][y].getMark() == '0' && cells[x+1][y].isVisited() == false){ //右邊
stack.push(cells[x+1][y]);
cells[x+1][y].setVisited(true);
}else if(cells[x][y+1].getMark() == '0' && cells[x][y+1].isVisited() == false){ //下邊
stack.push(cells[x][y+1]);
cells[x][y+1].setVisited(true);
}else if(cells[x-1][y].getMark() == '0' && cells[x-1][y].isVisited() == false){ //左邊
stack.push(cells[x-1][y]);
cells[x-1][y].setVisited(true);
}else if(cells[x][y-1].getMark() == '0' && cells[x][y-1].isVisited() == false){
stack.push(cells[x][y-1]); //上邊
cells[x][y-1].setVisited(true);
}else{
stack.pop() ; //若為死路則退棧
}
}
}
return flag ;
}
程式完整程式碼
package stack_question;
import stack.LinkStack;
import stack.Stack;
/**
* @author 葉清逸
* @date 2018年7月31日下午8:00:38
* @version 1.0
* @project stack_question
*/
public class Q2_MazePath {
/**
* 問題描述:假設有一個迷宮使用二維陣列,牆使用1表示,路徑使用0表示,可達路徑使用*表示,試寫一演算法計算
* 出從起點到終點的一條可行路徑。
*
* 演算法分析:1. 使用二維陣列存放初始化的迷宮
* 2. 藉助棧來遍歷迷宮的路徑,棧中儲存的是正確的路徑
* 3. 業務邏輯如下
* while(棧不為空){
* if(找到終點){
* 元素全部出棧,且將其標誌位設為*
* }else{
* 若沒不是終點,則判斷上下左右位置若合法則壓進棧,若無路可走則出棧
* }
* }
*/
public static void main(String[] args) {
/*初始化迷宮的二維陣列*/
char [][] mazearr = {{'1','1','1','1','1','1','1','1','1','1'} ,
{'1','0','0','1','0','0','0','1','0','1'} ,
{'1','0','0','1','0','0','0','1','0','1'} ,
{'1','0','0','0','0','1','1','0','0','1'} ,
{'1','0','1','1','1','0','0','0','0','1'} ,
{'1','0','0','0','1','0','0','0','0','1'} ,
{'1','0','1','0','0','0','1','0','0','1'} ,
{'1','0','1','1','1','0','1','1','0','1'} ,
{'1','1','0','0','0','0','0','0','0','1'} ,
{'1','1','1','1','1','1','1','1','1','1'} } ;
/*初始化迷宮*/
Maze maze = new Maze() ;
maze.create(mazearr) ;
System.out.println("迷宮初始狀態:");
maze.print();
/*計算從起點到終點的路徑*/
boolean flag = findPath(maze, 1, 1, 8, 8) ;
if(flag){
System.out.println("路徑已找到,如下:");
maze.print();
}else{
System.out.println("起點與終點之間沒有可達路徑");
}
}
/**
* @explain findPath方法: 計算迷宮起點到終點的路徑
* @param maze 要尋路迷宮
* @param sx 起點的橫座標
* @param sy 起點的縱座標
* @param ex 終點的橫座標
* @param ey 終點的縱座標
* @return boolean 起點到終點間是否存在路徑,若存在返回true,不存在返回false
* @throws
* @author 葉清逸
* @date 2018年7月31日 下午9:29:07
*/
private static boolean findPath(Maze maze , int sx , int sy , int ex , int ey){
boolean flag = false ;
/*獲取迷宮的資料域*/
Cell[][]cells = maze.getCELLS() ;
/*初始化輔助棧*/
Stack stack = new LinkStack() ;
stack.init();
/*獲取起點與終點*/
Cell startCell = cells[sx][sy] ;
Cell endCell = cells[ex][ey] ;
/*將起點加入棧中*/
stack.push(startCell);
startCell.setVisited(true); //設定起點已被訪問
/*窮舉出所有情況*/
while(!stack.isEmpty()){
/*取出棧頂元素,若其是終點,則順序將所訪問的節點標識製為* */
Cell curCell = (Cell)stack.getTop() ;
if(curCell == endCell){
while(!stack.isEmpty()){
Cell cell = (Cell)stack.pop() ;
cell.setMark('*') ;
}
flag = true ;
}else{
/*獲取該點的橫座標和縱座標*/
int x = curCell.getX() ;
int y = curCell.getY() ;
/*若棧頂元素不為終點,判斷上下左右位置是否合法,若合法壓入棧*/
if(cells[x+1][y].getMark() == '0' && cells[x+1][y].isVisited() == false){ //右邊
stack.push(cells[x+1][y]);
cells[x+1][y].setVisited(true);
}else if(cells[x][y+1].getMark() == '0' && cells[x][y+1].isVisited() == false){ //下邊
stack.push(cells[x][y+1]);
cells[x][y+1].setVisited(true);
}else if(cells[x-1][y].getMark() == '0' && cells[x-1][y].isVisited() == false){ //左邊
stack.push(cells[x-1][y]);
cells[x-1][y].setVisited(true);
}else if(cells[x][y-1].getMark() == '0' && cells[x][y-1].isVisited() == false){
stack.push(cells[x][y-1]); //上邊
cells[x][y-1].setVisited(true);
}else{
stack.pop() ; //若為死路則退棧
}
}
}
return flag ;
}
}
/*迷宮元素的介面*/
interface Cell{
//獲取元素的橫座標
public int getX() ;
//設定元素的橫座標
public void setX(int x) ;
//獲取元素的縱座標
public int getY() ;
//設定元素的縱座標
public void setY(int y) ;
//是否被訪問過
public boolean isVisited() ;
//訪問元素節點
public void setVisited(boolean visited) ;
//獲取元素標識
public char getMark() ;
//設定元素標識
public void setMark(char mark) ;
}
/*迷宮的資料結構*/
class Maze {
/*迷宮大小*/
final int LENGTH = 10 ;
/*迷宮資料域*/
private Cell CELLS [][] ;
/*迷宮單元的資料結構*/
private class Cellimpl implements Cell{
private int x ; //單元所在行
private int y ; //單元所在列
private boolean visited = false ; //單元是否已被訪問
private char mark ; //單元格的型別,1表示牆,0表示路,*表示可行路徑
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 boolean isVisited() {
return visited;
}
public void setVisited(boolean visited) {
this.visited = visited;
}
public char getMark() {
return mark;
}
public void setMark(char mark) {
this.mark = mark;
}
}
/**
* @explain createMaze方法: 建立迷宮
* @param maze 傳入表示迷宮的二維陣列
* @throws
* @author 葉清逸
* @date 2018年7月31日 下午8:49:23
*/
public void create(char maze[][]){
/*為迷宮的資料域分配空間*/
CELLS = new Cell[LENGTH][LENGTH] ;
/*將二維陣列型別轉換為迷宮*/
for(int i=0 ; i<maze.length ; i++){
for(int j=0 ; j<maze.length ; j++){
/*獲取陣列元素*/
Cell cell = new Cellimpl() ;
/*初始化該迷宮元素*/
char c = maze[i][j] ;
/*將二維陣列元素轉換為迷宮元素*/
if(c == '1'){
cell.setMark('1') ; //設定標識位1
}else if(c == '0'){
cell.setMark('0') ; //設定標識為0
}
cell.setX(i) ; //設定橫座標
cell.setY(j) ; //設定縱座標
cell.setVisited(false); //設定訪問標識
/*將該元素放入迷宮對應位置*/
CELLS[i][j] = cell ;
}
}
}
/**
* @explain print方法: 列印迷宮
* @throws
* @author 葉清逸
* @date 2018年7月31日 下午9:20:35
*/
public void print(){
/*遍歷整個迷宮的資料域*/
for(int i=0 ; i<LENGTH ; i++){
for(int j=0 ; j<LENGTH ; j++){
/*列印該迷宮單元的標識*/
System.out.print(CELLS[i][j].getMark()+" ");
}
System.out.println();
}
}
public Cell[][] getCELLS() {
return CELLS;
}
}
樣例輸出
迷宮初始狀態:
1 1 1 1 1 1 1 1 1 1
1 0 0 1 0 0 0 1 0 1
1 0 0 1 0 0 0 1 0 1
1 0 0 0 0 1 1 0 0 1
1 0 1 1 1 0 0 0 0 1
1 0 0 0 1 0 0 0 0 1
1 0 1 0 0 0 1 0 0 1
1 0 1 1 1 0 1 1 0 1
1 1 0 0 0 0 0 0 0 1
1 1 1 1 1 1 1 1 1 1
路徑已找到,如下:
1 1 1 1 1 1 1 1 1 1
1 * 0 1 0 0 0 1 0 1
1 * 0 1 0 0 0 1 0 1
1 * 0 0 0 1 1 0 0 1
1 * 1 1 1 0 0 0 0 1
1 * * * 1 0 0 0 0 1
1 0 1 * * * 1 0 0 1
1 0 1 1 1 * 1 1 0 1
1 1 0 0 0 * * * * 1
1 1 1 1 1 1 1 1 1 1
程式的問題
由於探索路徑的方法不同造成的結果不同,如程式中遍歷當前節點的上下左右節點時採取的是右下左上的方案,該方案適用於起點在左上,終點在右下的路徑探尋,若探尋起點在右下終點在左上的路徑時則會造成走過多的路,如若將上例的終點改為(5,5)則會造成如下情況:
路徑已找到,如下:
1 1 1 1 1 1 1 1 1 1
1 * 0 1 0 0 0 1 0 1
1 * 0 1 0 0 0 1 0 1
1 * 0 0 0 1 1 * * 1
1 * 1 1 1 * * * * 1
1 * * * 1 * * * * 1
1 0 1 * * * 1 0 * 1
1 0 1 1 1 * 1 1 * 1
1 1 0 0 0 * * * * 1
1 1 1 1 1 1 1 1 1 1
這樣雖然可以正確找到路徑,但也會多走很多路