棋盤覆蓋問題——詳解(C++)

【問題描述】

在一個 2 ^k × 2 ^k 個方格組成的棋盤中,若有一個方格與其他方格不同,則稱該方格爲一特殊方格,且稱該棋盤爲一個特殊棋盤.顯然特殊方格在棋盤上出現的位置有4^k 種情形.因而對任何k≥0,有4^k種不同的特殊棋盤.

下圖中的特殊棋盤是當k=3時64個特殊棋盤中的一個:
在這裏插入圖片描述
在棋盤覆蓋問題中,要用下圖中 4 中不同形態的** L 型骨牌覆蓋一個給定的特殊棋牌上除特殊方格以外的所有方格,且任何 2 個 L 型骨牌不得重疊覆蓋**。易知,在任何一個 2^k × 2^k 的棋盤中,用到的 L 型骨牌個數恰爲 (4^k-1)/3 。

在這裏插入圖片描述
用分治策略,可以設計解棋盤問題的一個簡捷的算法。

當 k>0 時,將 2^k * 2^k 棋盤分割爲 4 個 2^(k-1) * 2^(k-1) 子棋盤,如下圖所示:
在這裏插入圖片描述
特殊方格必位於 4 個較小子棋盤之一中,其餘 3 個子棋盤中無特殊方格。

爲了將這 3 個無特殊方格的子棋盤轉化爲特殊棋盤,我們可以用一個 L 型骨牌覆蓋這 3 個較小的棋盤的匯合處,如下圖所示,這 3 個子棋盤上被 L 型骨牌覆蓋的方格就成爲該棋盤上的特殊方格,從而將原問題化爲 4 個較小規模的棋盤覆蓋問題。
在這裏插入圖片描述遞歸的使用這種分割,直至棋盤簡化爲 1x1 棋盤。

【算法實現】

下面討論棋盤覆蓋問題中數據結構的設計:

(1)棋盤:可以用一個二維數組board[size][size]表示一個棋盤,其中,size=2^k。爲了在遞歸處理的過程中使用同一個棋盤,將數組board設爲全局變量;
(2)子棋盤:整個棋盤用二維數組board[size][size]表示,其中的子棋盤由棋盤左上角的下標tr、tc和棋盤大小s表示;
(3)特殊方格:用board[dr][dc]表示特殊方格,dr和dc是該特殊方格在二維數組board中的下標;
(4) L型骨牌:一個2k×2k的棋盤中有一個特殊方格,所以,用到L型骨牌的個數爲(4^k-1)/3,將所有L型骨牌從1開始連續編號,用一個全局變量t表示。

【算法分析】
設T(k)是算法ChessBoard覆蓋一個2k×2k棋盤所需時間,從算法的劃分策略可知,T(k)滿足如下遞推式:

T(k) = 1             當k=0時
T(k) = 4 * T(k-1)    當k>0時

解此遞推式可得 T(k) = O(4^k)。

C++代碼1

#include<iostream>  
using namespace std;  
int tile=1;                   //L型骨牌的編號(遞增)  
int board[100][100];  //棋盤  
/***************************************************** 
* 遞歸方式實現棋盤覆蓋算法 
* 輸入參數: 
* tr--當前棋盤左上角的行號 
* tc--當前棋盤左上角的列號 
* dr--當前特殊方格所在的行號 
* dc--當前特殊方格所在的列號 
* size:當前棋盤的:2^k 
*****************************************************/  
void chessBoard ( int tr, int tc, int dr, int dc, int size )  
{  
    if ( size==1 )    //棋盤方格大小爲1,說明遞歸到最裏層  
        return;  
    int t=tile++;     //每次遞增1  
    int s=size/2;    //棋盤中間的行、列號(相等的)  
    //檢查特殊方塊是否在左上角子棋盤中  
    if ( dr<tr+s && dc<tc+s )              //在  
        chessBoard ( tr, tc, dr, dc, s );  
    else         //不在,將該子棋盤右下角的方塊視爲特殊方塊  
    {  
        board[tr+s-1][tc+s-1]=t;  
        chessBoard ( tr, tc, tr+s-1, tc+s-1, s );  
    }  
    //檢查特殊方塊是否在右上角子棋盤中  
    if ( dr<tr+s && dc>=tc+s )               //在  
        chessBoard ( tr, tc+s, dr, dc, s );  
    else          //不在,將該子棋盤左下角的方塊視爲特殊方塊  
    {  
        board[tr+s-1][tc+s]=t;  
        chessBoard ( tr, tc+s, tr+s-1, tc+s, s );  
    }  
    //檢查特殊方塊是否在左下角子棋盤中  
    if ( dr>=tr+s && dc<tc+s )              //在  
        chessBoard ( tr+s, tc, dr, dc, s );  
    else            //不在,將該子棋盤右上角的方塊視爲特殊方塊  
    {  
        board[tr+s][tc+s-1]=t;  
        chessBoard ( tr+s, tc, tr+s, tc+s-1, s );  
    }  
    //檢查特殊方塊是否在右下角子棋盤中  
    if ( dr>=tr+s && dc>=tc+s )                //在  
        chessBoard ( tr+s, tc+s, dr, dc, s );  
    else         //不在,將該子棋盤左上角的方塊視爲特殊方塊  
    {  
        board[tr+s][tc+s]=t;  
        chessBoard ( tr+s, tc+s, tr+s, tc+s, s );  
    }  
}  
  
