計算機算法設計與分析之棋盤覆蓋問題


一、引子

近期又又一次上了算法課,如今想來有點汗顏。大學期間已經學習了一個學期。到如今卻依舊感覺僅僅是把老師講過的題目弄懂了,並沒有學到算法的一些好的分析方法和思路,碰到一個新的問題后往往感覺非常棘手,痛定思痛之后認為還是好好再學習一遍。爭取能理解透徹每種算法的思路和核心,同一時候也勸誡各位同行們做事要腳踏實地,不能應付老師的作業,最后吃虧的還是自己啊。


二、棋盤覆蓋問題

       在一個由2^k *2^k個方格組成的棋盤中,恰有一個方格與其它方格不同。稱該方格為一特殊方格,且稱該棋盤
為一特殊棋盤。現有四種L型骨牌例如以下圖所看到的,要用這四種骨牌覆蓋棋盤上除特殊方格之外的其它全部格子,且兩個L型骨牌不能相互覆蓋。

      

三、解題思路

對於復雜問題。我們的一種經常使用思路是簡化問題,簡化到我們能一眼能看出問題的答案,這里也一樣。

當k=1時,問題簡化為一個2*2的棋盤的問題。因為僅僅有四個格子,且含有一個特殊格子,這樣就僅僅能用一個相應的L型骨牌覆蓋了。問題已經非常easy了。
在此 我們又一次定義四種L型骨牌:



在棋盤中,我們採用(行。列)來表示某一個格子,由於(x,y)這樣的表示對圖像處理的人來說是有歧義的,我們更願意覺得第一維是行,第二維是列。

如果在2*2的棋盤中。特殊格子出如今(0,0)這個位子上,則我們要使用0型骨牌覆蓋其余位置,同理,如果特殊格子出如今(0,1)這個位置上,我們要用1型骨牌覆蓋其余位置。更具有一般性的。如果特殊格子出如今2*2的(row,col)這個位置上,我們要使用row*2+col型骨牌覆蓋其余位置,row和col都是從0開始索引的。


當k=2時,問題變成了一個4*4的棋盤問題,這個時候問題略顯復雜,我們就想啊。假設能夠把它變成2*2的棋盤問題多好啊,好吧,我們就把它分成四個2*2的子棋盤,對於那個有特殊的格子的2*2子棋盤,非常快變能夠解決,剩下的三個呢?讓我們畫出圖好好看看。



如果特殊格子出如今(0,2)這個位置,如圖3所看到的,那么對於含有特殊格子的右上角的子棋盤我們用0型骨牌填充,如圖4。那么剩余的三個子棋盤呢,這個時候我們發現左上角僅僅能覆蓋3型和2型,其它兩種會有剩余空格。如果覆蓋2型骨牌,后面的左下角必定無法全然覆蓋(自己能夠試一下),則僅僅能使用3型骨牌覆蓋,以此類推,我們也能夠覆蓋左下角和右下角此時僅僅剩三個格子沒有覆蓋,如圖5所看到的。

如今細致觀測剩余的三個格子,我們發現他們都是分開在三個子棋盤里。那么這些空格子在子棋盤中是無法直接被覆蓋的,由於每一個子棋盤僅僅剩一個空格子了,我們是不是能夠把這個空格子當成一個特殊格子,這樣四個子棋盤都是含有一個特殊格子的小棋盤。這樣原問題就變成了四個相同的子問題,再求解了每一個子棋盤后,我們再對三個假的子棋盤格子進行覆蓋(如圖6)

那么怎樣選擇空格作為子棋盤的特殊格子呢。通過觀察我們發現,對於含有特殊格子的子棋我們不用指定特殊格子,對於剩下三個子棋盤,我們指定四個子棋盤的交界處的格子作為特殊格子。

四、歸納

