DLX算法解数独游戏 Java版

使用DLX算法解数独游戏。
初学Java,算法代码是由C代码转换的。
DLX.java是算法类,Sudoku.java是界面类。
解号称世界上最难的数独用时10ms以内。


算法详解

Sudoku 数独 Dancing Links模板

使用方法

int n = 9;
DLX dlx = new DLX(n * n * n + 1, 4 * n * n);
                dlx.setNum(5);//最多求5个解,默认为2
                dlx.solve(data);//int data[9][9];

                List solutions = dlx.getSolutions();


代码下载

https://code.google.com/p/sudoku-dlx/source/checkout

DLX.java

import java.util.ArrayList;
import java.util.List;

public class DLX
{
	private static final int ROW = 4096 + 50;
	private static final int COL = 1024 + 50;
	private static final int N = 4 * 9 * 9;
	private static final int m = 3;

	DLXNode row[] = new DLXNode[ROW];
	DLXNode col[] = new DLXNode[COL];
	DLXNode head;

	private int n;
	private int num = 2;
	private int size[] = new int[COL];
	int data[][] = new int[9][9];
	List solutions;

	public DLX(int r, int c)
	{
		n = m * m;
		head = new DLXNode(r, c);
		head.U = head.D = head.L = head.R = head;
		for (int i = 0; i < c; ++i)
		{
			col[i] = new DLXNode(r, i);
			col[i].L = head;
			col[i].R = head.R;
			col[i].L.R = col[i].R.L = col[i];
			col[i].U = col[i].D = col[i];
			size[i] = 0;
		}

		for (int i = r - 1; i > -1; --i)
		{
			row[i] = new DLXNode(i, c);
			row[i].U = head;
			row[i].D = head.D;
			row[i].U.D = row[i].D.U = row[i];
			row[i].L = row[i].R = row[i];
		}
	}

	public void addNode(int r, int c)
	{
		DLXNode p = new DLXNode(r, c);
		p.R = row[r];
		p.L = row[r].L;
		p.L.R = p.R.L = p;
		p.U = col[c];
		p.D = col[c].D;
		p.U.D = p.D.U = p;
		++size[c];
	}

	public void addNode(int i, int j, int k)
	{
		int r = (i * n + j) * n + k;
		addNode(r, i * n + k - 1);
		addNode(r, n * n + j * n + k - 1);
		addNode(r, 2 * n * n + block(i, j) * n + k - 1);
		addNode(r, 3 * n * n + i * n + j);
	}

	int block(int x, int y)
	{
		return x / m * m + y / m;
	}

	public void cover(int c)
	{
		if (c == N)
			return;

		col[c].delLR();
		DLXNode R, C;
		for (C = col[c].D; C != col[c]; C = C.D)
		{
			if (C.c == N)
				continue;
			for (R = C.L; R != C; R = R.L)
			{
				if (R.c == N)
					continue;
				--size[R.c];
				R.delUD();
			}
			C.delLR();
		}
	}

	public void resume(int c)
	{
		if (c == N)
			return;

		DLXNode R, C;
		for (C = col[c].U; C != col[c]; C = C.U)
		{
			if (C.c == N)
				continue;
			C.resumeLR();
			for (R = C.R; R != C; R = R.R)
			{
				if (R.c == N)
					continue;
				++size[R.c];
				R.resumeUD();
			}
		}
		col[c].resumeLR();
	}

	public boolean solve(int depth)
	{
		if (head.L == head)
		{
			int solution[][] = new int[n][n];
			for (int i = 0; i < n; ++i)
				for (int j = 0; j < n; ++j)
					solution[i][j] = data[i][j];
			solutions.add(solution);

			if (solutions.size() == num)
				return true;
			return false;
		}
		int minSize = 1 << 30;
		int c = -1;
		DLXNode p;
		for (p = head.L; p != head; p = p.L)
			if (size[p.c] < minSize)
			{
				minSize = size[p.c];
				c = p.c;
			}
		cover(c);

		for (p = col[c].D; p != col[c]; p = p.D)
		{
			DLXNode cell;
			p.R.L = p;
			for (cell = p.L; cell != p; cell = cell.L)
			{
				cover(cell.c);
			}
			p.R.L = p.L;
			int rr = p.r - 1;
			data[rr / (n * n)][rr / n % n] = rr % n + 1;
			if (solve(depth + 1))
				return true;

			p.L.R = p;
			for (cell = p.R; cell != p; cell = cell.R)
				resume(cell.c);
			p.L.R = p.R;
		}

		resume(c);
		return false;
	}

	public boolean solve(int data[][])
	{
		init(data);
		return solve(0);
	}

