我们在了解计算机语言的时候,能够知道,当软件被使用时,会先调入内存,当调用完成,就释放空间。
那么。这些连续存储空间的分配和回收操作,存在一个问题:会产生“内存碎片”
为了避免上述情况,现代计算机通常采用“页式存储系统”来实现内存申请与回收操作
那么,为了将这个例子更加形象地展示出来,我们这里用一个算法来解释这种操作的原理——发牌算法
发牌算法:
顾名思义,要求是,发出去的牌不会再次被发出
现在看到这个要求,我们的第一想法可能是用一个数组(先命名为arr数组)来存储取出的值,每当规定范围随机数产生数的时候,我们遍历arr数组,来看看这个值存在不存在这个数组中,若不存在,我们就将这个数输出,并将这个数存在arr数组中,以防下次输出此数。
若是今天本篇博文按照上述的思想去编程的话,可能会让知情的编程老手笑掉大牙吧,因为我们数据结构与算法的主要思想是编写的程序的时间复杂度尽可能地低,而若是按照上面的思想,无疑每产生一个数就要去遍历数组,有新的数产生的话,我们还要为数组赋值,这样看来,时间复杂度很高。
那么,现在,本人来介绍本篇博文的主题算法——发牌算法。用这个算法来实现我们上述的要求,时间复杂度非常低,只需遍历一个长度尽可能低的数组(即时间复杂度为O(n))
那么,发牌算法大致思想:
我们按照数组的下标取数,将已经取出的“牌”和未取出数组内的最后一张“牌”交换下标,并使得下一次“取牌”时的未取出的范围比这次的范围少1
现在我们来编写一些程序,来使得我们上述的逻辑更加清晰化、形象化:
我们现在要取牌,那么就先编写两个全局数组——花色数组、牌值数组:
const char *pokerType[] = {
"黑桃", "红桃", "方块", "梅花"}; //[]比*优先级高,所以这个定义的意义是字符串数组
//10是两个字符,所以类型是char *
const char *pokerValue[] =
{
"A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"};
那么,现在我们来编写一个构建牌池函数:
#define POKERS_COUNT 52
void resetPoker(char *poker) {
int i;
for(i = 0; i < POKERS_COUNT; i++) {
poker[i] = i;
}
}
上面这个函数可能让好多人感觉一头雾水,为什么单纯的将相应下标存入数组相应单元的操作被我们称为取数函数呢?
因为一副扑克牌一共有52张,我们花色按照“黑桃、红桃、方块、梅花”,大小按照“A->K”的顺序,将所有牌从下标为0到51全部存入一个数组中,相当于将所有牌全部放在了poker数组中。
那么,有了牌池,我们现在就来为玩家发牌,即“取牌操作”了,现在我们来编写取牌函数:
#define POKERS_COUNT 52
#define POKER_TYPE_COUNT 13
void dealPoker(char (*pokerSet)[POKER_TYPE_COUNT]) {
char pokerSet[POKERS_COUNT];
int range;
int index = 0;
resetPoker(pokerSet);
srand(time(0));
for(range = POKERS_COUNT; range >