使用多线程并行执行解决网格大小超过9 * 9的数独

时间:2017-04-24 12:12:18

标签: java multithreading performance

我正在开展一个我解决数独难题的项目N ^ 2 * N ^ 2  其中N小于等于20。

我已经编写了一个单线程数独求解器,它可以正常使用具有N值为5的谜题,但是如果我增加N的值,如N = 10或20,则代码变得无法响应。我也尝试使用线程池(java.concurrent)并分配N^2个线程来执行并行。但它没有用,任何人都可以给我任何可以提高性能的解决方案。

这是我的单线程方法:

public class SUDOKU {

    public static int[][] grid;

    public boolean solveSUDOKU() {
        int row;
        int col;
        int[] blankCell = findBlankLocation();
        row = blankCell[0];
        col = blankCell[1];
        if (row == -1) {
            // means will have filled the grid, return;
            return true;
        }
        // we need to fill grid[row][col] cell
        for (int i = 1; i <= grid.length; i++) {
            // check if number i is safe for grid[row][col] cell
            if (isSafe(row, col, i)) {
                // means its safe to fill the number
                grid[row][col] = i;
                // fill the rest of the grid
                if (solveSUDOKU()) {
                    return true;
                }
                // if we are here that means current selection of number didnt
                // work, revert back the changes
                grid[row][col] = 0;
            }
        }
        return false; // This will cause the backtracking
    }

    public boolean isSafe(int row, int col, int n) {
        // we need to check row contains number n OR
        // Column contains number n OR
        // Block in which cell appears contains number n
        // If Any of the above statement is true, return false
        if (!UsedInRow(row, n)
                && !UsedInColumn(col, n)
                && !UsedInBox((int) (row - row % Math.sqrt(grid.length)),
                        (int) (col - col % Math.sqrt(grid.length)), n)) {
            return true;
        }
        return false;
    }

    // check if n not in particular row
    public boolean UsedInRow(int row, int n) {
        for (int i = 0; i < grid.length; i++) {
            if (grid[row][i] == n) {
                return true;
            }
        }
        return false;
    }

    // check if n not in particular column
    public boolean UsedInColumn(int col, int n) {
        for (int i = 0; i < grid.length; i++) {
            if (grid[i][col] == n) {
                return true;
            }
        }
        return false;
    }

    // check if n not in particular box
    public boolean UsedInBox(int boxStartRow, int boxStartCol, int n) {
        for (int i = 0; i < Math.sqrt(grid.length); i++) {
            for (int j = 0; j < Math.sqrt(grid.length); j++) {
                if (grid[i + boxStartRow][j + boxStartCol] == n) {
                    return true;
                }
            }
        }
        return false;
    }

    public int[] findBlankLocation() {
        int[] cell = new int[2]; // cell[0]-row cell[1] -column
        for (int i = 0; i < grid.length; i++) {
            for (int j = 0; j < grid.length; j++) {
                if (grid[i][j] == 0) {
                    cell[0] = i;
                    cell[1] = j;
                    return cell;
                }
            }
        }
        cell[0] = -1;
        cell[1] = -1;
        return cell; // means grid is full
    }

    public void print() {
        for (int row = 0; row < grid.length; row++) {
            if (row % Math.sqrt(grid.length) == 0) {
                System.out.println(); // for more readability
            }
            for (int col = 0; col < grid.length; col++) {
                if (col % Math.sqrt(grid.length) == 0) {
                    System.out.print(" "); // for more readability
                }
                System.out.print(grid[row][col] + " ");

            }
            System.out.println();
        }
    }

    public static void main(String[] args) {
        grid = new int[][]{
            {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
            {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
            {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
            {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
            {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
            {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
            {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
            {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
            {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
            {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
            {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
            {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
            {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
            {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
            {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
            {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}};
        SUDOKU s = new SUDOKU();
        if (s.solveSUDOKU()) {
            s.print();
        } else {
            System.out.println("NO SOLUTION");
        }
    }

}

1 个答案:

答案 0 :(得分:2)

事情并非那么简单。你不会在一个问题上抛出“平行”和“更多线程”而你会神奇地提高性能。

在您的情况下,您有一个巨大的搜索树来覆盖。含义:您的方法可能被称为数百万次次。

首先要做的事情是:了解您的代码正在做什么。

含义:你可以从简单的print语句开始;或者通过添加“调用计数器”。或者,如果需要了解现有的性能分析工具,可以帮助您了解程序花费大部分时间的哪些部分。然后你开始研究优化方法。

第一个明显的候选人:你正在计算

Math.sqrt(grid.length)
喜欢在3或5个不同的地方。这是一个相当昂贵的解决方案。 sqrt()可以轻松避免(通过计算一次值并将其放入所有其他代码将使用的常量)。它也可能值得研究模数计算(尽管可能更难摆脱)。

除此之外:为了并行化您的代码,您必须了解该代码如何处理其数据。你看,你不能只用n个线程来处理相同的单个数字数组。因为突然间你不得不担心一致性;你不能让一个线程覆盖内容,而另一个线程正在读取它。

所以,基本答案是:

  • 了解单线程场景中的代码
  • 通过避免任何过于昂贵的事情来优化您的单线程解决方案

然后;当你真正理解你的代码在做什么时;而你仍然不快乐;然后你可能会考虑在你的问题上投入“更多线程”。但即便如此:请记住线程不是免费的。在它们之间创建和切换存在巨大的开销。对于纯粹的CPU密集型操作而言,拥有更多线程将无济于事。

当您有大量IO操作(以及等待从IO读取数据的线程)时,线程有助于提高吞吐量。