1.栈
1.1 定义
在之前的学习中,我们知道线性表是具有相同数据类型的n个数据元素的有限序列,而栈就是只允许在一端进行插入和删除操作的线性表。因此栈是一种先进后出(FILO)的结构
1.2 注意事项
各位老铁,值得注意的是 ,栈的生长方向(从栈底向栈顶)在物理地址大小中,可以是从小到大(栈顶的物理地址大于栈底),也可以是从大到小(栈顶的物理地址小于栈底)。这一点爱出一些选择题!
在做关于栈的题目时候也需要注意,有的时候 题目会说栈顶指针所指的栈顶是有元素的(左图),有的时候会说栈顶指针指向的是栈顶元素的下一个空间(右图)。不同情况下判断栈空、栈满的条件也不同(比较的物理地址不同)。
有一种特殊的应用情况,那就是共享栈,在共享栈中,两个栈的栈底分别位于存储空间的两端,向中间增长。这样,两个栈可以共享这一固定大小的存储空间。
共享栈的优点包括:
1. 有效地利用了存储空间,尤其在存储空间有限的情况下,避免为两个独立的栈分别分配内存。
2. 降低了内存的开销和分配的复杂性。
共享栈也存在一些限制:
1. 每个栈可用的最大空间受到总空间大小和另一个栈使用情况的限制。
2. 实现和操作相对较复杂,需要特别注意两个栈的增长和边界情况,以避免相互干扰和溢出。
考的话一般只会问判空条件:
如果如图的共享栈地址从左向右增长,那么判空条件为top2-top1=1
1.3 顺序栈实现
注意这里是顺序栈,各位同学,栈当然也有链式存储,不过因为这种数据结构(线性表、队列、串之类)考研中不咋考察代码,我就懒得写了。
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#define MAX_SIZE 100
typedef struct {
int data[MAX_SIZE];
int top;
}SeqStack;
// 初始化栈
void initStack(SeqStack &stack) {
stack.top = -1;//注意这里 初始的栈顶指针指向的位置为-1
}
// 判断栈是否为空
bool isEmpty(SeqStack stack) {
return stack.top == -1;//判空条件因为初始情况改变
}
// 判断栈是否已满
bool isFull(SeqStack stack) {
return stack.top == MAX_SIZE - 1;//判断条件同上
}
// 入栈
void push(SeqStack &stack, int element) {
if (isFull(stack)) {
printf("Stack is full!\n");
return;
}
stack.data[++stack.top] = element;
//因为初始的时候栈顶指针指向-1,当有元素要进栈时,需要先将top+1到0的位置在入栈
//可以将这样的情况看作时栈顶指针指向的位置装有元素
//如果栈顶指针指向位置不装有元素,入栈时可以直接将元素存放在栈顶指针现在所指的位置,而后栈顶指针+1即可(top++)
}
// 出栈
int pop(SeqStack &stack) {
if (isEmpty(stack)) {
printf("Stack is empty!\n");
return -1;//空了就返回-1
}
int element = stack.data[stack.top];
stack.top--;
return element;
}
// 获取栈顶元素
int getTop(SeqStack stack) {//没有&引用,因为只获取不改变栈
if (isEmpty(stack)) {
printf("Stack is empty!\n");
return -1;
}
return stack.data[stack.top];
}
int main() {
SeqStack stack;
initStack(stack);
push(stack, 1);
push(stack, 2);
push(stack, 3);
push(stack, 4);
push(stack, 5);
push(stack, 6);
printf("Top element: %d\n", getTop(stack));//获取栈顶元素
int poppedElement = pop(stack);//弹出栈顶元素
if (poppedElement!= -1) {//栈没空的情况
printf("Popped element: %d\n", poppedElement);
}
printf("Top element after pop: %d\n", getTop(stack));//弹出操作之后的栈顶元素
return 0;
}
2.队列
2.1 定义
什么是队列?队列只允许在一端(常叫队尾)进行插入,另一端(队头)进行删除操作的线性表。这里需要注意,是一边插入,另一边删除!!!同时队列是一种先进先出(FIFO)的结构。
2.2 注意事项
各位同学可能会发现,即使是像队列这样可以一头插入,一头删除,依旧不是特别方便,如果可以两头都可以插入删除岂不是太妙了,恭喜你,你发明了双端队列!
可是,你还不满足,如果可以在任意限制其中一头的操作那该多方便,比如我想让头部只允许出,但是尾部可以进可以出。那么又得恭喜你了,你发明了操作受限的队列!
然而,当出现(d)这种情况时,在想添加元素就麻烦了,因此如果把头尾连在一起,就可以方便想继续添加元素了,恭喜你发明了循环队列。
此时存在关系式 Q. front=Q. rear, 只凭等式 Q. front= Q. rear 无法 判别队列空间是“空”还是“满”。可有两种处理方法:其一是另设一个标志位以区别队列 是“空”还是“满”;其二是少用一个元素空间,约定以"队列头指针在队列尾指针的下一位 置(指环状的下一位置)上”作为队列呈“满“状态的标志。(自行理解)
2.3 队列代码实现(循环队列)
注意看代码的判满!使用的就是少用一个元素空间,约定以"队列头指针在队列尾指针的下一位 置(指环状的下一位置)上”作为队列呈“满“状态的标志,但是会牺牲一个存储空间。
#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 6 // 队列的最大容量
typedef struct {
int data[MAX_SIZE];
int front;
int rear;
} Queue;
// 初始化队列
void initQueue(Queue &q) {
q.front = 0;
q.rear = 0;
}
// 判断队列是否为空
int isEmpty(Queue q) {
return q.front == q.rear;
}
// 判断队列是否已满
int isFull(Queue q) {
return (q.rear + 1) % MAX_SIZE == q.front;//少用一个元素空间,约定以"队列头指针在队列尾指针的下一位置
//(指环状的下一位置)上”作为队列呈“满“状态的标志。
}
// 入队操作
void enQueue(Queue &q, int element) {
if (isFull(q)) {
printf("Queue is full!\n");
return;
}
q.data[q.rear] = element;
q.rear = (q.rear + 1) % MAX_SIZE;
}
// 出队操作
int deQueue(Queue &q) {
if (isEmpty(q)) {
printf("Queue is empty!\n");
return -1;
}
int element = q.data[q.front];
q.front = (q.front + 1) % MAX_SIZE;
return element;
}
// 打印队列元素
void printQueue(Queue &q) {
if (isEmpty(q)) {
printf("Queue is empty!\n");
return;
}
int i = q.front;
printf("Queue: ");
do {
printf("%d ", q.data[i]);
i = (i + 1) % MAX_SIZE;
} while (i!= q.rear);
printf("\n");
}
int main() {
Queue q;
initQueue(q);
enQueue(q, 10);
printQueue(q);
enQueue(q, 20);
printQueue(q);
enQueue(q, 30);
printQueue(q);
enQueue(q, 40);
printQueue(q);
enQueue(q, 50);
printQueue(q);
enQueue(q, 60);
printQueue(q);
/*int dequeuedElement = deQueue(q);
if (dequeuedElement!= -1) {
printf("Dequeued element: %d\n", dequeuedElement);
}*/
return 0;
}
如图,60这个元素并没有存入, 在入队的时候显示已经满了,再打印也只有5个元素。