天天看點

算法系列(一):分治政策--棋盤覆寫算法系列(一):分治政策--棋盤覆寫

算法系列(一):分治政策--棋盤覆寫

一、分析

問題描述:

算法系列(一):分治政策--棋盤覆寫算法系列(一):分治政策--棋盤覆寫

 圖1-1 k=2時的一個特殊棋盤

        在一個 2^k * 2^k 個方格組成的棋盤中,若恰有一個方格與其它方格不同,則稱該方格為一特殊方格,且稱該棋盤為一特殊棋盤。顯然特殊方格在棋盤上出現的位置有 4^k 種情形。因而對任何 k>=0 ,有 4^k 種不同的特殊棋盤。圖1-1中的特殊棋盤為 k=2 時 16 個特殊棋盤中的一個。

        在棋盤覆寫問題中,要用圖1-2所示的4種不同形态的 L 型骨牌覆寫一個給定的特殊棋牌上除特殊方格以外的所有方格,且任何 2 個 L 型骨牌不得重疊覆寫。易知,在任何一個 2^k * 2^k 的棋盤中,用到的 L 型骨牌個數恰為 (4^k-1)/3 。

算法系列(一):分治政策--棋盤覆寫算法系列(一):分治政策--棋盤覆寫

            (a)          (b)            (c)           (d)           圖1-2 4種不同形态的L型骨牌

        用分治政策,可以設計解棋盤問題的一個簡捷的算法。

        在本例分析之前先簡單回顧分治政策,“分治”,顧名思義,分而治之:分治法的基本思想是将一個規模為n的問題分解為k個規模較小的子問題,這些子問題互相獨立且與原問題相同。遞歸地解這些子問題,然後将各子問題的解合并得到原問題的解。

基本模式:                                                                          

算法系列(一):分治政策--棋盤覆寫算法系列(一):分治政策--棋盤覆寫
算法系列(一):分治政策--棋盤覆寫算法系列(一):分治政策--棋盤覆寫

二、求解

        數學歸納法證明:問題有解

     (1) k=1時,有解

算法系列(一):分治政策--棋盤覆寫算法系列(一):分治政策--棋盤覆寫

     (2) k=2時,有解

算法系列(一):分治政策--棋盤覆寫算法系列(一):分治政策--棋盤覆寫

     (3) 設k-1時成立(k>=2),則k時?

          将 2^k * 2^k 棋盤分割為 4 個 2^(k-1) * 2^(k-1) 子棋盤,如下圖所示。

算法系列(一):分治政策--棋盤覆寫算法系列(一):分治政策--棋盤覆寫

         特殊方格必位于 4 個較小子棋盤之一中,其餘 3 個子棋盤中無特殊方格。為了将這 3 個無特殊方格的子棋盤轉化為特殊盤,我們可以用一個 L 型骨牌覆寫這 3 個較小的棋盤的彙合處,如下圖所示,這 3 個子棋盤上被 L 型骨牌覆寫的方格就成為該棋盤上的特殊方格,進而将原問題化為 4 個較小規模的棋盤覆寫問題。遞歸的使用這種分割,直至棋盤簡化為 2^1x2^1 棋盤。

算法系列(一):分治政策--棋盤覆寫算法系列(一):分治政策--棋盤覆寫

三、程式

     例:每一個L型骨牌用相等的數字填充。從1,2,3,......,(0号為特殊方塊所在位置),按左上、右上、右下、左下順序依次填充

                                   示例:當 k=3,即2^3x2^3:                                                                                     

算法系列(一):分治政策--棋盤覆寫算法系列(一):分治政策--棋盤覆寫
算法系列(一):分治政策--棋盤覆寫算法系列(一):分治政策--棋盤覆寫

程式分析及代碼(Java):

版本(1.0):我們先把窗體繪制出來:

/**
 * @author Sherly-Liu
 * @version 1.0(繪制窗體)
 */
class WindowsFrame extends JFrame implements ActionListener {

