【問題描述】
在一個 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