文章目录
-
- 考纲和命题形式
-
- 考纲
- 命题形式
- 栈
-
- 基本概念
- 栈的基本操作
- 栈的两种存储方式
-
- 顺序栈
- 代码
- 共享栈
- 链栈
- 代码
- 队列
-
- 队列的基本概念
- 队列的顺序存储结构
-
- 循环队列
- 队列的链式存储结构
- 双端队列
- 错题
-
- 栈
- 队列
今日格言:有很多时候,勇气不是从你的脑袋里生出,而是从你的脚下涌现。
考纲和命题形式
考纲
- 栈和队列的基本概念
- 栈和队列的顺序存储结构
- 栈和队列的链式存储结构
- 多维数组的存储
- 特殊矩阵的压缩存储
- 栈、队列和数组的应用
命题形式
- 通常以选择题1至2题考察4个考点
- 大题中只有19年考了一个队列
题目不算难,但命题的形式比较灵活,其中栈(出入栈的过程、出栈序列的合法性和种类)以及其特征是重点。
栈
基本概念

🌟栈的定义:栈(Stack)是只允许在一端进行插入或删除操作 的线性表 。也就是说栈是一种受限线性表,限定这种线性表只能在某一端进行插入和删除操作。这是从数据的逻辑结构上了解栈。
如上图所示:不含任何元素的栈叫做空栈 也是空表。
🌟栈顶 (Top)是栈(线性表)允许进行插入和删除的那一端。
🌟栈底 (Bottom)是固定的,不允许进行插入和删除的另外一端。
可以将其形象地记忆为垒起来的石堆(stack)或者一摞只允许从上面放下或者拿走的一摞盘子。
另外,从图中可以看出a1a_1a1是栈底元素,a4a_4a4是栈顶元素,a1a_1a1虽然最先从栈顶入栈,但是要想出栈必须在a4a_4a4出栈之后,这就是栈的操作特性:后进先出即Last in First out(LIFO)。
下面这段简单的模拟栈的C语言程序有助于理解:
c
#include<stdio.h>
int main(){
int Stack[5];
int top = -1; //模拟栈顶指针
int bottom = 0;
Stack[++top]=985;//模拟入栈
Stack[++top]=211;
Stack[++top]=400;
while(top >= bottom) { //按序打印栈中内容
printf("当前栈顶元素为:%d\n",Stack[top]);// 打印当前栈顶元素
top--; // 出栈
}
return 0;
}