	// 菜單
	JMenuBar bar = new JMenuBar();
	JMenu fileMenu = new JMenu("  檔案     ");
	JMenuItem setupItem = new JMenuItem("  建立      ");// 菜單項
	JMenuItem quitItem = new JMenuItem("  退出      ");

	JMenu helpMenu = new JMenu("  幫助     ");
	JMenuItem aboutItem = new JMenuItem("關于ChessboardCover");

	JLabel backgroundLabel = new JLabel(); // 背景
	Container contentPane = this.getContentPane();

	// 建立任務的視窗
	JFrame setupFrame;
	// 建立任務視窗元素

	JPanel jPanel1, jPanel2, jPanel3, jPanel4;
	JLabel jLabel1, jLabel2, jLabel3, jLabel4, jLabel5;
	JTextField drTextField, dcTextField;// 特殊方塊行、列
	JButton confirmButton, cancelButton;// 确認、取消按鈕
	JComboBox k_cmb;// 下拉清單(問題規模k)

	/**
	 * 視窗類構造方法
	 */
	public WindowsFrame() {
		super("ChessboardCover(棋盤覆寫)");
		initialize();

	}

	/**
	 * 主視窗元素初始化
	 */
	public void initialize() {
		// 将視窗位置放在螢幕中央
		Dimension screensize = Toolkit.getDefaultToolkit().getScreenSize();
		this.setSize(880, 600);
		Dimension framesize = this.getSize();
		int x = (int) screensize.getWidth() / 2 - (int) framesize.getWidth()
				/ 2;
		int y = (int) screensize.getHeight() / 2 - (int) framesize.getHeight()
				/ 2;
		this.setLocation(x, y);

		// 設定背景圖檔,把Lable加進LayoutPane
		try {
			backgroundLabel.setIcon(new ImageIcon(ImageIO.read(new File(
					"drawable", "background.jpg"))));
		} catch (IOException e) {
			e.printStackTrace();
		}

		this.getLayeredPane().add(backgroundLabel,
				new Integer(Integer.MIN_VALUE));
		backgroundLabel.setBounds(0, 20, 880, 550);

		contentPane.setLayout(null);

		this.setJMenuBar(bar);
		fileMenu.add(setupItem);
		fileMenu.addSeparator();// 分割線
		fileMenu.add(quitItem);

		helpMenu.add(aboutItem);

		bar.add(fileMenu);
		bar.add(helpMenu);

		setupItem.addActionListener(this);
		quitItem.addActionListener(this);
		aboutItem.addActionListener(this);

		((JPanel) contentPane).setOpaque(false);// 内容面闆設定為透明,LayoutPane面闆背景才顯現
		this.setResizable(false);
		this.setVisible(true);

		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

	}