	public void init(int data[][])
	{
		solutions = new ArrayList();
		int i, j, k;
		for (i = 0; i < n; ++i)
			for (j = 0; j < n; ++j)
			{
				if (data[i][j] > 0)
				{
					addNode(i, j, data[i][j]);
				} else
				{
					for (k = 1; k <= n; ++k)
						addNode(i, j, k);
				}
			}
	}

	public void setNum(int num)
	{
		this.num = num;
	}

	public int getNum()
	{
		return num;
	}

	public List getSolutions()
	{
		return solutions;
	}
}

class DLXNode
{
	int r,c;
	DLXNode U,D,L,R;
	
	DLXNode()
	{
		r = c = 0;
	}
	
	DLXNode(int r, int c)
	{
		this.r = r;
		this.c = c;
	}

	DLXNode(int r, int c, DLXNode U, DLXNode D, DLXNode L, DLXNode R)
	{
		this.r = r;
		this.c = c;
		this.U = U;
		this.D = D;
		this.L = L;
		this.R = R;
	}

	public void delLR()
	{
		L.R = R;
		R.L = L;
	}
	
	public void delUD()
	{
		U.D = D;
		D.U = U;
	}
	
	public void resumeLR()
	{
		L.R = R.L = this;
	}
	
	public void resumeUD()
	{
		U.D = D.U = this;
	}
}

Sudoku.java

import java.awt.*;
import java.awt.event.*;

import javax.swing.*;
import javax.swing.border.LineBorder;
import javax.swing.border.TitledBorder;
import javax.swing.plaf.basic.BasicArrowButton;
import javax.swing.text.*;

public class Sudoku extends JPanel
{
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	private static final int width = 9;
	private static final int height = 9;
	private int data[][] = new int[height][width];
	private String infoStr = "";

	JButton solve = new JButton("Solve"), reset = new JButton("Reset"), clean = new JButton("Clean");
	BasicArrowButton right = new BasicArrowButton(BasicArrowButton.EAST), left = new BasicArrowButton(BasicArrowButton.WEST);
	JTextField sudokus[][] = new JTextField[height][width];
	JTextPane info = new JTextPane();

	java.util.List solutions;
	private int solutionIdx = 0;

	Sudoku()
	{
		setLayout(null);
		for (int r = 0; r < 3; ++r)
		{
			for (int c = 0; c < 3; ++c)
			{
				JPanel jp = new JPanel();
				jp.setLayout(null);
				jp.setBounds(r * 95 + 5, c * 95 + 5, 95, 95);
				jp.setBorder(new LineBorder(Color.black));
				
				for (int x = 0; x < 3; ++x)
					for (int y = 0; y < 3; ++y)
					{
						int j = r * 3 + x, i = c * 3 + y;
						sudokus[i][j] = new JTextField();
						sudokus[i][j].setBounds(30 * x + 5, 30 * y + 5, 25, 25);
						sudokus[i][j].setDocument(new NumberLenghtLimitedDmt(1));
						jp.add(sudokus[i][j]);
					}
				add(jp);
			}
		}

		solve.setBounds(85, 300, 75, 25);
		solve.addActionListener(new SolveAL());
		add(solve);

		reset.setBounds(175, 300, 75, 25);
		reset.addActionListener(new ResetAL());
		add(reset);

		clean.setBounds(265, 300, 75, 25);
		clean.addActionListener(new cleanAL());
		add(clean);

		left.setVisible(false);
		left.setBounds(170, 330, 50, 25);
		left.addActionListener(new prevAL());
		add(left);
		
		right.setVisible(false);
		right.setBounds(230, 330, 50, 25);
		right.addActionListener(new nextAL());
		add(right);

		JScrollPane jsp = new JScrollPane(info);
		info.setEditable(false);
		jsp.setBounds(300, 5, 140, 285);
		jsp.setBorder(new TitledBorder("Info"));
		add(jsp);
	}

	public boolean solve()
	{
		int i, j;
		for (i = 0; i < height; ++i)
		{
			for (j = 0; j < width; ++j)
			{
				try
				{
					data[i][j] = Integer.parseInt(sudokus[i][j].getText());
					sudokus[i][j].setEditable(false);
				} catch (NumberFormatException e)
				{
					data[i][j] = 0;
					sudokus[i][j].setEnabled(false);
				}
			}
		}

		int n = 9;
		long startTime = System.currentTimeMillis();
		DLX dlx = new DLX(n * n * n + 1, 4 * n * n);
		dlx.setNum(5);
		dlx.solve(data);
		solutions = dlx.getSolutions();

		if (solutions.size() > 1)
			infoStr += "Find multi solutions.\n";
		else if (solutions.size() > 0)
			infoStr += "Find one solution.\n";
		else
			infoStr += "Cannot find a solution.\n";
		infoStr += "Used time: " + (System.currentTimeMillis() - startTime) + " ms\n";

		return update();
	}

