算法系列(一):分治策略--棋盘覆盖

算法系列(一):分治策略--棋盘覆盖

一、分析

问题描述:

            图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

参考:计算机算法设计与分析 电子工业出版社 王晓东编著


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值