	/**
	 * 建立問題視窗初始化
	 */
	public void setupInit() {
		setupFrame = new JFrame("setup");
		jPanel1 = new JPanel();
		jPanel2 = new JPanel();
		jPanel3 = new JPanel();
		jPanel4 = new JPanel();
		jLabel1 = new JLabel("k=   ");
		jLabel2 = new JLabel("   将建立2^k寬度的棋盤");
		jLabel3 = new JLabel("特殊方格的位置: ");
		jLabel4 = new JLabel("行: ");
		jLabel5 = new JLabel("          列: ");
		Font font = new Font("隸書", Font.BOLD, 18);// 字型大小的設定
		jLabel1.setFont(font);
		jLabel2.setFont(font);
		drTextField = new JTextField(8);
		dcTextField = new JTextField(8);
		String[] item = { "1", "2", "3", "4", "5", "6", "7" };
		k_cmb = new JComboBox(item);

		confirmButton = new JButton("确定");
		confirmButton.addActionListener(new SetupActionListener());

		cancelButton = new JButton("取消");
		cancelButton.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				setupFrame.dispose();

			}
		});

		jPanel1.add(jLabel1);
		jPanel1.add(k_cmb);
		jPanel1.add(jLabel2);
		jPanel2.add(jLabel3, BorderLayout.WEST);
		jPanel3.add(jLabel4);
		jPanel3.add(drTextField);
		jPanel3.add(jLabel5);
		jPanel3.add(dcTextField);
		jPanel4.add(confirmButton);
		jPanel4.add(cancelButton);

		setupFrame.setLayout(new GridLayout(4, 1, 5, 5));

		setupFrame.add(jPanel1);
		setupFrame.add(jPanel2);
		setupFrame.add(jPanel3);
		setupFrame.add(jPanel4);
		setupFrame.setVisible(true);
		setupFrame.setSize(400, 220);
		Dimension scrs = Toolkit.getDefaultToolkit().getScreenSize();
		Dimension fs = setupFrame.getSize();

		int x = (int) scrs.getWidth() / 2 - (int) fs.getWidth() / 2;
		int y = (int) scrs.getHeight() / 2 - (int) fs.getHeight() / 2;
		setupFrame.setLocation(x, y);

	}

	/**
	 * 菜單響應事件
	 */
	@Override
	public void actionPerformed(ActionEvent e) {
		// TODO Auto-generated method stub
		String command = e.getActionCommand();
		// 建立問題
		// ********************************************
		if (command.equals("  建立      ")) {
			setupInit();
		}
		// 退出***************
		if (command.equals("  退出      ")) {
			int result = JOptionPane.showConfirmDialog(null, "是否真的退出程式?",
					"提示資訊", JOptionPane.YES_NO_OPTION,
					JOptionPane.QUESTION_MESSAGE);
			if (result == 0) {
				System.exit(0);
			}
		}

		// 關于******************************************
		if (command.equals("關于ChessboardCover")) {
			JOptionPane.showMessageDialog(null, "ChessboardCover Demo 1.0"
					+ "\n" + "Author:劉曉玲" + "\n" + "2015.12",
					"關于ChessboardCover", JOptionPane.INFORMATION_MESSAGE, null);
		}

	}

	/**
	 * 命名内部類,建立問題監聽器
	 */
	private class SetupActionListener implements ActionListener {
		@Override
		public void actionPerformed(ActionEvent e) {

		}
	}

}

// ***************************************
public class Main {
	public static void main(String[] args) {
		new WindowsFrame();
	}

}
           

        運作一下程式:

算法系列(一):分治政策--棋盤覆寫算法系列(一):分治政策--棋盤覆寫

版本(1.1):設計棋盤的資料結構:

(1)重載Label标簽,添加行列坐标屬性以及顔色;

import javax.swing.JLabel;
/**
 * @author Sherly-Liu 
 * 重載标簽,增加行列坐标屬性
 * @param lable标簽
 * @param row行坐标
 * @param column列坐标
 */
public class MyLabel extends JLabel {
	JLabel label;
	private int row;// 行坐标
	private int column;// 列坐标
	
	public MyLabel(int row, int column) {
		super();
		label = new JLabel();
		this.row = row;
		this.column = column;
	}

	public int getRow() {
		return row;
	}

	public void setRow(int row) {
		this.row = row;
	}

	public int getColumn() {
		return column;
	}

	public void setColumn(int column) {
		this.column = column;
	}
}
           

(2)設計L型骨牌的資料結構

import java.awt.Color;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

import javax.swing.JPanel;
/**
 * @author Sherly-Liu L型骨牌類:組成為三個呈L型相連的lable标簽
 * @param lable1
 * @param lable2
 * @param lable3
 * @param tile骨牌編号
 */
public class L_Chess extends JPanel {
	MyLabel label1;
	MyLabel label2;
	MyLabel label3;
	Color color;

	private int tile;// 骨牌編号

