问题:小哈是小哼的新同桌,小哼向小哈询问 QQ 号,小哈给了小哼一串加密过的数字,同时小哈也告诉了小哼解密规则。规则是这样的:首先将第 1 个数删除,紧接着将第 2 个数放到这串数的末尾,再将第 3 个数删除并将第 4 个数放到这串数的末尾,再将第 5 个数删除…直到剩下最后一个数,将最后一个数也删除。按照刚才删除的顺序,把这些删除的数连在一起就是小哈的 QQ 啦。小哈给小哼加密过的一串数是“6 3 1 7 5 8 9 2 4”。
思路:
#include
int main()
{
int q[102]={0,6,3,1,7,5,8,9,2,4},head,tail;
int i;
//初始化队列
head=1;
tail=10; //队列中已经有9个元素了,tail指向队尾的后一个位置
while(head
总结:
struct queue
{
int data[100];//队列的主体,用来存储内容
int head;//队首
int tail;//队尾
};
上面的代码进行修改如下:
#include
struct queue
{
int data[100];//队列的主体,用来存储内容
int head;//队首
int tail;//队尾
};
int main()
{
struct queue q;
int i;
//初始化队列
q.head=1;
q.tail=1;
for(i=1;i<=9;i++)
{
//依次向队列插入9个数
scanf("%d",&q.data[q.tail]);
q.tail++;
}
while(q.head
还有一种是后进先出的数据结构,它叫做栈。栈限定为只能在一端进行插入和删除操作。
问题:我们来看一个例子。“xyzyx”是一个回文字符串,所谓回文字符串就是指正读反读均相同的字符序列,如“席主席”、“记书记”、“aha”和“ahaha”均是回文,但“ahah”不是回文。通过栈这个数据结构我们将很容易判断一个字符串是否为回文。
思路:
代码:
#include
#include
int main()
{
char a[101],s[101];
int i,len,mid,next,top;
gets(a); //读入一行字符串
len=strlen(a); //求字符串的长度
mid=len/2-1; //求字符串的中点
top=0;//栈的初始化
//将mid前的字符依次入栈
for(i=0;i<=mid;i++)
s[++top]=a[i];
//判断字符串的长度是奇数还是偶数,并找出需要进行字符匹配的起始下标
if(len%2==0)
next=mid+1;
else
next=mid+2;
//开始匹配
for(i=next;i<=len-1;i++)
{
if(a[i]!=s[top])
break;
top--;
}
//如果top的值为0,则说明栈内所有的字符都被一一匹配了
if(top==0)
printf("YES");
else
printf("NO");
getchar();getchar();
return 0;
}
栈还可以用来进行验证括号的匹配。比如输入一行只包含“()[]{}”的字符串,请判断
形如“([{}()])”或者“{()[]{}}”的是否可以正确匹配。
问题:游戏的规则是这样的:将一副扑克牌平均分成两份,每人拿一份。小哼先拿出手中的第一张扑克牌放在桌上,然后小哈也拿出手中的第一张扑克牌,并放在小哼刚打出的扑克牌的上面,就像这样两人交替出牌。出牌时,如果某人打出的牌与桌上某张牌的牌面相同,即可将两张相同的牌及其中间所夹的牌全部取走,并依次放到自己手中牌的末尾。当任意一人手中的牌全部出完时,游戏结束,对手获胜。
假如游戏开始时,小哼手中有 6 张牌,顺序为 2 4 1 2 5 6,小哈手中也有 6 张牌,顺序为 3 1 3 5 6 4,最终谁会获胜呢?现在你可以拿出纸牌来试一试。接下来请你写一个程序来自动判断谁将获胜。这里我们做一个约定,小哼和小哈手中牌的牌面只有 1~9。
思路:
struct queue
{
int data[1000];
int head;
int tail;
}
再创建一个结构体用来实现栈,如下。
struct stack
{
int data[10];
int top;
};
其中 top 用来存储栈顶,数组 data 用来存储栈中的元素,大小设置为 10。因为只有 9种不同的牌面,所以桌上最多可能有 9 张牌,因此数组大小设置为 10 就够了。提示一下:为什么不设置为 9 呢?因为 C 语言数组下标是从 0 开始的。
接下来我们需要定义两个队列变量 q1 和 q2。 q1 用来模拟小哼手中的牌, q2 用来模拟小
哈手中的牌。定义一个栈变量 s 用来模拟桌上的牌。
struct queue
struct stack
q1,q2;
s;
接下来来初始化一下队列和栈。
//初始化队列q1和q2为空,此时两人手中都还没有牌
q1.head=1; q1.tail=1;
q2.head=1; q2.tail=1;
//初始化栈s为空,最开始的时候桌上也没有牌
s.top=0;
接下来需要读入小哼和小哈最初时手中的牌,分两次读入,每次读入 6 个数,分别插入q1 和 q2 中。
//先读入6张牌,放到小哼手上
for(i=1;i<=6;i++)
{
scanf("%d",&q1.data[q1.tail]); //读入一个数到队尾
q1.tail++;//队尾往后挪一位
}
//再读入6张牌,放到小哈手上
for(i=1;i<=6;i++)
{
scanf("%d",&q2.data[q2.tail]); //读入一个数到队尾
q2.tail++;//队尾往后挪一位
}
现在准备工作已经基本上做好了,游戏正式开始,小哼先出牌。t=q1.data[q1.head]; //小哼先亮出一张牌小哼打出第一张牌,也就是 q1 的队首,我们将这张牌存放在临时变量 t 中。接下来我们要判断小哼当前打出的牌是否能赢得桌上的牌。也就是判断桌上的牌与 t 有没有相同的,如何实现呢?我们需要枚举桌上的每一张牌与 t 进行比对,具体如下:
flag=0;
for(i=1;i<=top;i++)
{
if(t==s[i]) { flag=1; break; }
}
如果 flag 的值为 0 就表明小哼没能赢得桌上的牌,将打出的牌留在桌上。
if(flag==0)
{
//小哼此轮没有赢牌
q1.head++; //小哼已经打出一张牌,所以要把打出的牌出队
s.top++;
s.data[s.top]=t; //再把打出的牌放到桌上,即入栈
}
如果 flag 的值为 1 就表明小哼可以赢得桌上的牌,
需要将赢得的牌依次放入小哼的手中。
f(flag==1)
{
//小哼此轮可以赢牌
q1.head++;//小哼已经打出一张牌,所以要把打出的牌出队
q1.data[q1.tail]=t; //因为此轮可以赢牌,所以紧接着把刚才打出的牌又放到手中牌的末尾
q1.tail++;
while(s.data[s.top]!=t) //把桌上可以赢得的牌(从当前桌面最顶部一张牌开始取,直至取到与打出的牌相同为止)依次放到手中牌的末尾
{
q1.data[q1.tail]=s.data[s.top]; //依次放入队尾
q1.tail++;
s.top--; //栈中少了一张牌,所以栈顶要减1
}
}
小哼出牌的所有阶段就模拟完了,小哈出牌和小哼出牌是一样的。接下来我们要判断游戏如何结束。即只要两人中有一个人的牌用完了游戏就结束了。因此需要在模拟两人出牌代码的外面加一个 while 循环来判断,如下。
while(q1.head 反之,小哈获胜,代码的实现也是差不多的,就不再赘述了。到此,所有的代码实现就都讲完了,在上面我们讲解的所有实现中,每个人打出一张牌后,判断能否赢牌这一点可以优化。之前我们是通过枚举桌上的每一张牌来实现的,即用了一个 for 循环来依次判断桌上的每一张牌是否与打出的牌相等。其实有更好的办法来解决这个问题,就是用一个数组来记录桌上有哪些牌。因为牌面只有 1~9,因此只需开一个大小为 10 的数组来记录当前桌上已经有哪些牌面就可以了。 这里我再一次使用了 book 这个单词,因为这个单词有记录、登记的意思,而且单词拼写简洁。另外很多国外的算法书籍在处理需要标记问题的时候也都使用 book 这个单词,因此我这里就沿用了。当然你也可以使用 mark 等你自己觉得好理解的单词啦。下面需要将数组book[1]~book[9]初始化为 0,因为刚开始桌面上一张牌也没有。 接下来,如果桌面上增加了一张牌面为 2 的牌,那就需要将 book[2]设置为 1,表示牌面为 2 的牌桌上已经有了。当然如果这张牌面为 2 的牌被拿走后,需要及时将 book[2]重新设置为 0,表示桌面上已经没有牌面为 2 的牌了。这样一来,寻找桌上是否有与打出的牌牌面相同的牌,就不需要再循环枚举桌面上的每一张牌了,而只需用一个 if 判断即可。这一点是不是有点像第 1 章第 1 节的桶排序的方法呢?具体如下。 代码: 在存储一大波数的时候,我们通常使用的是数组,但有时候数组显得不够灵活,比如下面这个例子。有一串已经从小到大排好序的数 2 3 5 8 9 10 18 26 32。现需要往这串数中插入 6 使其得到的新序列仍符合从小到大排列。如我们使用数组来实现这一操作,则需要将 8 和 8 后面的数都依次往后挪一位。 现在我们来创建第一个结点,并用临时指针 p 指向这个结点。 接下来分别设置新创建的这个结点的左半部分和右半部分。 完整代码: 需要说明的一点是:上面这段代码没有释放动态申请的空间,虽然没有错误,但是这样 使用模拟链表也可以实现双向链表和循环链表。if(q2.head==q2.tail)
{
printf("小哼win\n");
printf("小哼当前手中的牌是");
for(i=q1.head;i<=q1.tail-1;i++)
printf(" %d",q1.data[i]);
if(s.top>0) //如果桌上有牌则依次输出桌上的牌
{
printf("\n桌上的牌是");
for(i=1;i<=s.top;i++)
printf(" %d",s.data[i]);
}
else
printf("\n桌上已经没有牌了");
}
}
int book[10];
for(i=1;i<=9;i++)
book[i]=0;
t=q1.data[q1.head]; //小哼先亮出一张牌
if(book[t]==0) // 表明桌上没有牌面为t的牌
{
//小哼此轮没有赢牌
q1.head++; //小哼已经打出一张牌,所以要把打出的牌出队
s.top++;
s.data[s.top]=t; //再把打出的牌放到桌上,即入栈
book[t]=1; //标记桌上现在已经有牌面为t的牌
}
完整代码如下:#include
链表
这样操作显然很耽误时间,如果使用链表则会快很多。那什么是链表呢?请看下图。
首先我们来看一下,链表中的每一个结点应该如何存储。
每一个结点都由两个部分组成。左边的部分用来存放具体的数值,那么用一个整型变量就可以;右边的部分需要存储下一个结点的地址,可以用指针来实现(也称为后继指针)。这里我们定义一个结构体类型来存储这个结点,如下。struct node
{
int data;
struct node *next;
};
上面代码中,我们定义了一个叫做 node 的结构体类型,这个结构体类型有两个成员。第一个成员是整型 data,用来存储具体的数值;第二个成员是一个指针,用来存储下一个结点的地址。因为下一个结点的类型也是 struct node,所以这个指针的类型也必须是 struct node* 类型的指针。
如何建立链表呢?首先我们需要一个头指针 head 指向链表的最开始。当链表还没有建立的时候头指针 head 为空(也可以理解为指向空结点)。struct node *head;
head = NULL;//头指针初始为空
struct node *p;
//动态申请一个空间,用来存放一个结点,并用临时指针p指向这个结点
p=(struct node *)malloc(sizeof(struct node));
scanf("%d",&a);
p->data=a;//将数据存储到当前结点的data域中
p->next=NULL;//设置当前结点的后继指针指向空,也就是当前结点的下一个结点为空
最后要将指针 q 也指向当前结点,因为待会儿临时指针 p 将会指向新创建的结点。
q=p;//指针q也指向当前结点#include
会很不安全,有兴趣的朋友可以去了解一下 free 命令。模拟链表
#include