成比引号中的字符数多一个字符的null终上字符串。4个字符串长度分别为7、9、6、7。尽管这些字符串好像是放在suil数组中,其实数组中只存放指针(如图5.22)。每个指针指向对应字符串中的第一个字符。这样,尽管:suit数组是定长的,但可以访问任意长度的字符串,这是C++强大的数据结构功能所带来的灵活性。
图5. 22 suit数组的图形表示
suit字符串可以放在双下标数组中,每一行表示一个suit,每一列表示suit名的第一个字符、这种数据结构每一行应有固定列数,能够放下最长的字符串。因此,存放大量字符串而大部分字符串长度均比最长字符串短许多时,可能浪费很多内存空间。我们将在下一节用字符串数组帮助整理一副牌。
5.10 实例研究:洗牌与发牌
本节用随机数产生器开发一个洗牌与发牌程序。这个程序可以用于实现玩某种牌的游戏程序。
为了解决一些微妙的性能问题,我们故意用次优洗牌与发牌算法。练习中要开发更有效的算法。
利用自上而下逐步完善的方法,我们开发一个程序,洗52张牌并发52张牌。自上而下逐步完善的方法在解决大而复杂的问题时特别有用。
我们用4 x 13的双下标数组deck表示要玩的牌(如图5.23)。行表示花色,0表示红心,1表示方块,2表示梅花,3表示黑桃。列表示牌的面值,0到9对应A到10,10到12对应J、Q、K。我们要装入字符串数组suit,用字符串表示4个花色,用字符串数组face的字符串表示13张牌的面值。
这堆牌可以进行如下的洗牌:首先将数组deck清空,然后随机选择row(0--3)和column(0—12)。将数字插入数组元素deck[row][column](表示这个牌是洗出的牌中要发的第一张牌)。继续这个过程,在deck数组中随机插入数字2、3、?52,
表示洗出的牌中要发的第二、三、?、五十二张牌。在deck数组填上牌号时,一张牌可能选择两次,即选择的时候deck[row][column]为非0值。 忽略这个选择,随机重复选择其他row和colunm,直到找出未选择的牌。最后,在52个deck元素中插入1到52的值。这时,就完全洗好了牌。
图5.23 双下标数组deck表示要玩的牌
这个洗牌算法在随机重复选择已经洗过的牌时可能需要无限长的时间。这种现象称为无穷延迟(indefinite postponement)。练习中将介绍更好的洗牌算法,消陈无穷延迟。
性能提示5.3
有时自然方式的算法可能包含无穷延迟等微妙的性能问题,应寻找能避免无穷延迟的算法。
要发第一张牌,我们要寻找匹配1的deck[row][column]元素,这是用嵌套for结构进行的,n,w取。到3t column取。到12。这个数组元素对应哪种牌呢?suit数组预先装入了四种花色,因此要取花色,只要打印字符串suit[row];同样,要取牌值,只要打印字符串face[column]还要打印字符串”of\的顺序打印,即可得到每张牌如”King of Clubs\of Diamonds',等等。 下面用自上而下逐步完善的方法进行。顶层为: Shuffle and deal 52 cards
第一步完善结果为:
Initialize the suit array Initialize the face array Initialize the deck array
Shuffle the deck Deal 52 cards
”Shumelhedeck”可以展开成: For each of the 52 cards
Place card number in randomly selected unoccupied slot of deck \可以展开成: For each of the 52 cards
Find card number in deck array and print face and suit of card
合在一起得到第二步完善结果为: Initialize the suit array Initialize the face array Initialize the deck array For each of the 52 cards
Place card number in randomly selected unoccupied slot of deck For each of the 52 cards
Find card number in deck array and print face and suit of card \可以展开成:
Choose slot of deck randomly
While chosen slot of deck has been previously chosen Choose slot of deck randomly
Place card number in chosen slot of deck
\可以展开成:
For each slot of the deck array If slot contains card number
Print the face and suit of the card
合在一起得到第三步完善结果为: Initialize the suit array Initialize the face array Initialize the deck array For each of the 52 cards
Choose slot of deck randomly
While slot of deck has been previously chosen Choose slot of deck randomly
Place card number in chosen slot of deck For each of the 52 cards
For each slot of deck array
If slot contains desired card number Print the face and suit of the card
这样就完成了完善过程。注意,如果将洗牌与发牌算法组合成每张牌在放到牌堆上时进行发牌,则这个程序能更加有效。我们选择分别编程这些操作,因为通常是先洗后发,而不是边洗边发。
图5.24显示了洗牌与发牌程序,图5.25显示了示例执行结果。注意函数deal中使用的输出格式:
cout << setw( 5 ) << setiosflags( ios::right ) << wFace[ column ] << \
<< setw( 8 ) << setiosflags( ios::left ) << wSuit[ row ]
<< (card % 2 ==0? '\\n': '\\t');
上述输出语句使牌的面值在5个字符的域中右对齐输出,而花色在8个字符的域中左对齐输出。输出打印成两列格式。如果输出的牌在第一列,则在后面输出一个制表符,移到第二列,否则输出换行符。
1 // Fig. 5.24: fig05_24.cpp
2 // Card shuffling and dealing program 3 #include
8 void shuffle( iht [][ 13 ] );
9 void deal( const int [][ 13 ], const char *[], const char *[] ); 10
11 int main() 12 {
13 const char * suit[ 4 ] =
14 { \15 const char * face[ 13 ] =
16 { \17 \
18 \19 int deck[ 4 ][ 13 ] = { 0 } ; 20
21 srand( time( 0 ) ); 22
23 shuffle( deck );
24 deal( deck, face, suit ): 25
26 return 0; 27 } 28
29 void shuffle( int wDeck[ ][ 13 ] )