	/**
	 * 構造方法:參數:三個标簽的橫縱坐标以及骨牌編号
	 */
	public L_Chess(int label1_r, int label1_c, int label2_r, int label2_c,
			int label3_r, int label3_c, int tile) {
		super();
		this.tile = tile;
		label1 = new MyLabel(label1_r, label1_c);
		label2 = new MyLabel(label2_r, label2_c);
		label3 = new MyLabel(label3_r, label3_c);
		color = getRandomColor();
		MyMouseListener listener = new MyMouseListener();
		label1.addMouseListener(listener);
		label2.addMouseListener(listener);
		label3.addMouseListener(listener);
	}

	public int getTile() {
		return tile;
	}

	public void setTile(int tile) {
		this.tile = tile;
	}

	/**
	 * 擷取一個随機顔色
	 * 
	 * @return Color
	 */
	public Color getRandomColor() {
		int r1 = (int) (Math.random() * 255); // 通過調用Math類的random産生随機數
		int g1 = (int) (Math.random() * 255); // 再通過随機數分别設定三原色,紅綠藍
		int b1 = (int) (Math.random() * 255);
		Color color = new Color(r1, g1, b1); // 建立一個顔色對象

		return color;
	}

	/**
	 * 命名内部類,滑鼠點選監聽器
	 */
	private class MyMouseListener implements MouseListener {

		@Override
		public void mouseClicked(MouseEvent e) {
			label1.setText(tile + "");
			label1.setBackground(color);
			label2.setText(tile + "");
			label2.setBackground(color);
			label3.setText(tile + "");
			label3.setBackground(color);
		}

		@Override
		public void mouseEntered(MouseEvent e) {
		}

		@Override
		public void mouseExited(MouseEvent e) {
		}

		@Override
		public void mousePressed(MouseEvent e) {
			label1.setVisible(true);
			label2.setVisible(true);
			label3.setVisible(true);
		}

		@Override
		public void mouseReleased(MouseEvent e) {
		}

	}
}
           

(3)設計棋盤的資料結構

import java.awt.Color;

import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.LineBorder;

/**
 * @author Sherly-Liu 棋盤類
 * @param tr表示棋盤左上角行坐标
 * @param tc表示棋盤左上角列坐标
 * @param dr表示特殊方格的行坐标
 * @param dc表示特殊方格的列坐标
 * @param SIZE
 *            =2^k。棋盤的規格為2^k*2^k
 * 
 */
public class Chessboard extends JPanel {
	int k; // 棋盤的規格為2^k*2^k
	int SIZE; // SIZE=2^k
	int[][] b;// 棋格,棋盤規格為2^k*2^k個棋格
	int tile = 1;// 骨牌編号
	L_Chess[] chess;// L型骨牌,個數為 (4^k-1)/3
	int dr;// 表示特殊方格的行坐标
	int dc;// 表示特殊方格的列坐标

	// 構造方法
	public Chessboard(int k, int dr, int dc) {
		this.k = k;
		this.dr = dr;
		this.dc = dc;
		SIZE = (int) Math.pow(2, k);
		b = new int[SIZE][SIZE];// 申請n*n數組空間
		int temp = (((int) Math.pow(4, k)) - 1) / 3;// L型骨牌,個數為 (4^k-1)/3
		chess = new L_Chess[temp];
		Fill(0, 0, SIZE, dr, dc);
		InitUI();
	}

