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