栈的基本操作
接下来从数据的运算角度来看栈---栈的基本操作。除了上面提到的进栈、出栈操作,栈还有以下基本操作:
- 创: InitStack(&S):初始化一个空栈S。
- 销:DestroyStack(&S):销毁栈,并释放栈S占用的存储空间(顺序栈使用静态数组存储元素,只需逻辑上删除所有元素即可,存储空间系统自动回收)。
- 查:GetTop(S,&x):若栈S非空,则读取栈顶元素,并不出栈;若栈为空则报错。
- 判空:StackEmpty(S):判空操作,若S为空栈,则返回true否则返回false。
入栈和出栈的操作如下: - 增:Push(&S,x):若栈未满则进栈,使x成为新栈顶,若栈满则报错。
- 删:Pop(&S,&x):出栈,若栈非空,则弹出栈顶元素并用x返回;若栈空则报错。
一个小知识点:当n个不同元素进栈时,出栈元素不同排列的个数为:
这个数也叫做卡特兰数。
栈的两种存储方式
接下来我们将从数据的存储结构上来了解栈,栈也采用之前线性表使用的两种存储结构---顺序存储和链式存储。至于索引存储,我只在操作系统和计算机组成原理的存储系统这一章里看到过,而散列存储似乎和查找这一章节有关。
顺序栈
🌟采用顺序存储的栈称为顺序栈,它利用一组地址连续的存储单元存放自栈底到栈顶的数据元素,同时附设一个指针top指示当前栈顶元素的位置。 (真正有效的文字就应该这样,简短而富含信息,值得学习)一组连续的存储单元在这里就是指静态数组。
代码
示例代码仅供参考,更严谨代码请动手写代码并且运行。
1.顺序栈的定义
c
#include <stdio.h>
#define MaxSize 10
typedef struct {
ElemType data[Maxsize];
int top;
}SqStack; //Sequence Stack 顺序栈
建议写代码的时候脑子里也要想着内存图:
- 顺序栈的基本操作
c
//初始化
void InitStack(SqStack &S) {
S.top = -1; //初始化栈顶指针
}
//判栈空
bool StackEmpty(SqStack S) {
if(S.top == -1)
return true;
else
return false;
}
//进栈
bool Push(SqStack &S,ElemType x) {
if(S.top == MaxSize - 1)
return false; //栈满报错
S.data[++S.top] = x;
return true;
}
//出栈
bool Pop(SqStack &S,ElemType &x) {
if(S.top == -1)
return false; //栈空报错
x = S.data[S.top--];
return true;
}
//读栈顶元素
bool GetTop(SqStack S,ElemType &x) {
if(S.top == -1)
return false; //栈空报错
x = S.data[S.top]; //x记录栈顶元素
return true;
}
另一种方法是令top为0,这就会带来入栈操作、出栈操作、判空操作和判满操作的变化:
c
S.data[top++]=x;//进栈
x=S.data[--top];//出栈
if(S.top == 0) //判空
if(S.top == MaxSize) //判满
- 使用顺序栈实现第一个模拟栈的程序
c
int main() {
ElemType x;
SqStack S;
InitStack(S);
Push(S,985);
Push(S,211);
Push(S,400);
while(!StackEmpty(S)) {
Pop(S,x); //获取栈顶元素并出栈
printf("当前栈顶元素为%d\n",x);
}
return 0;
}
共享栈
利用栈底位置相对不变的特性,可让两个顺序栈共享一个一维数组空间,将两个栈的栈底分别设置在共享空间的两端,两个栈顶向共享空间的中间延伸,如下图所示:
两个栈的栈顶指针都指向栈顶元素,top0=-1时0号栈为空,top1=MaxSize时1号栈为空;仅当两个栈顶指针相邻(top1-top0=1)时,判断栈满。当0号栈进栈时top0先加上1再赋值,1号栈进栈时top1先减1再赋值,出栈时顺序正好相反。
共享栈是为了更有效地利用存储空间,两个栈的空间相互调节,只有在整个存储空间被占满时才发生上溢。其存取数据的时间复杂度均为O(1),所以对存取效率没有什么影响。
定义及初始化代码如下:
c
#define MaxSize 10
typedef struct{
ElemType data[MaxSize];
int top0; //0号栈栈顶指针
int top1; //1号栈栈顶指针
}ShStack;
void InitStack(ShStack &S) {
S.top0 = -1;
S.top1 = MaxSize;
}
链栈
🌟定义:采用链式存储的栈就是链栈。
优点:便于多个栈共享存储空间和提高其效率,且不存在栈满上溢 的情况。通常采用单链表 实现,并规定入栈和出栈操作是在单链表的表头 进行的。采用链式存储,便于结点的插入与删除。链栈的操作与链表类似,入栈和出栈的操作都在链表的表头 进行。需要注意的是:对于带头结点和不带头结点的链栈,具体的实现会有所不同 。
链栈本质上还是一个单链表 ,只不过是把表头的那端当作栈顶 。入栈操作对应单链表的头插法 ,出栈操作对应头结点的后删操作 。
下面这个过程图是使用带头结点的链栈 实现第一个模拟栈的C语言程序:
代码
1.带头结点的链栈(c++)
c++
/带头结点的链栈
typedef int ElemType; //定义元素类型为int类型
typedef struct LinkNode{
ElemType data; //数据域
struct LinkNode *next; //指针域
}LinkStack, *Lhead;
//初始化
void InitStack(Lhead &L) {
L=(LinkStack *)malloc(sizeof(LinkStack));
L->next = NULL;
}
//判栈空
bool StackEmpty(Lhead L){
if(L->next == NULL)
return true; //栈空
else
return false;
}
//进栈
bool Push(Lhead &L,ElemType x){
LinkStack *p = (LinkStack *)malloc(sizeof(LinkStack));
if(p==NULL)
return false;//申请空间失败
p->data = x;
p->next = L->next;
L->next = p;
return true; }
//出栈bool Pop(Lhead &L,ElemType &x) {
if(StackEmpty(L))
return false; //栈空
LinkStack *p = L->next; //找到栈顶结点
x = p->data; //获取栈顶元素
L->next = p->next; //出栈
free(p); //释放结点空间
return true;
}
int main() {
//用链栈表示第一个模拟栈程序
ElemType x;
Lhead L; //创建一个链栈并初始化
InitStack(L);
Push(L,985);
Push(L,211);
Push(L,400);
//按序打印栈中元素
while(!StackEmpty(L)){
Pop(L,x); //获取栈顶元素并出栈
printf("当前栈顶元素为:%d\n",x);
}
Pop(L,x);
return 0;
}

2.不带头结点的链栈
队列
仍然从逻辑结构、基本运算、存储结构这三个方面了解队列
队列的基本概念
1.队列的定义
队列(Queue)和栈一样也是一种操作受限的线性表,只允许在表的一端进行插入,而在表的另一端进行删除 。允许删除的一端称为队头(Front) ,又称队首。允许插入的一端称为队尾(Rear) 。向队列中插入元素称为入队或进队;删除元素称为出队或离队。这和日常生活中的排队是一致的,最早排队的也是最早离队的,其操作特性是先进先出(FIFO)。
2.队列常见的基本操作
- 创:
- 销:
- 增:
- 删:
- 查:
- 判队空:
- 读队头:
队列的顺序存储结构
队列的顺序实现是指分配一块连续的存储单元存放队列中的元素,并附设两个指针:队头指针(front)和队尾指针(rear)。
队列的顺序存储类型可描述为:
c
#define MaxSize 50 //定义队列中元素的最大个数
typedef struct{
ElemType data[MaxSize];
int rear,front;
}SqQueue;
循环队列
队列的链式存储结构
队列的链式表示称为链队列,它实际上是一个同时有队头指针和队尾指针的单链表,如下图所示:
双端队列
双端队列是指允许两端都可以进行插入和删除操作的线性表,如下图所示:
1小时看45min视频,1小时整理基础知识,1小时做选择题,0.5小时敲代码,0.5小时处理错题。
错题
栈

第一次做对了,但第二次做错了,根本原因就是用的是举特例,第一次列出来了,第二次列出来少了一个,本质上还是不会,应该使用讨论的方法,真正做对。