多執行緒結合經典位運算解n王后問題的優化

lt發表於2016-11-05

前文的基礎上,結合經典的位運算方法,得出了以下程式碼:

//package com.newflypig.eightqueen;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;


public class EightQueen10 {
    private static final short K=8;     //使用常量來定義,方便之後解N皇后問題
    private static short N=0;
    public static void main(String[] args) throws Exception {
        for(N=9;N<=17;N++){
            long count=0;
            Date begin =new Date();
            /**
             * 初始化棋盤,使用一維陣列存放棋盤資訊
             * chess[n]=X:表示第n行X列有一個皇后
             */

            List<short[]> chessList=new ArrayList<short[]>(N);
            for(short i=0;i<N;i++){
                short chess[]=new short[N];
                chess[0]=(short)(1<<i);
                chessList.add(chess);
            }

            short taskSize =(short)( N/2+(N%2==1?1:0) );
            // 建立一個執行緒池
            ExecutorService pool = Executors.newFixedThreadPool(taskSize);
            // 建立多個有返回值的任務
            List<Future<Long>> futureList = new ArrayList<Future<Long>>(taskSize);
            for (int i = 0; i < taskSize; i++) {
                Callable<Long> c = new EightQueenThread10(chessList.get(i));
                // 執行任務並獲取Future物件
                Future<Long> f = pool.submit(c);
                futureList.add(f);
            }
            // 關閉執行緒池
            pool.shutdown();

            for(short i=0; i<(short) (taskSize - (N%2==1?1:0)); i++){              
                count+=futureList.get(i).get();
            }
            count=count*2;
            if(N%2==1)
                count+=futureList.get(N/2).get();

            Date end =new Date();
            System.out.println("解決 " +N+ "皇后問題,用時:" +String.valueOf(end.getTime()-begin.getTime())+ "毫秒,計算結果:"+count);
        }
    }
}

class EightQueenThread10 implements Callable<Long>{
    private short[] chess;
    private short N;
    /**sum用來記錄皇后放置成功的不同佈局數*/
    public long sum = 0;

    /**upperlim用來標記所有列都已經放置好了皇后*/
    public long upperlim = 1;      

    public EightQueenThread10(short[] chess){
        this.chess=chess;
        this.N=(short) chess.length;
        upperlim = (upperlim << N) - 1;

    }


    @Override
    public Long call() throws Exception {
        //System.out.printf("chess[0]=%d\n",chess[0]);
        return queenPos(chess[0], chess[0]<<1, chess[0]>>1);

    }


    private long queenPos(long row, long ld, long rd)  
    {  
        if (row != upperlim)  
        {  
            // row,ld,rd進行“或”運算,求得所有可以放置皇后的列,對應位為0,  
            // 然後再取反後“與”上全1的數,來求得當前所有可以放置皇后的位置,對應列改為1  
            // 也就是求取當前哪些列可以放置皇后  
            long pos = upperlim & ~(row | ld | rd);   
            while (pos != 0)    // 0 -- 皇后沒有地方可放,回溯  
            {  
                // 拷貝pos最右邊為1的bit,其餘bit置0  
                // 也就是取得可以放皇后的最右邊的列  
                long p = pos & -pos;  

                // 將pos最右邊為1的bit清零  
                // 也就是為獲取下一次的最右可用列使用做準備,  
                // 程式將來會回溯到這個位置繼續試探  
                pos -= p;   

                // row + p,將當前列置1,表示記錄這次皇后放置的列。  
                // (ld + p) << 1,標記當前皇后左邊相鄰的列不允許下一個皇后放置。  
                // (ld + p) >> 1,標記當前皇后右邊相鄰的列不允許下一個皇后放置。  
                // 此處的移位操作實際上是記錄對角線上的限制,只是因為問題都化歸  
                // 到一行網格上來解決,所以表示為列的限制就可以了。顯然,隨著移位  
                // 在每次選擇列之前進行,原來N×N網格中某個已放置的皇后針對其對角線  
                // 上產生的限制都被記錄下來了  
                queenPos(row + p, (ld + p) << 1, (rd + p) >> 1);
            }  
        }  
        else     
        {  
            // row的所有位都為1,即找到了一個成功的佈局,回溯  
            sum++;
        }  

        return sum;
    }

}

