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

来源:互联网 发布:淘宝达人网站 编辑:程序博客网 时间:2024/04/30 10:25

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

一、分析

问题描述:

            图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加进LayoutPanetry {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() {@Overridepublic 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);}/** * 菜单响应事件 */@Overridepublic void actionPerformed(ActionEvent e) {// TODO Auto-generated method stubString 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 {@Overridepublic 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 {@Overridepublic void mouseClicked(MouseEvent e) {label1.setText(tile + "");label1.setBackground(color);label2.setText(tile + "");label2.setBackground(color);label3.setText(tile + "");label3.setBackground(color);}@Overridepublic void mouseEntered(MouseEvent e) {}@Overridepublic void mouseExited(MouseEvent e) {}@Overridepublic void mousePressed(MouseEvent e) {label1.setVisible(true);label2.setVisible(true);label3.setVisible(true);}@Overridepublic 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^kint SIZE; // SIZE=2^kint[][] b;// 棋格,棋盘规格为2^k*2^k个棋格int tile = 1;// 骨牌编号L_Chess[] chess;// L型骨牌,个数为 (4^k-1)/3int 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)/3chess = 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 {@Overridepublic 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

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


0 0
原创粉丝点击