void main()  
{  
    int size;  
    cout<<"輸入棋盤的size(大小必須是2的n次冪): ";  
    cin>>size;  
    int index_x,index_y;  
    cout<<"輸入特殊方格位置的座標: ";  
    cin>>index_x>>index_y;  
    chessBoard ( 0,0,index_x,index_y,size );  
    for ( int i=0; i<size; i++ )  
    {  
        for ( int j=0; j<size; j++ )  
            cout<<board[i][j]<<"/t";  
        cout<<endl;  
    }  
}  

C++代碼2

#include<iostream>  
#include<vector>  
#include<stack>  
   
using namespace std;  
   
vector<vector<int> > board(4);//棋盤數組,也可以作爲參數傳遞進chessBoard中去,作爲全局變量可以減少參數傳遞  
stack<int> stI;   //記錄當前所使用的骨牌號碼,使用棧頂元素填充棋盤數組  
int sL = 0;     //L型骨牌序號  
   
//所有下標皆爲0開始的C C++下標  
void chessBoard(int uRow, int lCol, int specPosR, int specPosC, int rowSize)  
{  
    if(rowSize ==1) return;  
    //static int sL = 0;棋牌和骨牌都可以用static代替,如果不喜歡用全局變量的話。  
    sL++;     
    stI.push(sL); //每遞歸深入一層,就把一個骨牌序號入棧  
    int halfSize = rowSize/2;//拆分  
   
    //注意:下面四個if else,肯定是隻有一個if成立,然後執行if句,而肯定有三個else語句要執行的,因爲肯定有一個是特殊位置,而其他三個是空白位置,需要填充骨牌。  
   
    //1如果特殊位置在左上角區域,則繼續遞歸,直到剩下一個格子,並且該格子已經填充,遇到函數頭一句if(rowSize == 1) return;就跳出一層遞歸。  
    //注意是一個區域或子棋盤,有一個或者多個格子,並不是就指一個格子。  
    if(specPosR<uRow+halfSize && specPosC<lCol+halfSize)  
        chessBoard(uRow, lCol, specPosR, specPosC, halfSize);  
    //如果其他情況  
    else 
    {  
        board[uRow+halfSize-1][lCol+halfSize-1] = stI.top();  
        //因爲特殊位置不在,所以可以選擇任意一個空格填充,但是本算法只填充左上角(也許不止一個格,也許有很多個格子)區域的右下角。大家仔細查一下,就知道下標[uRow+halfSize-1][lCol+halfSize-1]是本區域中最右下角的一個格子的下標號。  
        chessBoard(uRow, lCol, uRow+halfSize-1, lCol+halfSize-1, halfSize);  
        //然後是遞歸填充這個區域的其他空白格子。因爲上一句已經填充了[uRow+halfSize-1][lCol+halfSize-1]這個格子,所以,這個下標作爲特殊位置參數傳遞進chessBoard中。  
    }     
   
    //2右上角區域,解析類上  
    if(specPosR<uRow+halfSize && specPosC>=lCol+halfSize)  
        chessBoard(uRow, lCol+halfSize, specPosR, specPosC, halfSize);  
    else 
    {  
        board[uRow+halfSize-1][lCol+halfSize] = stI.top();  
        chessBoard(uRow, lCol+halfSize, uRow+halfSize-1, lCol+halfSize, halfSize);  
    }         
   
    //3左下角區域,類上  
    if(specPosR>=uRow+halfSize && specPosC<lCol+halfSize)  
        chessBoard(uRow+halfSize, lCol, specPosR, specPosC, halfSize);  
    else 
    {  
        board[uRow+halfSize][lCol+halfSize-1] = stI.top();  
        chessBoard(uRow+halfSize, lCol, uRow+halfSize, lCol+halfSize-1, halfSize);  
    }     
   
    //4右下角區域,類上  
    if(specPosR>=uRow+halfSize && specPosC>=lCol+halfSize)  
        chessBoard(uRow+halfSize, lCol+halfSize, specPosR, specPosC, halfSize);  
    else 
    {  
        board[uRow+halfSize][lCol+halfSize] = stI.top();  
        chessBoard(uRow+halfSize, lCol+halfSize, uRow+halfSize, lCol+halfSize, halfSize);  
    }     
   
    stI.pop();//本次骨牌號填充了三個格,填充完就出棧  
}  
   
void test()  
{  
    //初始化數組  
    for(int i=0; i<4; i++)  
    {  
        board[i].resize(4);  
    }  
   
    chessBoard(0, 0, 3, 3, 4);  
   
    //特殊位置填充0  
    board[3][3] = 0;  
   
    //序列輸出  
    for(int j=0; j<4; j++)  
    {  
        for(int i=0; i<4; i++)  
            cout<<board[j][i]<<"\t";  
        cout<<endl;  
    }  
    cout<<endl;  
}  
   
   
int main()  
{  
    test();  
    return 0;  
}  

參考自:zhwhong

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章