	public void reset(int data[][])
	{
		solve.setEnabled(true);
		int i, j;
		for (i = 0; i < height; ++i)
		{
			for (j = 0; j < width; ++j)
			{
				sudokus[i][j].setEnabled(true);
				sudokus[i][j].setEditable(true);
				if (data[i][j] == 0)
				{
					sudokus[i][j].setText("");
				} else
				{
					sudokus[i][j].setText(Integer.toString(data[i][j]));
				}
			}
		}
	}

	boolean update()
	{
		info.setText(infoStr);
		if (solutionIdx >= solutions.size())
			return false;

		left.setVisible(true);
		right.setVisible(true);
		int i, j;
		int solution[][] = solutions.get(solutionIdx);
		for (i = 0; i < height; ++i)
		{
			for (j = 0; j < width; ++j)
			{
				sudokus[i][j].setText(Integer.toString(solution[i][j]));
			}
		}
		return true;
	}

	public static void inFrame(JPanel jp, int width, int height)
	{
		String title = "Sudoku";
		JFrame frame = new JFrame(title);
		frame.addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent e)
			{
				System.exit(0);
			}
		});
		frame.getContentPane().add(jp, BorderLayout.CENTER);
		frame.setLocationByPlatform(true);
		frame.setSize(width, height);
		frame.setResizable(false);
		frame.setVisible(true);
	}

	public static void main(String[] args)
	{
		int data[][] = { 
				{ 8, 0, 0, 0, 0, 0, 0, 0, 0 }, 
				{ 0, 0, 3, 6, 0, 0, 0, 0, 0 }, 
				{ 0, 7, 0, 0, 9, 0, 2, 0, 0 },

				{ 0, 5, 0, 0, 0, 7, 0, 0, 0 }, 
				{ 0, 0, 0, 0, 4, 5, 7, 0, 0 }, 
				{ 0, 0, 0, 1, 0, 0, 0, 3, 0 },
		
				{ 0, 0, 1, 0, 0, 0, 0, 6, 8 }, 
				{ 0, 0, 8, 5, 0, 0, 0, 1, 0 }, 
				{ 0, 9, 0, 0, 0, 0, 4, 0, 0 }, 
			};
		Sudoku sudoku = new Sudoku();
		sudoku.reset(data);
		inFrame(sudoku, 450, 400);
	}

	class SolveAL implements ActionListener
	{
		public void actionPerformed(ActionEvent e)
		{
			infoStr += "Start solve this sudoku.\n";
			info.setText(infoStr);
			solve.setEnabled(false);
			solve();
		}
	}

	class ResetAL implements ActionListener
	{
		public void actionPerformed(ActionEvent e)
		{
			infoStr += "Reset sudoku data.\n";
			info.setText(infoStr);
			left.setVisible(false);
			right.setVisible(false);
			reset(data);
		}
	}

	class cleanAL implements ActionListener
	{
		public void actionPerformed(ActionEvent e)
		{
			infoStr = "Clean sudoku data.\n";
			info.setText(infoStr);
			solve.setEnabled(true);
			left.setVisible(false);
			right.setVisible(false);
			int i, j;
			for (i = 0; i < height; ++i)
			{
				for (j = 0; j < width; ++j)
				{
					sudokus[i][j].setText("");
					sudokus[i][j].setEnabled(true);
					sudokus[i][j].setEditable(true);
				}
			}
		}
	}
	
	class prevAL implements ActionListener
	{
		public void actionPerformed(ActionEvent e)
		{
			int size = solutions.size();
			if(size > 0)
			{
				solutionIdx = (solutionIdx - 1 + size) % size;
				infoStr += "The " + (solutionIdx+1) + "th solution.\n";
				update();
			}
		}
	}

	class nextAL implements ActionListener
	{
		public void actionPerformed(ActionEvent e)
		{
			int size = solutions.size();
			if(size > 0)
			{
				solutionIdx = (solutionIdx + 1) % size;
				infoStr += "The " + (solutionIdx+1) + "th solution.\n";
				update();
			}
		}
	}

	class NumberLenghtLimitedDmt extends PlainDocument
	{
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;
		private int limit;

		public NumberLenghtLimitedDmt(int limit)
		{
			super();
			this.limit = limit;
		}

		public void insertString(int offset, String str, AttributeSet attr) throws BadLocationException
		{
			if (str == null)
			{
				return;
			}
			if ((getLength() + str.length()) <= limit)
			{
				char[] upper = str.toCharArray();
				int length = 0;
				for (int i = 0; i < upper.length; i++)
				{
					if (upper[i] >= '0' && upper[i] <= '9')
					{
						upper[length++] = upper[i];
					}
				}
				super.insertString(offset, new String(upper, 0, length), attr);
			}
		}
	}

}


你可能感兴趣的:(ACM算法,Java)