優化的效果非常好,幾乎比前文提高了一個數量級。與同演算法的單執行緒相比,也提高了幾倍(我的cpu core i3 4010U 1.7Ghz)

D:\>java EightQueen9 
解決 9皇后問題,用時:0毫秒,計算結果:352
解決 10皇后問題,用時:16毫秒,計算結果:724
解決 11皇后問題,用時:15毫秒,計算結果:2680
解決 12皇后問題,用時:47毫秒,計算結果:14200
解決 13皇后問題,用時:281毫秒,計算結果:73712
解決 14皇后問題,用時:1279毫秒,計算結果:365596
解決 15皇后問題,用時:8908毫秒,計算結果:2279184
D:\>javac EightQueen10.java

D:\>java EightQueen10
解決 9皇后問題,用時:0毫秒,計算結果:352
解決 10皇后問題,用時:0毫秒,計算結果:724
解決 11皇后問題,用時:16毫秒,計算結果:2680
解決 12皇后問題,用時:0毫秒,計算結果:14200
解決 13皇后問題,用時:46毫秒,計算結果:73712
解決 14皇后問題,用時:219毫秒,計算結果:365596
解決 15皇后問題,用時:1341毫秒,計算結果:2279184
解決 16皇后問題,用時:8237毫秒,計算結果:14772512
解決 17皇后問題,用時:60794毫秒,計算結果:95815104

D:\>java QueenTest3
請輸入皇后數目:15
總共解數為:2279184
程式總共執行時間:5.491s

D:\>java QueenTest3
請輸入皇后數目:16
總共解數為:14772512
程式總共執行時間:36.348s

與“世上最快”的單執行緒c程式並駕齊驅。

D:\>timer nq 15
Timer 9.01 : Igor Pavlov : Public domain : 2009-05-31
N Queens program by Jeff Somers.
        allagash98@yahoo.com or jsomers@alumni.williams.edu
Start:   Sat Nov 05 07:59:20 2016
End:    Sat Nov 05 07:59:22 2016
Calculations took 2 seconds.
For board size 15, 2279184 solutions found.

Kernel Time  =     0.015 =    1%
User Time    =     1.294 =   84%
Process Time =     1.310 =   85%
Global Time  =     1.526 =  100%

D:\>timer nq 16
Timer 9.01 : Igor Pavlov : Public domain : 2009-05-31
N Queens program by Jeff Somers.
        allagash98@yahoo.com or jsomers@alumni.williams.edu
Start:   Sat Nov 05 07:59:25 2016
End:    Sat Nov 05 07:59:34 2016
Calculations took 9 seconds.
For board size 16, 14772512 solutions found.

Kernel Time  =     0.062 =    0%
User Time    =     8.611 =   98%
Process Time =     8.673 =   99%
Global Time  =     8.744 =  100%

D:\>timer nq 17
Timer 9.01 : Igor Pavlov : Public domain : 2009-05-31
N Queens program by Jeff Somers.
        allagash98@yahoo.com or jsomers@alumni.williams.edu
Start:   Sat Nov 05 07:59:50 2016
End:    Sat Nov 05 08:00:51 2016
Calculations took 61 seconds.
Equals 1 minute and 1 second.
For board size 17, 95815104 solutions found.

Kernel Time  =     0.577 =    0%
User Time    =    60.403 =   98%
Process Time =    60.980 =   99%
Global Time  =    61.256 =  100%

值得一提的,上述java程式和c程式都利用了同樣的演算法,並都利用翻轉剪枝一半。

相關文章