问题引入
很久很久以前,我们的阿三同学喜欢上了如花,然后阿三就问如花要她的扣扣。然后如花这个就不太愿意吼!就给了阿三一串数字让他解密!
阿三:如花,我可以拥有你的扣扣号嘛?
如花:那倒也不是不可以,听说你会开挂,把这串数字用我给你的解码方式解开,你就能得到我的扣扣号了!
于是阿三就开始了开挂之路,搞错了,是解码之路,先让我们看看这串数字 " 6 3 1 7 5 8 9 2 4 " 。
解码规则是:首先将第 1 个数删除,紧接着将第 2 个数放到 这串数的末尾,再将第 3 个数删除并将第 4 个数放到这串数的末尾,再将第 5 个数删除…… 直到剩下最后一个数,将最后一个数也删除。
问题剖析
其实这个问题不难,自己动手算一下,很容易计算出来这个结果:" 6 1 5 9 4 7 2 8 3 "
解密的过程就像是“排队”,每次从最前面拿两个,第 1 个扔掉,第 2 个放到尾部。
具体过程是这样的:
刚开始这串数是" 6 3 1 7 5 8 9 2 4 " ,首先删除 6 并将 3 放到这串数的末尾,这串数更新为" 1 7 5 8 9 2 4 3 "。
接下来删除 1 并将 7 放到末尾,即更新为 " 5 8 9 2 4 3 7 " 。
再删除 5 并将 8 放到末尾即" 9 2 4 3 7 8 " ,删除 9 并将 2 放到末尾即" 4 3 7 8 2 " ,删除 4 并将 3 放到末尾即" 7 8 2 3 " ,删除 7 并将 8 放到末尾即" 2 3 8 " ,删除 2 并将 3 放到末尾即" 8 3 " ,删除 8 并将 3 放到末尾即" 3 " ,最后删除 3。
因此被删除的顺序是" 6 1 5 9 4 7 2 8 3 " ,
这就是 如花 的扣扣号码了,单身的xdm,可以加她聊一下喔!
代码思路分析
主要思想:队列(先进先出)
首先需要一个数组来存储这一串数即 int q[101],并初始化这个数组即 int q[101]= {0,6,3,1,7,5,8,9,2,4};(此处初始化是我多写了一个 0,用来填充 q[0],当然也可以从q[0]开始,依据个人习惯)
解码的第一步就是删除元素,当然,最简单的办法就是覆盖删除,将所有后面的数都往前面挪动一位,将前面的数覆盖,但是这样做很浪费时间,也比较麻烦。
在这里,我们引入两个整型变量 head 与 tail ,head 用来记录队列的队首(即第一位),tail 用来记录队列的队尾(即最后一位)的下一个位置。
你可能会问:为什么 tail 不直接记录队尾,却要记录队尾的下一个位置呢?这是因为当队列中只剩下一个元素时,队首和队尾重合(队尾的值会被覆盖)会带来一些麻烦。我们这里规定队首和队尾重合时,队列为空。
现在有9个数,将这9个数放入队列中,初始化队列 head = 1;tail = 10;head与tail之间的数称为队列中“有效”的数,如果删除第一个元素,直接将队首下标加一,即head++即可,这样仍然可以保持 head 和 tail 之间的数为目前队列中“有效”的数,且确保队列的完整性。
新增加一个数也很简单,把需要增加的数放到队尾,即 q[tail] 之后再 tail++就 OK 啦。
在队首删除一个数,head++
在队尾增加一个数(假设这个数是 x)的操作是 q[tail]=x; tail++;
流程图
代码演示
#include "stdio.h"
int main()
{
int queue[101] = {0, 6, 3, 1, 7, 5, 8, 9, 2, 4};
int head, tail, i;
//初始化队列
head = 1;
tail = 10; //队列里有九个元素,tail队尾队尾后一个位置
while (tail > head) { //队列不为空,循环队列进行操作
//打印队首,然后将队首下标后移,队首出队
printf("%d ", queue[head]);
head++;
//将新的队首值放至队尾,队尾下标加一(进队)
queue[tail] = queue[head];
tail++;
//此时已完成删一(已打印),移一的操作,再次将队首出队,开始下一轮循环(出队进队操作)
head++;
}
return 0;
}
小结
队列是一种特殊的线性结构,它只允许在队列的首部(head)进行删除操作,这称为“出队”,而在队列 的尾部(tail)进行插入操作,这称为“入队”。当队列中没有元素时(即 head==tail),称为 空队列。
比如我们之前提到过的买票, 每个排队买票的窗口就是一个队列。在这个队列当中,新来的人总是站在队列的最后面,来得越早的人越靠前,也就越早能买到票,就是先来的人先服务,我们称为“先进先出”(First In First Out,FIFO)原则。
结构体实现队列
队列将是我们今后学习广度优先搜索以及队列优化的 Bellman-Ford 最短路算法的核心 数据结构。所以现在将队列的三个基本元素(一个数组,两个变量)封装为一个结构体类型, 如下。
struct queue
{
int data[100]; //队列的主体,用来存储内容
int head; //队首
int tail; //队尾
};
上面定义了一个结构体类型,我们通常将其放在 main 函数的外面,请注意结构体的定 义末尾有个;号。struct 是结构体的关键字,queue 是我们为这个结构体起的名字。这个结构体有三个成员分别是:整型数组 data、整型 head 和整型 tail。这样我们就可以把这三个部分放在一起作为一个整体来对待。你可以这么理解:我们定义了一个新的数据类型,这个新类 型非常强大,用这个新类型定义出的每一个变量可以同时存储一个整型数组和两个整数。
有了新的结构体类型,如何定义结构体变量呢?很简单,这与我们之前定义变量的方式 是一样的,具体做法如下。
struct queue q;
请注意 struct queue 需要整体使用,不能直接写 queue q;。这样我们就定义了一个结构体 变量 q。这个结构体变量就可以满足队列的所有操作了。那又该如何访问结构体变量的内部成员呢?可以使用.号,它叫做成员运算符或者点号运算符,如下:
q.head=1;
q.tail=1;
scanf("%d", &q.data[q.tail]);
下面这段代码就是使用结构体来实现的队列操作:
#include "stdio.h"
struct queue
{
int head, tail; //定义队首队尾
int data[101]; //队列主体,用来存储内容
};
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<q.tail) //当队列不为空的时候执行循环
{
//打印队首并将队首出队
printf("%d ",q.data[q.head]);
q.head++;
//先将新队首的数添加到队尾
q.data[q.tail]=q.data[q.head];
q.tail++;
//再将队首出队
q.head++;
}
return 0;
}
上面的这种写法看起来虽然冗余了一些,但是可以加强你对队列这个算法的理解。C++ 的 STL 库已经有队列的实现,有兴趣的可以参看相关材料。
参考书籍
《啊哈!算法》