【啊哈!算法】系列文章目录
需求介绍
新学期开始了,小哈是小哼的新同桌(小哈是个小美女哦~),小哼向小哈询问 QQ 号,小哈当然不会直接告诉小哼啦,原因嘛你懂的。所以小哈给了小哼一串加密过的数字,同时小哈也告诉了小哼解密规则。规则是这样的:首先将第 1 个数删除,紧接着将第 2 个数放到这串数的末尾,再将第 3 个数删除并将第 4 个数放到这串数的末尾,再将第 5 个数删除……直到剩下最后一个数,将最后一个数也删除。按照刚才删除的顺序,把这些删除的数连在一起就是小哈的 QQ 啦。现在你来帮帮小哼吧。小哈给小哼加密过的一串数是“6 3 1 7 5 8 9 2 4
”。
思路分析
其实解密的过程就像是将这些数 “排队”。每次从最前面拿两个,第 1 个扔掉,第 2 个放到尾部。
解密的第一步是将第一个数删除,你可以想一下如何在数组中删除一个数呢。最简单的方法是将所有后面的数都往前面挪动一位,将前面的数覆盖。就好比我们在排队买票,最前面的人买好离开了,后面所有的人就需要全部向前面走一步,补上之前的空位,但是这样的做法很耗费时间。
在这里,我们可以采用队列的思想,引入两个整型变量 head
和 tail
。head
用来记录队列的队首(即第一位),tail
用来记录队列的队尾(即最后一位)的下一个位置。
你可能会问:为什么
tail
不直接记录队尾,却要记录队尾的下一个位置呢?这是因为当队列中只剩下一个元素时,队首和队尾重合会带来一些麻烦。我们这里规定队首和队尾重合时,队列为空。
现在有 9 个数,9 个数全部放入队列之后 head=1
; tail=10
; 此时 head
和 tail
之间的数就是目前队列中“有效”的数。如果要删除一个数的话,就将 head++
就 OK 了,这样仍然可以保持 head
和 tail
之间的数为目前队列中“有效”的数。这样做虽然浪费了一个空间,却节省了大量的时间,这是非常划算的。新增加一个数也很简单,把需要增加的数放到队尾即 q[tail]
之后再 tail++
就 OK 啦。
我们来小结一下,在队首删除一个数的操作是 head++;
。
在队尾增加一个数(假设这个数是 x
)的操作是 q[tail]=x; tail++;
整个解密过程,请看下面这个霸气外漏的图。
最后的输出就是 6 1 5 9 4 7 2 8 3
。
C语言代码1——正常思路
# include <stdio.h>
int main() {
int a[50] = {6,3,1,7,5,8,9,2,4}, b[9];
int i=0;
int head, tail;
//初始化队列
head = 0;
tail = 9; // 队列中已经有9个元素了,tail指向队尾的后一个位置 (最后一位的下一个位置)
while(head!=tail) {
//将队首的数提取出,放到数组b中
b[i++] = a[head];
//将队首移出队
head++;
//先将新队首的数添加到队尾
a[tail] = a[head];
tail++;
//再将队首出队
head++;
}
// 按照删除的顺序,把删除的数连在一起输出
for(i=0;i<9;i++) {
printf("%d", b[i]);
}
// // 按照与删除相反的顺序,把删除的数连在一起输出
// for(i=8;i>=0;i++) {
// printf("%d", b[i]);
// }
getchar();getchar();
return 0;
}
队列封装
队列将是我们今后学习广度优先搜索以及队列优化的 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]);
C语言代码2——使用结构体
#include <stdio.h>
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<q.tail) //当队列不为空的时候执行循环
{
//打印队首并将队首出队
printf("%d ",q.data[q.head]);
q.head++;
//先将新队首的数添加到队尾
q.data[q.tail]=q.data[q.head];
q.tail++;
//再将队首出队
q.head++;
}
getchar();getchar();
return 0;
}
上面的这种写法看起来虽然冗余了一些,但是可以加强你对队列这个算法的理解。C++的 STL 库已经有队列的实现,有兴趣的同学可以参看相关材料。
C++ STL 库中的队列Queues
The C++ Queue is a container adapter that gives the programmer a FIFO (first-in, first-out) data structure.
C++队列是一个容器适配器,它给程序员提供了FIFO(先进先出)数据结构。
序号 | 函数 | 说明 |
---|---|---|
1 | back() | 返回一个引用,指向最后一个元素 |
2 | empty() | 如果队列空则返回真 |
3 | front() | 返回第一个元素 |
4 | pop() | 删除第一个元素 |
5 | push() | 在末尾加入一个元素 |
6 | size() | 返回队列中元素的个数 |
函数可分为以下几类:
back()
front()
pop()
push()
size()
empty()
1. 整体应用
#include <iostream>
#include <queue>
using namespace std;
int main()
{
queue<int> q; //定义队列
q.push(3); //向队尾添加数
q.push(4); //向队尾添加数
q.push(5); //向队尾添加数
q.push(6); //向队尾添加数
q.pop(); //从队头取出数
cout << "q.front() = " << q.front() << endl; //队头的数:4
cout << "q.back() = " << q.back() << endl; //队尾的数:6
cout << "q.size() = " << q.size() << endl; //队中数的个数:3
cout << "q.empty() = " << q.empty() << endl; //是否为空队列:0
return 0;
}
2. 用C++ 解决需求
#include <iostream>
#include <stdio.h>
#include <queue>
using namespace std;
int main()
{
queue<int> q;
int i, temp;
for(i=0; i<9; i++)
{
//依次向队列中插入9个数
scanf("%d", &temp);
q.push(temp);
}
//当队列不为空时执行循环
while(!q.empty())
{
//打印队首并将队首出队
cout << q.front() << " ";
q.pop();
//先将新队首的数添加到队尾
q.push(q.front());
//再将队首出队
q.pop();
}
return 0;
}
/* 输入:
* 6 3 1 7 5 8 9 2 4
* 输出:
* 6 1 5 9 4 7 2 8 3
*/