	/**
	 * Fill方法:每一個L型骨牌用相等的數字填充。從1,2,3,......,(0号為特殊方塊所在位置),按左上、右上、右下、左下順序依次填充
	 */
	public void Fill(int tr, int tc, int size, int dr, int dc) {
		if (size == 1)
			return;
		else {
			int t = tile++;
			int s = size / 2;// 将棋盤四等分,即将問題分解為四個SIZE=2^(k-1)的子問題
			if ((dr < tr + s) && (dc < tc + s)) {// 特殊方格在左上子棋盤
				// b[tr + s - 1][tc + s] = b[tr + s][tc + s] = b[tr + s][tc + s
				// - 1] = t;// 覆寫第一個L型骨牌:在右上、右下、左下交界處
				chess[t - 1] = new L_Chess(tr + s - 1, tc + s, tr + s, tc + s,
						tr + s, tc + s - 1, t);// 覆寫第一個L型骨牌:在右上、右下、左下交界處
				// 采用遞歸分别求解每個子問題
				Fill(tr, tc, s, dr, dc);
				Fill(tr, tc + s, s, tr + s - 1, tc + s);
				Fill(tr + s, tc + s, s, tr + s, tc + s);
				Fill(tr + s, tc, s, tr + s, tc + s - 1);

			} else if ((dr < tr + s) && (dc >= tc + s)) {// 特殊方格在右上子棋盤
				// b[tr + s - 1][tc + s - 1] = b[tr + s][tc + s - 1] = b[tr +
				// s][tc
				// + s] = t;// 覆寫第一個L型骨牌:在左上、左下、右下交界處
				chess[t - 1] = new L_Chess(tr + s - 1, tc + s - 1, tr + s, tc
						+ s - 1, tr + s, tc + s, t);// 覆寫第一個L型骨牌:在左上、左下、右下交界處
				// 采用遞歸分别求解每個子問題
				Fill(tr, tc, s, tr + s - 1, tc + s - 1);
				Fill(tr, tc + s, s, dr, dc);
				Fill(tr + s, tc + s, s, tr + s, tc + s);
				Fill(tr + s, tc, s, tr + s, tc + s - 1);
			} else if ((dr >= tr + s) && (dc >= tc + s)) {// 特殊方格在右下子棋盤
				// b[tr + s - 1][tc + s - 1] = b[tr + s - 1][tc + s] = b[tr +
				// s][tc
				// + s - 1] = t;// 覆寫第一個L型骨牌:在右上、左上、左下交界處
				chess[t - 1] = new L_Chess(tr + s - 1, tc + s - 1, tr + s - 1,
						tc + s, tr + s, tc + s - 1, t);// 覆寫第一個L型骨牌:在右上、左上、左下交界處
				// 采用遞歸分别求解每個子問題
				Fill(tr, tc, s, tr + s - 1, tc + s - 1);
				Fill(tr, tc + s, s, tr + s - 1, tc + s);
				Fill(tr + s, tc + s, s, dr, dc);
				Fill(tr + s, tc, s, tr + s, tc + s - 1);

			} else if ((dr >= tr + s) && (dc < tc + s)) {// 特殊方格在左下子棋盤
				// b[tr + s - 1][tc + s - 1] = b[tr + s - 1][tc + s] = b[tr +
				// s][tc
				// + s] = t;// 覆寫第一個L型骨牌:在左上、右上、右下交界處
				chess[t - 1] = new L_Chess(tr + s - 1, tc + s - 1, tr + s - 1,
						tc + s, tr + s, tc + s, t);// 覆寫第一個L型骨牌:在左上、右上、右下交界處
				// 采用遞歸分别求解每個子問題
				Fill(tr, tc, s, tr + s - 1, tc + s - 1);
				Fill(tr, tc + s, s, tr + s - 1, tc + s);
				Fill(tr + s, tc + s, s, tr + s, tc + s);
				Fill(tr + s, tc, s, dr, dc);
			}
		}

	}

