C 扑克牌配对说链表
以后每个数据结构作业都会那么小小的总结一下,数据结构的作业做了之后收获还是颇多的(废话当然比电子电路、大学物理收获多咯)
题目:
采用线性链表方式编程序模拟扑克牌配对游戏。通过键盘输入点数模拟抓牌,拿到新牌时,看手上是不是有同点数的牌,有的话,两者配对并抽掉手上的牌,没有的话,将牌插入手上的牌中,手上的牌保持按点数从小到大顺序排列。
想了半天终于决定行动起来,思路是:遍历,然后找找看有没有和读取到的相同的数字,有的话,就把next指针指向下下一个元素,把这个直接free掉,就算是丢掉了,比起数组来说方便的是不用考虑存入问题(但事实证明数组也能做的很方便)。
尝试了各种奇怪的写法,最终感受完毕,确认,路途还是那么坎坷,归根结底,数据结构仍需磨砺: 第一版是完成是bridge1.c,其他都是试水: 在此,我们如此建立数据结构:
1typedef struct Node
2{
3 int num;
4 struct Node *next;
5} Node, *Linklist;
6
然后通过一个search&create一站式服务:
1/*
2 function: search & change
3 return 1 找到相同,删除
4 return 0 未找到,插入操作
5*/
6Linklist search_num(Node *heading, int num)
7{
8 Node *p = heading, *next;
9 Node *newer;
10
11 while (p != NULL)
12 {
13 next = p->next;
14 if (p->next == NULL && num > p->num) // 如果找到末尾仍然没有找到
15 {
16 p->next = node_create(num, NULL);
17 break;
18 }
19 else if (p->next == NULL && num == heading->num)
20 {
21 heading->next = NULL;
22 heading->num = 0;
23 break;
24 }
25 if (num > p->num && num < next->num) // 如果找到了排序到的位置
26 {
27 p->next = node_create(num, next);
28 break;
29 }
30 else if (num < heading->num)
31 {
32 heading = node_create(num, p);
33 break;
34 }
35 else if (num == heading->num) // 如果找到的位置是heading
36 {
37 heading = node_create(next->num, next->next);
38 heading->next = next->next;
39 heading->num = next->num;
40 break;
41 }
42 else if (num == next->num) // 如果找到两个相同的
43 {
44 p->next = next->next;
45 break;
46 }
47 p = p->next;
48 }
49 return (Linklist)heading;
50}
51
但是这样,1是不好用,分离的太不彻底,2是对于heading而言,并没有很清晰的表现出来,而是不得不通过返回的形式来解决这个矛盾。
最糟糕的一点还是移植性,数据结构是一个抽象的概念,换句话说,链表理论上应该是拿到哪里复制粘帖,不需要多做什么修改就能直接使用的,而使用这个写法之后,只适用于本题了,从这层意义上来说,这是一个糟糕透顶的难懂而苦涩的代码。
于是接下来,我们就要进行修改,既然迈出了第一步,那么其实改起来也不算太难,一是课件还不错,给了你需要的框架——你需要做的是哪些函数,甚至连基本框架都有了,你需要的就是思考怎么将他们联系起来,怎么才能做到最好的优化成可分离,让它与你的main函数彻底分离。
于是写了一个link_list.c,这次的效果至少让我非常的满意(同时也收到了表扬呢):
1typedef int ElemType; // 修改类型
2
3/* 定义链表 */
4typedef struct Node {
5 ElemType data;
6 struct Node *next;
7} Node, *LinkList;
8
这段使用了ElemType之后,只要修改刚开始的typedef就能很方便的进行移植了,需要注意的是,之后也不能用到一切关于int的操作,否则就会让可移植性崩坏。
1/* 初始化链表 */
2LinkList InitList()
3{
4 struct Node *heading = NodeCreate(0, NULL);
5 return (LinkList)heading;
6}
7
在最初一个版本中,可以看出,我试图改变头指针并赋值,这让其代码变得相当复杂,而现在直接使用了一个空的头指针,从它的next开始存放,这样做的好处是,只要有一处头指针就好了,接下来会一直使用,而不用改变它的值,同时增加了代码的可读性(毕竟不用改变heading了)
然后我们把插入,删除,搜索,计数,清空,显示等功能统统分离出来,这样是为了满足日后多变的需求,正如之前所说的,数据结构是一个抽象的概念,我们要做的是考虑如何实现main函数,而不是为了该程序的便利去修改链表内的内容。
关于为什么不传入各种地址,归根结底还是因为到时候你搞得清吗?搞不清?还是写的简单点吧——类似于这样的理由。
那么开始了扑克牌配对的部分:
1int cards[13] = {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4};
2
这个数组用于后续的检测数量,加上total来检测卡组剩余,两个就能够达成检测牌堆和剩余的效果了。
1while (!scanf("%d", &num))
2{
3 printf("请输入正确的数字");
4 fflush(stdin);
5}
6
这里我觉得不再需要多做解释了,对于非正常输入的处理。
1position = GetElem(cardDeck, num);
2p = NodeShow(cardDeck, position);
3if (p && p->data == num)
4{
5 NodeDelete(cardDeck, position);
6}
7else
8{
9 NodeInsert(cardDeck, position, num);
10}
11
这里也是整个的难点,刚开始的时候想着如果没找到,是不是应该返回一下-1之类的表示没找到,然后再进行处理,但又觉得这样破坏了本来的完整性,后来发现,如果GetElem没有找到,那么位置会是最后的,在NodeShow时展示的会是一个NULL,就能得知查找到最后的情况并进行插入(而不会因为直接读取p->data
导致程序崩溃了)
1if (ElemCount(cardDeck))
2{
3 Traverse(cardDeck, Show);
4}
5else
6{
7 printf("NULL");
8}
9
最后通过Count来分开显示。
整个程序变得非常的简短,比起原来的显示效果不知道好了多少。
之后我们就开始考虑增加上趣味性,这之中导致了之后我蛋疼的原因:游戏规则是错误的,导致结果只有平局一个可能性,但从函数的角度而言,还是有很多值得注意的地方:
在card_game1.c中:
1srand((unsigned int)time(NULL));
2
随机数种子,为了生成伪随机数做准备。
1num = 1+(int)(13.0*rand()/(RAND_MAX+1.0));
2_sleep(rand() % 10 * 100);
3
这两种写法结果是一样的,都能够产生随机数。原则上我觉得如果是要整数的话,第二种更好。
上一句中还用到了_sleep()
函数,这是在windows.h
头文件中的,用于延时,里面的时间按照毫秒来计算。
否则按照C语言的秒显示根本把持不住……
OK,就说那么多,版本4就看心情吧。
评论 (0)