本文内容源于对王道书籍中栈和队列知识点的笔记和总结。主要以习题方式熟悉栈和队列的基本操作(包含初始化、判栈空、进栈出栈等基本运算的实现)。方便考研复习栈和队列。
可搭配以下链接一起学习:
【考研】栈在表达式求值中的应用(真题分析)_住在阳光的心里的博客-CSDN博客
【2023考研】数据结构常考应用典型例题(含真题)_住在阳光的心里的博客-CSDN博客
前言
一、栈
(一)顺序栈基本运算的实现
(二)习题
二、队列
(一)循环队列的操作
(二)链式队列的基本操作
(三)习题
//初始化
void InitStack(SqStack &S){
S.top = -1; //初始化栈顶指针为 -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; //先栈顶指针加1,再进栈
/*若初始化 S.top = 0,即 top 指向栈顶元素的下一位置
则进栈改为 S.data[S.top++] = x;
出栈改为 x = S.data[--S.top];
*/
return true;
}
//出栈
bool Pop(SqStack &S, ElemType &x){
if(S.top == -1)
return false; //栈空报错
x = S.data[S.top--]; //先出栈,再栈顶指针减1
return true;
}
//读栈顶元素
bool GetTop(SqStack S, ElemType &x){
if(S.top == -1)
return false; //栈空报错
x = S.data[S.top]; //x 记录栈顶元素
return true;
}
1、设单链表的表头指针为 L,结点结构由 data 和 next 两个域构成,其中 data 域为字符型。试设计算法判断该链表的全部 n 个字符是否中心对称。例如 xyx、xyyx 都是中心对称。
解:(1)算法思想:使用栈来判断链表中的数据是否中心对称。让链表的前一半元素依次入栈。在处理链表的后一半元素时,当访问到链表的一个元素后,就从栈中弹出一个元素,两个元素比较,若相等,则将链表中的下一个元素与栈中再弹出的元素比较,直至链表到尾。
这时若栈是空栈,则得出链表中心对称的结论;否则,当链表中的一个元素与栈中弹出元素不等时,结论为链表非中心对称,结束算法的执行。
(2)代码如下:
//单链表结点类型
typedef struct LNode{
char data;
struct LNode *next;
}LNode, *LinkList;
//判断带头结点的单链表的全部 n 个字符是否中心对称
int dc(LinkList L, int n){
int i;
char s[n/2]; // s 是字符栈
LNode *p = L->next; // p 是链表的工作指针,指向待处理的当前元素
for(i = 0; i < n/2; i++){ //链表的前一半元素进栈
s[i] = p->data;
p = p->next;
}
i--; //恢复最后的 i 值
if(n % 2 == 1) //如果 n 是奇数,中心结点字符不必比较,移动指针到下一字符开始比较
p = p->next;
while(p != NULL && s[i] == p->data){ //检测是否中心对称
i--; // i 充当栈顶指针
p = p->next;
}
if(i == -1) //栈为空栈
return 1; //链表中心对称
else
return 0; //链表不中心对称
}
2、设有两个栈 s1 和 s2 都采用顺序栈方式,并共享一个存储区 [0, ... , maxsize - 1] ,为了尽量利用空间,减少溢出的可能,可采用栈顶相向、迎面增长的存储方式。试设计 s1, s2 有关入栈和出栈的操作算法。
解:(1)算法思想:两个栈共享向量空间,将两个栈的栈底设在向量两端,初始时,s1 栈顶指针为 - 1,s2 栈顶指针为 maxsize。两个栈顶指针相邻时为栈满。两个栈顶相向,迎面增长,栈顶指针指向栈顶元素。
(本题关键:两个栈入栈和退栈时的栈顶指针的计算。 s1 是通常意义下的栈,而 s2 栈入栈操作时,其栈顶指针左移(减1),退栈时,栈顶指针右移(加1)。此外,对所有栈的操作,都要注意“入栈判满,出栈判空” 的检查。)
(2)代码如下:
#define maxsize 100 //两个栈共享顺序存储空间所能达到的最多元素数,初始化为100
#define elemtp int //假设元素类型为整型
typedef struct {
elemtp stack[maxsize]; //栈空间
int top[2]; // top 为两个栈顶指针
}stk;
stk s; //全局变量
//入栈操作
// i 为栈号,i = 0 表示左边的 s1 栈,i = 1 表示右边的 s2 栈
// x 为入栈元素
// 入栈成功返回1,否则返回0
int push(int i, elemtp x){
if(i < 0 || i > 1){
printf("栈号输入不对");
exit(0);
}
if(s.top[1] - s.top[0] == 1){
printf("栈已满\n");
return 0;
}
switch(i){
case 0: s.stack[++s.top[0]] = x; return 1; break;
case 1: s.stack[--s.top[1]] = x; return 1;
}
}
//退栈操作
// i 为栈号,i = 0 表示 s1 栈,i = 1 表示 s2 栈
// 退栈成功返回退栈元素,否则返回-1
elemtp pop(int i){
if(i < 0 || i > 1){
printf("栈号输入不对");
exit(0);
}
switch(i){
case 0:
if(s.top[0] == -1){
printf("栈空\n");
return -1;
}
else{
return s.stack[s.top[0]--];
}
case 1:
if(s.top[1] == maxsize){
printf("栈满\n");
return -1;
}
else{
return s.stack[s.top[1]++];
}
}
}
#define MaxSize 50 //定义队列中元素的最大个数
typedef struct{
ElemType data[MaxSize]; //存放队列元素
int front, rear; //队头指针和队尾指针
}
//初始化
void InitQueue(SqQueue &Q){
Q.rear = Q.front = 0; //初始化队首、队尾指针
}
//判队空
bool IsEmpty(SqQueue Q){
if(Q.rear == Q.front) //队空条件
return true;
else
return false;
}
//入队
bool EnQueue(SqQueue &Q, ElemType x){
if((Q.rear + 1) % MaxSize == Q.front) //队满则报错
return false;
Q.data[Q.rear] = x;
Q.rear = (Q.rear + 1) % MaxSize; //队尾指针加1取模
return true;
}
//出队
bool DeQueue(SqQueue &Q, ElemType &x){
if(Q.rear == Q.front) //队空则报错
return false;
x = Q.data[Q.front];
Q.front = (Q.front + 1) % MaxSize; //队头指针加1取模
return true;
}
typedef struct{ //链式队列结点
ElemType data;
struct LinkNode *next;
}LinkNode;
typedef struct{ //链式队列
LinkNode *front, *rear; //队列的队头和队尾指针
}LinkQueue;
//初始化
void InitQueue(LinkQueue &Q){
Q.front = Q.rear = (LinkNode*)malloc(sizeof(LinkNode)); //建立头结点
Q.front->next = NULL; //初始化空
}
//判队空
bool IsEmpty(LinkQueue Q){
if(Q.front == Q.rear)
return true;
else
return false;
}
//入队
void EnQueue(LinkQueue &Q, ElemType x){
LinkNode *s = (LinkNode*)malloc(sizeof(LinkNode));
s->data = x; //创建新结点,插入到链尾
s->next = NULL;
Q.rear->next = s;
Q.rear = s;
}
//出队
void DeQueue(LinkQueue &Q, ElemType &x){
if(Q.front == Q.rear) //队空则报错
return false;
LinkNode *p = Q.front->next;
x = p->data;
Q.front->next = p->next;
if(Q.rear == p)
Q.rear = Q.front; //若原队列只有一个结点,删除后变空
free(p);
return true;
}
1、若希望循环队列中的元素都能得到利用,则需设置一个标志域 tag,并以 tag 的值为 0 或 1 来区分队头指针 front 和队尾指针 rear 相同时的队列状态是“空”还是“满”。试编写与此结构相应的入队和出队算法。
解:(1)算法思想:在循环队列的类型结构中,增设一个 tag 的整型变量,进队时置 tag 为1,出队时置 tag 为 0。(因为只有入队操作可能导致队满,也只有出队操作可能导致队空)
队列 Q 初始时,置 tag = 0、front = rear = 0。这样队列的 4 要素如下:
队空条件 | Q.front == Q.rear | Q.tag == 0 |
队满条件 | Q.front == Q.rear | Q.tag == 1 |
进队操作 | Q.data[Q.rear] = x; Q.rear = (Q.rear+1)%MaxSize; |
Q.tag = 1 |
出队操作 | x = Q.data[Q.front]; Q.front = (Q.front+1)%MaxSize; |
Q.tag = 0 |
(2)代码如下:
//设 "tag" 法的循环队列,入队算法
int EnQueue1(SqQueue &Q, ElemType x){
if(Q.front == Q.rear && Q.tag == 1) //队满
return 0;
Q.data[Q.rear] = x;
Q.rear = (Q.rear + 1) % MaxSize;
Q.tag = 1; //可能队满
return 1;
}
//设 "tag" 法的循环队列,出队算法
int DeQueue1(SqQueue &Q, ElemType &x){
if(Q.front == Q.rear && Q.tag == 0) //队空
return 0;
x = Q.data[Q.front];
Q.front = (Q.front + 1) % MaxSize;
Q.tag = 0; //可能队空
return 1;
}
2、Q 是一个队列,S 是一个空栈,实现将队列中的元素逆置的算法。
解:(1)算法思想:由于对队列的一系列操作不可能将其中的元素逆置,而栈可以将入栈的元素逆序提取出来,因此我们可以让队列中的元素逐个出队列,入栈;全部入栈后再逐个出栈,入队列。
(2)代码如下:
void Inverser(Stack &s, Queue &q){
while(!QueueEmpty(q)){
x = DeQueue(q); //队列中全部元素依次出队
Push(s, x); //元素依次入栈
}
while(!StackEmpty(s)){
Pop(s, x); //栈中元素依次出栈
EnQueue(Q, x); //再入队
}
}