	/**
	 * 初始化UI
	 */
	public void InitUI() {
		this.setBounds(0, 0, 880, 500);
		this.setVisible(true);
		this.setBackground(Color.WHITE);
		this.setLayout(null);
		int width = 880 / SIZE;
		int height = 500 / SIZE;
		// 特殊方格
		JLabel label = new JLabel();
		label.setOpaque(true);
		label.setBorder(new LineBorder(Color.GRAY));
		label.setBackground(Color.WHITE);
		label.setText("0");
		label.setBounds(dc * width, dr * height, width, height);
		this.add(label);
		for (int i = 0; i < chess.length; i++) {
			chess[i].label1.setOpaque(true);
			chess[i].label1.setBorder(new LineBorder(Color.GRAY));
			chess[i].label1.setVisible(true);
			chess[i].label2.setOpaque(true);
			chess[i].label2.setBorder(new LineBorder(Color.GRAY));
			chess[i].label2.setVisible(true);
			chess[i].label3.setOpaque(true);
			chess[i].label3.setBorder(new LineBorder(Color.GRAY));
			chess[i].label3.setVisible(true);

			chess[i].label1.setBounds(chess[i].label1.getColumn() * width,
					chess[i].label1.getRow() * height, width, height);
			chess[i].label2.setBounds(chess[i].label2.getColumn() * width,
					chess[i].label2.getRow() * height, width, height);
			chess[i].label3.setBounds(chess[i].label3.getColumn() * width,
					chess[i].label3.getRow() * height, width, height);

			this.add(chess[i].label1);// 參數:(Component comp,int index)
			this.add(chess[i].label2);
			this.add(chess[i].label3);
		}

	}

	public void Show() {
		for (int i = 0; i < chess.length; i++) {
			chess[i].label1.setText(chess[i].getTile() + "");
			chess[i].label1.setBackground(chess[i].color);
			
			chess[i].label2.setText(chess[i].getTile() + "");
			chess[i].label2.setBackground(chess[i].color);
			
			chess[i].label3.setText(chess[i].getTile() + "");
			chess[i].label3.setBackground(chess[i].color);
			
		}
	}
}
           

版本(1.2):實作建立問題按鈕的監聽事件:

/**
	 * 命名内部類,建立問題監聽器
	 */
	private class SetupActionListener implements ActionListener {
		@Override
		public void actionPerformed(ActionEvent e) {
			setupFrame.dispose();

			if (drTextField.getText().toString().equals("")
					|| dcTextField.getText().toString().equals("")) {
				JOptionPane.showMessageDialog(null, "特殊方塊的行值、列值不允許為空", "警告",
						JOptionPane.WARNING_MESSAGE, null);

			} else {

				int k = Integer.valueOf(k_cmb.getSelectedItem().toString());
				int dc = Integer.valueOf(dcTextField.getText().toString()) - 1;
				int dr = Integer.valueOf(drTextField.getText().toString()) - 1;
				int n = (int) Math.pow(2, k);

				if (dr >= n || dc >= n) {
					JOptionPane.showMessageDialog(null, "特殊方塊的行值、列值不允許大于 2^k ",
							"警告", JOptionPane.WARNING_MESSAGE, null);

				} else if (dr < 0 || dc < 0) {
					JOptionPane.showMessageDialog(null, "特殊方塊的行值、列值不允許小于等于 0 ",
							"警告", JOptionPane.WARNING_MESSAGE, null);

				} else {
					if (chessboardPanel != null) {
						contentPane.remove(chessboardPanel);
					}
					chessboardPanel = new Chessboard(k, dr, dc);
					contentPane.add(chessboardPanel);
					showButton.setVisible(true);
					showButton.setEnabled(true);
				}

			}
		}

	}
           

四、程式運作結果

算法系列(一):分治政策--棋盤覆寫算法系列(一):分治政策--棋盤覆寫

        實作了點選一下棋格自動填充L型骨牌,點選Show All!按鈕全部填充:

算法系列(一):分治政策--棋盤覆寫算法系列(一):分治政策--棋盤覆寫

PS:類圖

算法系列(一):分治政策--棋盤覆寫算法系列(一):分治政策--棋盤覆寫

(PS:之是以這一次把代碼分析得那麼細是因為有很多同學總是開頭難,不知道要怎麼開始寫代碼,是以加上了詳細的版本說明,一步一步引導大家寫程式,很适合新手看,當然老程式看看就可以過了,也歡迎指出程式設計上的不足一起探讨。)

源碼下載下傳位址:http://download.csdn.net/detail/qq_22145801/9687074

參考:計算機算法設計與分析 電子工業出版社 王曉東編著