?? c++從零開始(八)——c++樣例一.txt
字號:
上面算法中提到的“再退回到更上一次的方案選擇”,也就是說第一次過河選擇了一個商人一個仆人的方案,接著選擇了一個商人回來的方案,此時如果選擇兩個仆人過河的方案將是錯誤的,則將重新選擇過河的方案。再假設此時所有過河的方案都失敗了,則只有再向后退以重新選擇回來的方案,如選擇一個仆人回來。對于此,由于這里只要求退回到上一次的狀態,也就是人數布局及選擇的方案,則可以將這些統一放在容器中,而它們各自都只依靠順序關系,即第二次過河的方案一定在第一次過河的方案成功的前提下才可能考慮,因此使用數組這個帶有順序關系的容器即可。
第一步:上面算法的資源有兩個:坐船的方案和兩岸的人數布局。坐船的方案最多五種,在此使用一個char類型的數字來映射它,即此8位二進制數的前4位用補碼格式來解釋得到的數字代表仆人的數量,后4位則代表商人的數量。因此一個商人和一個仆人就是( 1 << 4 ) | 1。兩岸的人數布局,即兩岸的商人數和仆人數,由于總共才3+3=6個人,這都可以使用char類型的數字就能映射,但只能映射一個人數,而兩岸的人數實際共有4個(左右兩岸的商人數和仆人數),則這里使用一個char[4]來實現(實際最好是使用結構來映射而不是char[4],下篇說明)。如char a[4];表示一人數布局,則a[0]表示河岸左側的商人數,a[1]表示左側的仆人數,a[2]表示河岸右側的商人數,a[3]表示右側的仆人數。
注意前面說的容器,在此為了裝可選的坐船方案故應有一容器,使用數組,如char sln[5];。在此還需要記錄已用的坐船方案,由于數組的元素具備順序關系,所以不用再生成一容器,直接使用一char數字記錄一下標,當此數字為3時,表示sln[0]、sln[1]和sln[2]都已經用過且都失敗了,當前可用的為sln[3]和sln[4]。同樣,為了裝已成功的坐船方案作用后的人數布局及當時所選的方案,就需要兩個容器,在此使用數組(實際應該鏈表)char oldLayout[4][200], cur[200];。oldLayout就是記錄已成功的方案的容器,其大小為200,表示假定在200次內,一定就已經得出結果了,否則就會因為超出數組上限而可能發生內存訪問違規,而為什么是可能在《C++從零開始(十五)》中說明。
前面說過數組這種容器無法確定里面的有效元素,必須依靠外界來確定,對此,使用一unsigned char curSln;來記錄oldLayout和cur中的有效元素的個數。規定當curSln為3時,表示oldLayout[0~3][0]、oldLayout[0~3][1]和oldLayout[0~3][2]都有效,同樣cur[0]、cur[1]和cur[2]都有效,而之后的如cur[3]等都無效。
第二步:操作有:執行過河方案、執行回來方案、檢查方案是否成功、退回到上一次方案選擇、是否所有人都過河、判斷人數布局是否相同。如下:
前兩個操作:將當前的左岸人數減去相應的方案定的人數,而右岸則加上人數。要表現當前左岸人數,可以用oldLayout[0][ curSln ]和oldLayout[1][ curSln ]表示,而相應方案的人數則為( sln[ cur[ curSln ] ] & 0xF0 ) >> 4和sln[ cur[ curSln ] ] & 0xF。由于這兩個操作非常類似,只是一個是加則另一個就是減,故將其定義為函數,則為了在函數中能操作oldLayout、curSln等變量,就需要將這些變量定義為全局變量。
檢查是否成功:即看是否
oldLayout[1][ curSln ] > oldLayout[0][ curSln ] && oldLayout[0][ curSln ]以及是否
oldLayout[3][ curSln ] > oldLayout[2][ curSln ] && oldLayout[2][ curSln ]
并且保證各自不為負數以及沒有和原來的方案沖突。檢查是否和原有方案相同就是枚舉所有原由方案以和當前方案比較,由于比較復雜,在此將其定義為函數,通過返回bool類型來表示是否沖突。
退回上一次方案或到下一個方案的選擇,只用curSln--或curSln++即可。而是否所有人都過河,則只用oldLayout[0~1][ curSln ]都為0而oldLayout[2~3][ curSln ]都為3。而判斷人數布局是否相同,則只用相應各元素是否相等即可。
第三步:下面剩下的就沒什么東西了,只需要按照算法說的順序,將剛才的各操作拼湊起來,并注意“重復直到所有人都過河了”轉成do while即可。如下:
#include
// 分別表示一個商人、一個仆人、兩個商人、兩個仆人、一個商人一個仆人
char sln[5] = { ( 1 << 4 ), 1, ( 2 << 4 ), 2, ( 1 << 4 ) | 1 };
unsigned char curSln = 1;
char oldLayout[4][200], cur[200];
void DoSolution( char b )
{
unsigned long oldSln = curSln - 1; // 臨時變量,出于效率
oldLayout[0][ curSln ] =
oldLayout[0][ oldSln ] - b * ( ( sln[ cur[ curSln ] ] & 0xF0 ) >> 4 );
oldLayout[1][ curSln ] =
oldLayout[1][ oldSln ] - b * ( sln[ cur[ curSln ] ] & 0xF );
oldLayout[2][ curSln ] =
oldLayout[2][ oldSln ] + b * ( ( sln[ cur[ curSln ] ] & 0xF0 ) >> 4 );
oldLayout[3][ curSln ] =
oldLayout[3][ oldSln ] + b * ( sln[ cur[ curSln ] ] & 0xF );
}
bool BeRepeated( char b )
{
for( unsigned long i = 0; i < curSln; i++ )
if( oldLayout[0][ curSln ] == oldLayout[0][ i ] &&
oldLayout[1][ curSln ] == oldLayout[1][ i ] &&
oldLayout[2][ curSln ] == oldLayout[2][ i ] &&
oldLayout[3][ curSln ] == oldLayout[3][ i ] &&
( ( i & 1 ) ? 1 : -1 ) == b ) // 保證過河后的方案之間比較,回來后的方案之間比較
// i&1等效于i%2,i&7等效于i%8,i&63等效于id
return true;
return false;
}
void main()
{
char b = 1;
oldLayout[0][0] = oldLayout[1][0] = 3;
cur[0] = oldLayout[2][0] = oldLayout[3][0] = 0;
for( unsigned char i = 0; i < 200; i++ ) // 初始化每次選擇方案時的初始化方案為sln[0]
cur[ i ] = 0; // 由于cur是全局變量,在VC中,其已經被賦值為0
// 原因涉及到數據節,在此不表
do
{
DoSolution( b );
if( ( oldLayout[1][ curSln ] > oldLayout[0][ curSln ] && oldLayout[0][ curSln ] ) ||
( oldLayout[3][ curSln ] > oldLayout[2][ curSln ] && oldLayout[2][ curSln ] ) ||
oldLayout[0][ curSln ] < 0 || oldLayout[1][ curSln ] < 0 ||
oldLayout[2][ curSln ] < 0 || oldLayout[3][ curSln ] < 0 ||
BeRepeated( b ) )
{
// 重新選擇本次的方案
P:
cur[ curSln ]++;
if( cur[ curSln ] > 4 )
{
b = -b;
cur[ curSln ] = 0;
curSln--;
if( !curSln )
break; // 此題無解
goto P; // 重新檢查以保證cur[ curSln ]的有效性
}
continue;
}
b = -b;
curSln++;
}
while( !( oldLayout[0][ curSln - 1 ] == 0 && oldLayout[1][ curSln - 1 ] == 0 &&
oldLayout[2][ curSln - 1 ] == 3 && oldLayout[3][ curSln - 1 ] == 3 ) );
for( i = 0; i < curSln; i++ )
printf( "%d %d\t %d %d\n",
oldLayout[0][ i ],
oldLayout[1][ i ],
oldLayout[2][ i ],
oldLayout[3][ i ] );
}
上面數組sln[5]的初始化方式下篇介紹。上面的預編譯指令#include將在《C++從零開始(十)》中說明,這里可以不用管它。上面使用的函數printf的用法,請參考其它資料,這里它只是將變量的值輸出在屏幕上而已。
前面說此法是枚舉法,其基本上屬于萬能方法,依靠CPU的計算能力來實現,一般情況下程序員第一時間就會想到這樣的算法。它的缺點就是效率極其低下,大量的CPU資源都浪費在無謂的計算上,因此也是產生瓶頸的大多數原因。由于它的萬能,編程時很容易將思維陷在其中,如求和1到100,一般就寫成如下:
for( unsigned long i = 1, s = 0; i <= 100; i++ ) s += i;
但更應該注意到還可unsigned long s = ( 1 + 100 ) * 100 / 2;,不要被枚舉的萬能占據了頭腦。
上面的人數布局映射成一結構是最好的,映射成char[4]所表現的語義不夠強,代碼可讀性較差。下篇說明結構,并展示類型的意義——如何解釋內存的值。
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -