C 扑克牌配对说链表

以后每个数据结构作业都会那么小小的总结一下,数据结构的作业做了之后收获还是颇多的(废话当然比电子电路、大学物理收获多咯)

题目:

采用线性链表方式编程序模拟扑克牌配对游戏。通过键盘输入点数模拟抓牌,拿到新牌时,看手上是不是有同点数的牌,有的话,两者配对并抽掉手上的牌,没有的话,将牌插入手上的牌中,手上的牌保持按点数从小到大顺序排列。

扑克牌配对github链接

想了半天终于决定行动起来,思路是:遍历,然后找找看有没有和读取到的相同的数字,有的话,就把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就看心情吧。

植入部分

如果您觉得文章不错,可以通过赞助支持我。

如果您不希望打赏,也可以通过关闭广告屏蔽插件的形式帮助网站运作。

标签: 成品, 源码, 知识, 语法, 题目

添加新评论