数组与链表是数据存储的基本方法。成员访问中,数组可以随机的访问成员,链表通过遍历可以去找到需要的数据单元。
栈与队列是两种特殊的数据成员管理方式。它们本身的数据存放方式也是数组或者链表。
- 栈:FILO(先进后出)。只允许在栈顶添加元素和删除元素(出栈和入栈)
- 队列:FIFO(先进先出)。在队首删除元素,在队尾添加元素(出队和入队)
1. 栈
栈顶、栈底:允许进行插入和删除操作的一端称为栈顶(top),另一端称为栈底(bottom),栈底固定,栈顶浮动;
空栈:栈中元素个数为零时称为空栈;
入栈:插入一般称为入栈(PUSH);
出栈:删除一般称为出栈(POP);
1.1 顺序栈
顺序栈,即用顺序表(底层实现是数组)实现栈存储结构,例如我们先使用顺序栈(a数组)存储{1,2,3,4},存储状态如下图所示:
于是,我们可以得到顺序栈的存储结构如下:
const int MAXSIZE = 10;
// 顺序栈的存储结构
typedef struct sq_stack{
int data[MAXSIZE]; // 栈大小
int top = -1; // top指向栈顶
}SqStack;
栈空:很自然的我们将top初始化为-1,代表栈空的状态top==-1;
栈满:因为MAXSIZE代表最大存储个数,所哟我们top最大下标只能到MAXSIZE-1,所以当top==MAXSIZE-1时即为栈满的状态;
1.1.1 入栈
首先我们需要明确,因为栈限制再栈顶进行插入和删除,所以我们只需对top进行移动即可实现。对于入栈(栈不满的情况下),设想一下,我们应该先得到空出位置的编号,再将其对号入座,而top是栈顶元素的下标,所以我们先将top++,再将数组对应的top位置赋值。
// 入栈
void Push(SqStack* s, int a)
{
if (s->top == MAXSIZE - 1) {
cout << "栈满" << endl;
return;
} else {
s->top++;
s->data[s->top] = a;
}
}
1.1.2 出栈
同样的,出栈我们应该先得到栈顶元素的数据,然后将其栈顶位置下标减1即可。(实际上原来top下标的数据仍然在存在于数组中,但是不在栈中,因为它不在top范围内)。
// 出栈
int Pop(SqStack* s)
{
int val = 0;
if (s->top == -1) {
cout << "栈空" << endl;
return -1;
} else {
val = s->data[s->top];
s->top--;
}
return val;
}
1.1.3 顺序栈完整代码
#include<iostream>
using namespace std;
const int MAXSIZE = 10;
// 栈存储结构
typedef struct sq_stack{
int data[MAXSIZE]; // 栈大小
int top = -1; // top指向栈顶
}SqStack;
// 入栈
void Push(SqStack* s, int a)
{
if (s->top == MAXSIZE - 1) {
cout << "栈满" << endl;
return;
} else {
s->top++;
s->data[s->top] = a;
}
}
// 出栈
int Pop(SqStack* s)
{
int val = 0;
if (s->top == -1) {
cout << "栈空" << endl;
return -1;
} else {
val = s->data[s->top];
s->top--;
}
return val;
}
// 打印
void Print(SqStack* s)
{
if (s->top == -1) {
cout << "栈空" << endl;
}
while (s->top != -1) {
cout << Pop(s) << endl;;
}
}
int main()
{
SqStack sq;
SqStack* s = &sq;
Push(s, 2);
Push(s, 4);
Push(s, 5);
Push(s, 8);
Pop(s);
Print(s);
}
2. 队列
队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作。
2.1 顺序队列
顺序队列通常采用一维数组存储队列中的元素,另外增加两个索引分别指示数组中存放的队首元素和队尾元素。其中指向队首的索引为front,指向队尾的索引为rear。
// 顺序队列的存储结构
typedef struct sq_queue {
int data[MAXSIZE];
int front = 0;
int rear = 0;
}SqQueue;
队列空:当front==rear时代表队列空;
队列满:当rear>MAXSIZE-1时代表队列满;
2.1.1 入队
void Push(SqQueue* s, int a)
{
if (s->rear <= MAXSIZE - 1) {
s->data[s->rear] = a;
s->rear++;
} else {
cout << "队满" << endl;
}
}
2.1.2 出队
int Pop(SqQueue* s)
{
if (s->front == s->rear) {
cout << "队空" << endl;
return -1;
} else {
return s->data[s->front++];
}
}
2.1.3 顺序队列完整代码
typedef struct sq_queue {
int data[MAXSIZE];
int front = 0;
int rear = 0;
}SqQueue;
void Push(SqQueue* s, int a)
{
if (s->rear <= MAXSIZE - 1) {
s->data[s->rear] = a;
s->rear++;
} else {
cout << "队满" << endl;
}
}
int Pop(SqQueue* s)
{
if (s->front == s->rear) {
cout << "队空" << endl;
return -1;
} else {
return s->data[s->front++];
}
}
void Print(SqQueue* s)
{
while (s->front != s->rear) {
cout << s->data[s->front] << endl;
s->front++;
}
}
int main()
{
SqQueue sq;
SqQueue* s = &sq;
Push(s, 2);
Push(s, 4);
Push(s, 5);
Push(s, 8);
Pop(s);
Print(s);
}
2.2 循环队列
假溢出:但是按照上述顺序存储的方式,很容易出现假溢出。所谓假溢出就是经过多次插入和删除操作后,实际队列还有存储空间,但是又无法向队列中插入元素,简单来说就是数组越界的错误。
例如在队列中删除a和b后,然后以此插入i和j,当插入j后就会出现队尾索引rear越出数组下界造成假溢出,如下图:
解决假溢出的办法就是后面满了,再从头开始,也就是头尾相连的循环结构体。
我们把队列的这种头尾相连的顺序存储结构称为循环队列。
当队尾rear和队首front到达存储空间的最大值时,让rear或front转化为0,这样就可以将元素插入到队列的空闲存储单元中,有效的利用存储空间,消除假溢出。
其实循环队列也是用数组存储,只不过为了形象表现出来,我们将图做成一个“环状”,实际上还是线性的数组结构。
// 循环队列的存储结构
typedef struct sq_queue {
int data[MAXSIZE];
int front = 0;
int rear = 0;
}SqQueue;
队空:显然当front==rear时表示没有元素,此时队空。
队满:为了和队空区分,我们不妨让队列空出一个位置,这个位置不妨任何元素,仅仅是为了区别队空和队满,但也可能整整相差一圈,所以如果最大尺寸为MAXSIZE,那么当(rear+1)%MAXSIZE==front时就代表队列满了。
获取队列长度:
当,队列长度为;
当,队列长度为;
所以可以统一为%
int Length(SqQueue* s)
{
return (s->rear - s->front + MAXSIZE) % MAXSIZE;
}
2.2.1 入队
void Push(SqQueue* s, int a)
{
if ((s->rear + 1) % MAXSIZE == s->front) {
cout << "栈满" << a << endl;
return;
} else {
s->data[s->rear] = a;
s->rear = (s->rear + 1) % MAXSIZE;
}
}
2.2.2 出队
int Pop(SqQueue* s)
{
if (s->front == s->rear) {
cout << "队空" << endl;
return -1;
} else {
int val = s->data[s->front];
s->front = (s->front + 1) % MAXSIZE;
return val;
}
}
2.2.3 循环队列完整代码
#include<iostream>
using namespace std;
const int MAXSIZE = 10;
typedef struct sq_queue {
int data[MAXSIZE];
int front = 0;
int rear = 0;
}SqQueue;
void Push(SqQueue* s, int a)
{
if ((s->rear + 1) % MAXSIZE == s->front) {
cout << "栈满" << a << endl;
return;
} else {
s->data[s->rear] = a;
s->rear = (s->rear + 1) % MAXSIZE;
}
}
int Pop(SqQueue* s)
{
if (s->front == s->rear) {
cout << "队空" << endl;
return -1;
} else {
int val = s->data[s->front];
s->front = (s->front + 1) % MAXSIZE;
return val;
}
}
void Print(SqQueue* s)
{
while (s->front != s->rear) {
cout << s->data[s->front] << endl;
s->front = (s->front + 1) % MAXSIZE;
}
}
int Length(SqQueue* s)
{
return (s->rear - s->front + MAXSIZE) % MAXSIZE;
}
int main()
{
SqQueue sq;
SqQueue* s = &sq;
Push(s, 2);
Push(s, 4);
Push(s, 5);
Push(s, 8);
Pop(s);
Push(s, 2);
Push(s, 4);
Push(s, 5);
Push(s, 8);
Push(s, 2);
Push(s, 4);
Pop(s);
Push(s, 2);
Print(s);
}