文章目录
Queue 队列
队列和栈(Stack)一样,都是一种操作受限的表。队列允许从一端入队,另一端出队,即我们日常所熟悉的排队的规则——先进先出(First In First Out, FIFO),其中:
- 队头(Front):允许出队
- 队尾(Rear):允许出队
常规操作:
isEmpty
:判断队列是否为空isFull
:判断是否满队pop_back
:出队push_front
:入队free
:清队
ArrayQueue 队列的连续存储
连续存储使用数组存储队列,用两个整型变量作为记录队尾和队头下标的指针,用于进队和出队的操作,以及判断当前是否为空,或者是满队:
#define MAX_SIZE
// define default value
class ArrayQueue{
private:
ElementType data[MAX_SIZE + 1];
int front = 0, rear = 0;
public:
// ...
};
其中,数组大小为最大容量加一,否则,当满队或者空队时,此时front
和rear
都是相等的,无法区分这两种状态,因此加一这样:
- 当
front == rear
时,队列为空 - 当
front == (rear + 1) % MAX_SIZE
时,此时队列已满
isEmpty
bool isEmpty(){
return this->front == this->rear;
}
isFull
bool isFull(){
return this->front == (this->rear + 1)%MAX_SIZE;
}
push_back
void push_back(ElementType e){
if(isFull()){
std::cout << "Full queue, not available!" << std::endl;
return;
}
this->data[this->rear++] = e;
this->rear = this->rear%MAX_SIZE;
}
pop_front
ElementType pop_front(){
if(isEmpty()){
std::cout << "Empty queue, not available!" << std::endl;
return DEFAULT_VALUE;
}
ELementType eleOut = this->data[this->front];
this->front = (this->front + 1)%MAX_SIZE;
return eleOut;
}
free
void free(){
this->front = this->rear = 0;
return;
}
LinkedQueue 队列的链式存储
不同于顺序存储,链式存储采用指针指向下一个元素,用两个结点指针变量分别指向队头和队尾不需要担心队满的情况发生,因此我们只需要简单判断当其中一个指针为空的时候(此时两个都为空,判断一个即可,但是涉及到头尾指向同一个结点或者都为空的情况,需要对两个结点都进行相应修改),此时队列为空
class LinkedQueue{
public:
typedef struct Node{
ElementType e;
Node* next;
Node(ElementType e_):e(e_){}
}Node;
private:
Node* front;
Node* rear;
int size = 0;
public:
// ...
};
isEmpty
bool isEmpty(){
return this->front == NULL;
}
push_back
void push_back(ElementType e){
Node* newNode = new Node(e);
if(isEmpty()){
// Note that both rear and front are now NULL,
// we need to do the same thing to front as what
// we do to rear 'cause they have the same adress
// when our queue has only one element
this->front = newNode;
}
else{
// on this situation, rear is not NULL anymore
this->rear->next = newNode;
}
this->rear = newNode;
this->size++;
return;
}
pop_front
ElementType pop_front(){
if(isEmpty()){
std::cout << "Empty queue, not available!" << std::endl;
return DEFAULT_VALUE;
}
Node* p = this->front;
this->front = this->front->next;
ElemenType reVal = p->e;
delete p;
this->size--;
if(this->size == 0){
// Note that we have just delet a node not only the front
// but also the rear, pay attention to have the rear to be NULL
this->rear = NULL;
}
return reVal;
}
clear
void clear(){
while(this->front != NULL){
Node* p = this->front;
this->front = this->front->next;
delete p;
}
this->rear = NULL;
return;
}
Cycle Queue 循环队列
我们非常熟悉这样的队列——即“轮流”的工作方式,轮流执行自己的功能,也就是说,当队头的元素完成了它的功能时,它将会重新回到队尾,这就是通常我们利用循环队列可以完成的工作。实际上和队列的实现方法相同,只是我们在这个实现上定义的操作不同:可以改变队头实现队列循环的功能。
Deque 双端队列
双端队列顾名思义,可以在队列的两端都进行删除和插入操作,队列两端称为前端和后端。(通常考察双端队列的出入队情况,给定入队顺序,给出可能的出队顺序)
此外:
- 如果一个双端队列,只允许在一端进行两项操作,而另一端只允许进行删除操作的话,这样的双端队列称为输入受限的双端队列。
- 如果规定一个双端队列的元素只可以从进队的端点出队,那么可以想象,这样的双端队列实际上就是两个栈——两个栈底相邻的栈。
显然,能够在两端进行插入和删除,那么构成双端队列的结点肯定是双向的:既有一个指针指向下一个结点,又有一个指针指向上一个结点:
typedef struct Node{
ElementType e;
Node* next;
Node* prev;
// ...
}Node;
迷宫问题
除了用栈(采用深度优先搜索时)解决迷宫问题以外,我们还可以利用队列解决层次遍历的方法,采用层次遍历的方式解决,按照步数,一步一层,每次都将下一步可能的结点进队,由于有 4 4 4 个方向,所以每一层的结点数量都是上一层有效结点的4倍,当队列为空的时候或者找到出口时结束:
typedef struct Grid{
char contain;
int xCoord;
int yCoord;
Grid(char contain_, int x_coord, int y_coord)
:contain(contain_), xCoord(x_coord), yCoord(y_coord){}
}Grid;
void solution(char** map, int length, int width, int startX, int startY){
LinkedQueue<Grid> queue;
// initialize
queue.push(Grid(' ', startX, startY));
// start iteration
for(;!queue.isEmpty();){
Grid g = queue.pop_front();
// goooooooooooal!
if(g.contain == '*'){
std::cout << "Exit -> X: " << g.xCoord << " Y: " << g.yCoord << std::endl;
return;
}
// push back nodes of next layer
if(g.contain == ' '){
// move up
if(g.yCoord > 0){
queue.push_back(Grid(map[g.yCoord - 1][g.xCoord], g.xCoord, g.yCoord - 1);
}
// move down
if(g.yCoord < length - 1){
queue.push_back(Grid(map[g.yCoord + 1][g.xCoord], g.xCoord, g.yCoord + 1);
}
// move left
if(g.xCoord > 0){
queue.push_back(Grid(map[g.yCoord][g.xCoord - 1], g.xCoord - 1, g.yCoord);
}
// move right
if(g.yCoord < width - 1){
queue.push_back(Grid(map[g.yCoord][g.xCoord + 1], g.xCoord + 1, g.yCoord);
}
}
}
// queue become empty, that means:
std::cout << "No solution!" << std::endl;
return;
}
相对于用栈实现的深度优先搜索(DFS,Depth First Search)策略,利用队列,采用层次遍历的方式,每次将下一层可能的所有的结点入队,这样的搜索策略称为广度优先搜索(BFS,Broadth First Search)策略