算法题-游戏类问题
24.Algorithm Gossip: 洗扑克牌(乱数排列)
说明
洗扑克牌的原理其实与乱数排列是相同的,都是将一组数字(例如1~N)打乱重新排列,只不过洗扑克牌多了一个花色判断的动作而已。
解法
初学者通常会直接想到,随机产生1~N的乱数并将之存入阵列中,后来产生的乱数存入阵列前必须先检查阵列中是否已有重复的数字,如果有这个数就不存入,再重新产生下一个数,运气不好的话,重复的次数就会很多,程序的执行速度就很慢了,这不是一个好方法。
以1~52的乱数排列为例好了,可以将阵列先依序由1到52填入,然后使用一个回圈走访阵列,并随机产生1~52的乱数,将产生的乱数当作索引取出阵列值,并与目前阵列走访到的值相交换,如此就不用担心乱数重复的问题了,阵列走访完毕后,所有的数字也就重新排列了。
至于如何判断花色?这只是除法的问题而已,取商数判断花色,取余数判断数字,您可以直接看程序比较清楚。
代码:
#include <stdio.h> #include <stdlib.h> #include <time.h> #define N 52 int main(void) { int poker[N + 1]; int i, j, tmp, remain; // 初始化阵列 for(i = 1; i <= N; i++) poker[i] = i; srand(time(0)); // 洗牌 for(i = 1; i <= N; i++) { j = rand() % 52 + 1; tmp = poker[i]; poker[i] = poker[j]; poker[j] = tmp; } for(i = 1; i <= N; i++) { // 判断花色 switch((poker[i]-1) / 13) { case 0: printf("桃"); break; case 1: printf("心"); break; case 2: printf("砖"); break; case 3: printf("梅"); break; } // 扑克牌数字 remain = poker[i] % 13; switch(remain) { case 0: printf("K "); break; case 12: printf("Q "); break; case 11: printf("J "); break; default: printf("%d ", remain); break; } if(i % 13 == 0) printf("\n"); } return 0; }
25.Algorithm Gossip: Craps赌博游戏
说明一个简单的赌博游戏,游戏规则如下:玩家掷两个骰子,点数为1到6,如果第一次点数和为7或11,则玩家胜,如果点数和为2、3或12,则玩家输,如果和 为其它点数,则记录第一次的点数和,然后继续掷骰,直至点数和等于第一次掷出的点数和,则玩家胜,如果在这之前掷出了点数和为7,则玩家输。
解法 规则看来有些复杂,但是其实只要使用switch配合if条件判断来撰写即可,小心不要弄错胜负顺序即可。
#include <stdio.h> #include <stdlib.h> #include <time.h> #define WON 0 #define LOST 1 #define CONTINUE 2 int rollDice() { return (rand() % 6) + (rand() % 6) + 2; } int main(void) { int firstRoll = 1; int gameStatus = CONTINUE; int die1, die2, sumOfDice; int firstPoint = 0; char c; srand(time(0)); printf("Craps赌博游戏,按Enter键开始游戏****"); while(1) { getchar(); if(firstRoll) { sumOfDice = rollDice(); printf("\n玩家掷出点数和:%d\n", sumOfDice); switch(sumOfDice) { case 7: case 11: gameStatus = WON; break; case 2: case 3: case 12: gameStatus = LOST; break; default: firstRoll = 0; gameStatus = CONTINUE; firstPoint = sumOfDice; break; } } else { sumOfDice = rollDice(); printf("\n玩家掷出点数和:%d\n", sumOfDice); if(sumOfDice == firstPoint) gameStatus = WON; else if(sumOfDice == 7) gameStatus = LOST; } if(gameStatus == CONTINUE) puts("未分胜负,再掷一次****\n"); else { if(gameStatus == WON) puts("玩家胜"); else puts("玩家输"); printf("再玩一次?"); scanf("%c", &c); if(c == 'n') { puts("游戏结束"); break; } firstRoll = 1; } } return 0; }
26.Algorithm Gossip: 约瑟夫问题(Josephus Problem)
说明据说着名犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人 开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。
然而Josephus 和他的朋友并不想遵从,Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。
解法约瑟夫问题可用代数分析来求解,将这个问题扩大好了,假设现在您与m个朋友不幸参与了这个游戏,您要如何保护您与您的朋友?只要画两个圆圈就可以让自己与朋友免于死亡游戏,这两个圆圈内圈是排列顺序,而外圈是自杀顺序,如下图所示:
使用程序来求解的话,只要将阵列当作环状来处理就可以了,在阵列中由计数1开始,每找到三个无资料区就填入一个计数,直而计数达41为止,然后将阵列由索引1开始列出,就可以得知每个位置的自杀顺序,这就是约瑟夫排列,41个人而报数3的约琴夫排列如下所示:
14 36 1 38 15 2 24 30 3 16 34 4 25 17 5 40 31 6 18 26 7 37 19 8 35 27 9 20 32 10 41 21 11 28 39 12 22 33 13 29 23
由上可知,最后一个自杀的是在第31个位置,而倒数第二个自杀的要排在第16个位置,之前的人都死光了,所以他们也就不知道约琴夫与他的朋友并没有遵守游戏规则了。
#include <stdio.h> #include <stdlib.h> #define N 41 void josephus(int*, int); int main(void) { printf("\n\n您想要救多少人?"); int alive; scanf("%d", &alive); int man[N] = {0}; josephus(man, alive); printf("\n約琴夫排列:"); int i; for(i = 0; i < N; i++) printf("%d ", man[i]); printf("\nL表示這%d人要放的位置:\n", alive); for(i = 0; i < N; i++) { printf(man[i] <= (N - alive) ? "D" : "L"); printf((i+1) % 5 == 0 ? " " : ""); } printf("\n"); return 0; } void josephus(int* man, int per) { int count; int pos; for(count = 1, pos = -1; count <= N; count++) { int i = 0; while(i != per) { pos = (pos+1) % N; // 環狀處理 if(man[pos] == 0) i++; } man[pos] = count; } }