如今我們歸納總結一下我們的解題方案:首先將大棋盤四等分分成四個2^(k-1)*2^(k-1)的子棋盤。然后對沒有特殊格子的子棋盤指定假的特殊格子的位置。將原問題分解成四個子問題進行求解。當然四個子問題可能還不能直接求解,可能還有繼續遞歸求解。如果已經求解了四個子問題,我們應該用特定的L型骨牌覆蓋三個假的特殊格子,至此整個大棋盤已經求解完畢。
我們會想整個求解過程,首先把問題簡化,簡化到直接看出答案的地步,然后分析略微復雜一點的情況,總結規律,運用分治的思想。將問題化解成四個子問題,分別求解四個子問題,在求解子問題的過程中可能還會有子問題。不斷的遞歸求解,終於每一個子問題解決。大問題也解決。


五、代碼實現

#include <iostream>
#include<memory.h>
using namespace std;
int **chessBoard;
int k=1;
int length=0;
int blueRow=-1;
int blueCol=-1;
void init();
void fillBoard(int **_chessBoard,int r,int c,int type);
void fillChessBoard(int **_chessBoard,int k,int blue_row,int blue_col,int baseRow,int baseCol);
void output(int **_chessBoard);
int main()
{
    init();

    fillChessBoard(chessBoard,k,blueRow,blueCol,0,0);
    output(chessBoard);

    for(int i=0;i<length;i++)
    {
        delete [] chessBoard[i];
    }
    delete chessBoard;
    return 0;
}
void init()
{
    cout<<"please input number k:"<<endl;
    cin>>k;
    cout<<"please input blue grid coordinate:row column"<<endl;
    cin>>blueRow>>blueCol;

    length=(1<<k);//長和寬均為2^k
    //動態分配2^k數組
    chessBoard=new int*[length];
    for(int i=0;i<length;i++)
    {
        chessBoard[i]=new int[length];
        //初始化為-1
        memset(chessBoard[i],-1,length*sizeof(int));
    }
    chessBoard[blueRow][blueCol]=4;
}
void output(int **_chessBoard)
{
    for(int i=0;i<length;i++)
    {
        for(int j=0;j<length;j++)
        {
            cout<<" "<<_chessBoard[i][j];
        }
        cout<<endl;
    }
    cout<<endl;
}
void fillBoard(int **_chessBoard,int r,int c,int type)
{
    for(int i=0;i<2;i++)
    {
        for(int j=0;j<2;j++)
        {
            if((i*2+j)!=type)
            {
                if(_chessBoard[r+i][c+j]!=-1)
                    cout<<"error"<<endl;
                _chessBoard[r+i][c+j]=type;
            }
        }
    }
}
void fillChessBoard(int **_chessBoard,int level,int blue_row,int blue_col,int baseRow,int baseCol)
{
    if(level==1)
    {
        int type=(blue_row<<1)+blue_col;
        fillBoard (_chessBoard,baseRow,baseCol,type);

    }else
    {
        //否則進行四等分,中間連接處自行填充

        //新的四分格的寬度
        int new_length=1<<(level-1);
        int type=(blue_row/new_length)*2+blue_col/new_length;
        for(int r=0;r<2;r++)
        {
            for(int c=0;c<2;c++)
            {
                if((r*2+c)==type)
                {
                    fillChessBoard (_chessBoard,level-1,blue_row-r*new_length,blue_col-c*new_length,r*new_length+baseRow,c*new_length+baseCol);
                }
                else
                {
                    fillChessBoard (_chessBoard,level-1,(new_length-1)*(1-r),(new_length-1)*(1-c),r*new_length+baseRow,c*new_length+baseCol);
                }
            }
        }
        fillBoard (_chessBoard,baseRow+new_length-1,baseCol+new_length-1,type);
    }

}

六、代碼解釋

程序輸入為棋盤大小的參數k、特殊格子的行和列,輸出為整個棋盤,我們用4表示特殊格子,0-3分別表示0-3型的L型骨牌。

fillBoard()函數是使用某種L型骨牌覆蓋棋盤的函數。fillChessBoard()是遞歸求解整個問題的函數,輸入為棋盤指針。參數level也就是k。blue_row,blue_col是特殊格子在子棋盤的坐標系的位置,baseRow和baseCol是子棋盤的(0,0)點在整個的大棋盤中的坐標。


持續跟新中。敬請關注








免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM