<p>
星期天小哼和小哈約在一起玩桌游,他們正在玩一個非常古怪的撲克游戲——“小貓釣魚”。游戲的規則是這樣的:將一副撲克牌平均分成兩份,每人拿一份。小哼先拿出手中的第一張撲克牌放在桌上,然后小哈也拿出手中的第一張撲克牌,并放在小哼剛打出的撲克牌的上面,就像這樣兩人交替出牌。出牌時,如果某人打出的牌與桌上某張牌的牌面相同,即可將兩張相同的牌及其中間所夾的牌全部取走,并依次放到自己手中牌的 尾。當任意一人手中的牌全部出完時,游戲結束,對手獲勝。
</p>
<p>假如游戲開始時,小哼手中有 6 張牌,順序為 2 4 1 2 5 6,小哈手中也有 6 張牌,順序為 3 1 3 5 6 4,最終誰會獲勝呢?現在你可以拿出紙牌來試一試。接下來請你寫一個程序來自動判斷誰將獲勝。這里我們做一個約定,小哼和小哈手中牌的牌面只有 1~9。</p>
<p>我們先來分析一下這個游戲有哪幾種操作。小哼有兩種操作,分別是出牌和贏牌。這恰好對應隊列的兩個操作,出牌就是出隊,贏牌就是入隊。小哈的操作和小哼是一樣的。而桌子就是一個棧,每打出一張牌放到桌上就相當于入棧。當有人贏牌的時候,依次將牌從桌上拿走,這就相當于出棧。那如何解決贏牌的問題呢?贏牌的規則是:如果某人打出的牌與桌上的某張牌相同,即可將兩張牌以及中間所夾的牌全部取走。那如何知道桌上已經有哪些牌了呢?最簡單的方法就是枚舉桌上的每一張牌,當然也有更好的辦法我們待會再說。OK,小結一下,我們需要兩個隊列、一個棧來模擬整個游戲。</p>
<p>首先我們先來創建一個結構體用來實現隊列,如下。</p>
<p>struct queue{ int data[1000]; int head; int tail; }
</p>
<p>上面代碼中 head 用來存儲隊頭,tail 用來存儲隊尾。數組 data 用來存儲隊列中的元素,數組 data 的大小我預設為 1000,其實應該設置得更大一些,以防數組越界。當然對于 題的數據來說 1000 已經足夠了。</p>
<p> 再創建一個結構體用來實現棧,如下。</p>
<p>struct stack{ int data[10]; int top; };
</p>
<p>其中 top 用來存儲棧頂,數組 data 用來存儲棧中的元素,大小設置為 10。因為只有 9種不同的牌面,所以桌上最多可能有 9 張牌,因此數組大小設置為 10 就夠了。提示一下:為什么不設置為 9 呢?因為 C 語言數組下標是從 0 開始的。</p>
<p>其中 top 用來存儲棧頂,數組 data 用來存儲棧中的元素,大小設置為 10。因為只有 9種不同的牌面,所以桌上最多可能有 9 張牌,因此數組大小設置為 10 就夠了。提示一下:為什么不設置為 9 呢?因為 C 語言數組下標是從 0 開始的。</p>
<p>struct queue q1,q2; struct stack s;
</p>
<p> 接下來來初始化一下隊列和棧。</p>
<p>//初始化隊列q1和q2為空,此時兩人手中都還沒有牌 q1.head=1; q1.tail=1; q2.head=1; q2.tail=1; //初始化棧s為空,最開始的時候桌上也沒有牌 s.top=0;
</p>
<p>接下來需要讀入小哼和小哈最初時手中的牌,分兩次讀入,每次讀入 6 個數,分別插入q1和q2中。</p>
<p>// 依次像隊列中插入6個數 // 小哼手中的牌 printf("請設置小哼手中的牌:\n"); for (i=1; i<=6; i++) { scanf("%d",&q1.data[q1.tail]); q1.tail++; } printf("\n"); printf("請設置小哈手中的牌:\n"); // 小哈手中的牌 for (i=1; i<=6; i++) { scanf("%d",&q2.data[q2.tail]); q2.tail++; } printf("\n");
</p>
<p>現在準備工作已經基 上做好了,游戲正式開始,小哼先出牌。</p>
<p>t=q1.data[q1.head]; //小哼先亮出一張牌
</p>
<p>小哼打出第一張牌,也就是 q1 的隊首,我們將這張牌存放在臨時變量 t 中。接下來我們要判斷小哼當前打出的牌是否能贏得桌上的牌。也就是判斷桌上的牌與 t 有沒有相同的,如何實現呢?我們需要枚舉桌上的每一張牌與 t 進行比對,具體如下:</p>
<p>flag=0; for(i=1;i<=top;i++) { if(t==s[i]) { flag=1; break; } }
</p>
<p>如果 flag 的值為 0 就表明小哼沒能贏得桌上的牌,將打出的牌留在桌上</p>
<p>if(flag==0){ //小哼此輪沒有贏牌 q1.head++; //小哼已經打出一張牌,所以要把打出的牌出隊 s.top++; s.data[s.top]=t; //再把打出的牌放到桌上,即入棧 }
</p>
<p>如果 flag 的值為 1 就表明小哼可以贏得桌上的牌,需要將贏得的牌依次放入小哼的手中。</p>
<p>if(flag==1) { //小哼此輪可以贏牌 q1.head++;//小哼已經打出一張牌,所以要把打出的牌出隊 q1.data[q1.tail]=t; //因為此輪可以贏牌,所以緊接著把剛才打出的牌又放到手中牌的對尾 q1.tail++; while(s.data[s.top]!=t) //把桌上可以贏得的牌(從當前桌面最頂部一張牌開始取,直至取到與打出的牌相同為止)依次放到手中牌的末尾 { q1.data[q1.tail]=s.data[s.top]; //依次放入隊尾 q1.tail++; s.top--; //棧中少了一張牌,所以棧頂要減1 } }
</p>
<p>小哼出牌的所有階段就模擬完了,小哈出牌和小哼出牌是一樣的。接下來我們要判斷游戲如何結束。即只要兩人中有一個人的牌用完了游戲就結束了。因此需要在模擬兩人出牌代碼的外面加一個 while 循環來判斷,如下。</p>
<p>while(q1.head<q1.tail && q2.head<q2.tail ) //當隊列q1和q2都不為空的時候執行循環
</p>
<p>最后一步,輸出誰最終贏得了游戲,以及游戲結束后獲勝者手中的牌和桌上的牌。如果
小哼獲勝了那么小哈的手中一定沒有牌了(隊列 q2 為空),即 q2.head==q2.tail,具體輸出如下。</p>
<p>if(q2.head==q2.tail){ printf("小哼win\n"); printf("小哼當前手中的牌是"); for(i=q1.head;i<=q1.tail-1;i++) printf(" %d",q1.data[i]); if(s.top>0) //如果桌上有牌則依次輸出桌上的牌 { printf("\n桌上的牌是"); for(i=1;i<=s.top;i++) printf(" %d",s.data[i]); }else printf("\n桌上已經沒有牌了");} }
</p>
<p>反之,小哈獲勝,代碼的實現也是差不多的,就不再贅述了。到此,所有的代碼實現就都講完了。</p>
<p>在上面我們講解的所有實現中,每個人打出一張牌后,判斷能否贏牌這一點可以優化。之前我們是通過枚舉桌上的每一張牌來實現的,即用了一個 for 循環來依次判斷桌上的每一張牌是否與打出的牌相等。其實有更好的辦法來解決這個問題,就是用一個數組來記錄桌上有哪些牌。因為牌面只有 1~9,因此只需開一個大小為 10 的數組來記錄當前桌上已經有哪些牌面就可以了。</p>
<p>int book[10];
</p>
<p>這里我再一次使用了 book 這個單詞,因為這個單詞有記錄、登記的意思,而且單詞拼寫簡潔。另外很多國外的算法書籍在處理需要標記問題的時候也都使用 book 這個單詞,因此我這里就沿用了。當然你也可以使用 mark 等你自己覺得好理解的單詞啦。下面需要將數組 book[1]~book[9]初始化為 0,因為剛開始桌面上一張牌也沒有。</p>
<p>for(i=1;i<=9;i++) book[i]=0;
</p>
<p>接下來,如果桌面上增加了一張牌面為 2 的牌,那就需要將 book[2]設置為 1,表示牌面為 2 的牌桌上已經有了。當然如果這張牌面為 2 的牌被拿走后,需要及時將 book[2]重新設置為 0,表示桌面上已經沒有牌面為 2 的牌了。這樣一來,尋找桌上是否有與打出的牌牌面相同的牌,就不需要再循環枚舉桌面上的每一張牌了,而只需用一個 if 判斷即可。這一點是不是有點像第 1 章第 1 節的桶排序的方法呢?具體如下。</p>
<p>t=q1.data[q1.head]; //小哼先亮出一張牌 if(book[t]==0) // 表明桌上沒有牌面為t的牌 { //小哼此輪沒有贏牌 q1.head++; //小哼已經打出一張牌,所以要把打出的牌出隊 s.top++; s.data[s.top]=t; //再把打出的牌放到桌上,即入棧 book[t]=1; //標記桌上現在已經有牌面為t的牌 }
</p>
<p>OK,算法的實現講完了,下面給出完整的代碼,如下:</p>
<p>``
include <stdio.h>
include <string.h>
// 使用結構體 (queue) 來表示小哼和小哈手中的牌
struct queue{
int data[1000]; // 用戶手中的牌
int head; // 隊首
int tail; // 對尾
};
// 使用結構體 (stack) 來表示桌面上的牌
struct stack{
int data[10]; // 桌面上所有的牌
int top; // 桌面上最上邊的牌
};
int main(){
struct queue q1, q2; // 聲名小哼, 小哈
struct stack s; // 聲名桌面上的牌
int book[10]; // 用來標記桌面上的牌
int i, t;
q1.head = 1; // 初始化小哼
q1.tail = 1;
q2.head = 1; // 初始化小哈
q2.tail = 1;
s.top = 0; // 初始化棧, 桌面上最上邊的的牌的索引
// 初始化用來標記的數組, 用來標記哪些牌出現過
for (i=1; i<=9; i++) {
book[i] = 0;
}
// 依次像隊列中插入6個數
// 小哼手中的牌
printf("請設置小哼手中的牌:\n");
for (i=1; i<=6; i++) {
scanf("%d",&q1.data[q1.tail]);
q1.tail++;
}
printf("\n");
printf("請設置小哈手中的牌:\n");
// 小哈手中的牌
for (i=1; i<=6; i++) {
scanf("%d",&q2.data[q2.tail]);
q2.tail++;
}
printf("\n");
// 當隊列不為空的時候執行循環 (判斷小哼小哈手中是否有牌)
while (q1.head < q1.tail && q2.head < q2.tail) {
// 游戲開始
// 小哼先出第一張牌
t = q1.data[q1.head];
// 判斷小哼是否能贏牌
if (book[t] == 0) {
// 沒有贏牌
q1.head++; // 出隊
s.top++;
s.data[s.top] = t; // 把牌加入到棧頂
book[t] = 1; // 標記桌面上已經出現了為 t 的牌
}else{
// 贏牌
q1.head++;
q1.data[q1.tail] = t;
q1.tail++;
while (s.data[s.top] != t) {
// 取消標記
book[s.data[s.top]] = 0;
q1.data[q1.tail] = s.data[s.top];
q1.tail++;
s.top--;
}
}
// 小哈出牌
t = q2.data[q2.head];
// 判斷小哈是否能贏牌
if (book[t] == 0) {
// 沒有贏牌
q2.head++; // 出隊
s.top++;
s.data[s.top] = t; // 把牌加入到棧頂
book[t] = 1; // 標記桌面上已經出現了為 t 的牌
}else{
// 贏牌
q2.head++;
q2.data[q2.tail] = t;
q2.tail++;
while (s.data[s.top] != t) {
// 取消標記
book[s.data[s.top]] = 0;
q2.data[q2.tail] = s.data[s.top];
q2.tail++;
s.top--;
}
}
}
// 判斷輸贏
if (q2.head == q2.tail) {
printf("小哼 win \n");
printf("小哼當前手中的牌是:");
for (i=q1.head; i<q1.tail; i++) {
printf(" %d",q1.data[i]);
}
if (s.top > 0) { // 如果桌上有牌則依次輸出桌上的牌
printf("桌上的牌是:");
for (i=1; i<=s.top; i++) {
printf("%d",s.data[i]);
}
}else{
printf("\n 桌上已經沒有牌了 ^o^ ");
}
}else{
printf("小哈 win \n");
printf("小哈當前手中的牌是:");
for (i=q1.head; i<q1.tail; i++) {
printf(" %d",q1.data[i]);
}
if (s.top > 0) { // 如果桌上有牌則依次輸出桌上的牌
printf("桌上的牌是:");
for (i=1; i<=s.top; i++) {
printf("%d",s.data[i]);
}
}else{
printf("\n 桌上已經沒有牌了 ^o^ ");
}
}
printf("\n");
return 0;
} ``</p>