不相交集生成隨機迷宮

(一)數學概念

若對於每一對元素(a,b),a,b ∈S,aRb或者爲true或者爲false,則稱在集合S上定義關係R。如果aRb爲true,則說明兩者有關係。

等價關係是指滿足以下三個性質的關係R

a) (自反性)對於所有屬於S的a,aRa

b)(對稱性) aRb 當且僅當 b R a

c) (傳遞性)若aRb 並且 b R a 意味着 aRc

對於集合S劃分,取任意兩個等價類,Si與Sj,如果Si∩Sj = ∅,則稱這些集合不相交。

對於不相交集,有兩種操作,Union/Find操作。

Find操作用來找包含給定元素的集合(等價類)名字。

Union操作用來把兩個等價類合併成一個新的等價類。

(二)基本數據結構

採用樹來表示每一個集合(等價類),因爲樹上的元素都有一個共同的根。

定義Union(X, Y)操作,將X,Y合併成一個新的等價類,且X做爲根

union操作步驟如下所示


按照這種運算,經過N-1次合併後,最壞能生成一棵深度爲N-1的樹,時間複雜度爲O(N)

採用數組結構來保存每個元素所在的等價關係,數組的第一個成員s[i]表示元素i的父節點。如果元素i所在的集合只有一個元素,則s[i] = 0。上述的森林用數組表示如下


定義Find(x)操作,該操作爲遞歸過程,如:Find(8), 父親爲7, 執行Find(7),父親爲5,執行Find(5),對應值爲0,表示5爲樹根,遞歸結束。

判斷6,8是否有關係,實爲判斷Find(8) == Find(6)。

(三)優化union操作

爲了避免union合併造成樹的深度過大,每次可以讓深度小的樹成爲深的樹的子樹。數組值可以讓根的值爲負值,其絕對值代表深度。

對於上次圖表,假設繼續執行Union(4,5),兩種操作對比如下:


(四)不相交集的ADT實現

public final class DisJoinSet {

	private int[] eleRoots;
	
	public DisJoinSet(int num){
		this.eleRoots = new int[num];
		for(int i=0;i<num;i++){
			getEleRoots()[i] = -1;
		}
	}
	
	public int find(int ele){
		if(getEleRoots()[ele] < 0){
			return ele;
		}
		
		return find(getEleRoots()[ele]);
	}
	
	public void union(int root1,int root2){
		//讓深度較小的樹成爲深度較大的樹的子樹
		if(getEleRoots()[root1] > getEleRoots()[root2]){
			getEleRoots()[root1] = root2;
		}else{
			if(getEleRoots()[root1] == getEleRoots()[root2]){//深度一樣,則更新深度
				getEleRoots()[root1]--;
			}
			getEleRoots()[root2] = root1;
		}
	}

	public int[] getEleRoots() {
		return eleRoots;
	}

}
(五)使用不相交集生成隨機迷宮

假設迷宮的起點在界面的左上點,終點在界面的右下點。我們可以把迷宮看成一個m*n的矩陣。矩陣一開始四周全部被牆壁隔開。爲此,我們不斷地在迷宮內隨機選擇一個點,如果該點與周圍某一點之間存在一面牆,我們就把這面牆拆掉。重複這個過程,直到起點和終點落在同一個等價類(即起點和終點之間存在可達關係)。相關代碼如下

import java.awt.Color;
import java.awt.Graphics;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import javax.swing.JFrame;


public class Maze extends JFrame{

	private int row;//行數
	private int col;	//列數
	private DisJoinSet disjSet;
	private int winHeight=700;  
	private int winWidth=780;  
	
	public Maze(int row,int col){
		this.row = row;
		this.col = col;
		this.setTitle("迷宮");  
		this.setSize(winWidth,winHeight);  
		this.setVisible(true);  
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  
	}
	
	public static void main(String[] args) {
		int rowCount = 50;
		int colCount = 50;
		Maze maze = new Maze(rowCount,colCount);

	}

	public void paint(Graphics g){  
		super.paint(g);
		//背景爲白色  
		g.setColor(Color.white);  
		g.fillRect(0, 0, winWidth, winHeight);  
		g.setColor(Color.black);  
		final int extraWidth = 20;
		final int cellWidth = (winWidth-2*extraWidth)/row;//定義每個格子的寬度
		final int cellHeight = (winHeight-4*extraWidth)/col;//定義每個格子的高度
		for(int i=0;i<row;i++)  {
			for(int j=0;j<col;j++)  
			{  
				//初始化m*n矩陣格子
				g.drawRect(i*cellWidth+extraWidth,j*cellHeight+2*extraWidth, cellWidth, cellHeight);  
			}  
		}
		
		int lastPos = getLastElePos();//迷宮最後一個格式的代表數字
		//起點,終點特殊處理
		g.setColor(Color.red);  
		g.fillRect(extraWidth, 2*extraWidth, cellWidth, cellHeight);
		g.fillRect((lastPos% row)*cellWidth + extraWidth,(lastPos/ row)*cellHeight + 2*extraWidth, cellWidth, cellHeight);
		
		this.setDisjSet(new DisJoinSet(row*col));
		g.setColor(Color.white);  //用後景色擦色
		while(disjSet.find(0) != disjSet.find(lastPos)){//如果起點和終點還沒在同一個等價類
			/*
			 *  在迷宮內隨機挖一個點,再找到該點周圍一點,使這兩個點落在同一個等價類
			 */
			Random random = new Random();
			int randPos = random.nextInt(lastPos+1);//+1是爲了能隨機到最後一位
			int rowIndex = randPos % row;
			int colIndex = randPos / col;
			List<Integer> neighborPos = getNeighborNums(rowIndex, colIndex) ;
			int randNeighbor = neighborPos.get(random.nextInt(neighborPos.size()));

			if(disjSet.find(randPos)  ==  disjSet.find(randNeighbor)){//兩點在同一個等價類
				continue;
			}else{
				int aRoot = disjSet.find(randPos);
				int bRoot = disjSet.find(randNeighbor);
				disjSet.union(aRoot, bRoot);
				int maxNum = Math.max(randPos, randNeighbor);//取得較大點
				int x1=0,y1=0,x2=0,y2=0;  
				if(Math.abs(randPos-randNeighbor) == 1){//說明在同一行,用豎線隔開
					x1= x2=(maxNum% row)*cellWidth + extraWidth;
					y1=(maxNum/ row)*cellHeight + 2*extraWidth;
					y2=y1+cellHeight;
				}else{//說明在同一列,用橫線隔開
					y1=y2=(maxNum/ row)*cellHeight + 2*extraWidth;
					x1=(maxNum% row)*cellWidth + extraWidth;
					x2=x1+cellWidth;
				}
//				System.err.println("x1="+x1+",x2="+x2+",y1="+y1+",y2="+y2);
				 g.drawLine(x1, y1, x2, y2);
			}
		}
		
	}

	/**
	 *  取得目標座標點周圍四個有效點
	 */
	public List<Integer> getNeighborNums(int rowIndex,int colIndex){
		List<Integer> neighborPos = new ArrayList<Integer>(4);
		//右元素
		if(isPointInMaze(rowIndex+1,colIndex)){
			neighborPos.add(getCoordinateNum(rowIndex+1,colIndex));
		}
		//下元素
		if(isPointInMaze(rowIndex,colIndex+1)){
			neighborPos.add(getCoordinateNum(rowIndex,colIndex+1));
		}
		//左元素
		if(isPointInMaze(rowIndex-1,colIndex)){
			neighborPos.add(getCoordinateNum(rowIndex-1,colIndex));
		}
		//上元素
		if(isPointInMaze(rowIndex,colIndex-1)){
			neighborPos.add(getCoordinateNum(rowIndex,colIndex-1));
		}

		return neighborPos;
	}
	public int getLastElePos(){
		return row*col-1;
	}

	public DisJoinSet getDisjSet() {
		return disjSet;
	}

	public void setDisjSet(DisJoinSet disjSet) {
		this.disjSet = disjSet;
	}

	/**
	 *  根據座標返回對應的值
	 *  例如在4*3矩陣,(0,0)返回0;(3,2)返回10
	 */
	public int getCoordinateNum(int x,int y){
		return y*col + x;
	}
	

	/**
	 *  判斷給定座標是否在迷宮矩陣內
	 */
	public boolean isPointInMaze(int x,int y){
		if(x < 0 || y < 0) return false;
		return x < row && y <col;
	}
}
程序執行結果




參考文獻

[1] 佛羅里達國際大學.數據結構與算法分析Java語言描述.機械工業出版社,219-233
發佈了48 篇原創文章 · 獲贊 98 · 訪問量 25萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章