栈本质也是一种线性表,只是操作受限,只能在一端插入或删除,具有last in first out的特性,故入栈、出栈顺序很重要。有了线性表的基础,也能快速构建【顺序栈】和【链栈】。
目录
一、顺序栈的实现
顺序栈:顾名思义,就是基于顺序表建立的栈,所以基本操作还是以顺序表为基础
1.指针top初始指向top=-1型顺序栈
0)初始化
bool InitStack(SqStack &S){
S.top=-1;
return true;
}
1)判断栈是否为空
//判栈空
bool StackEmpty(SqStack S){
if(S.top==-1)
return true;//栈空
else
return false;//栈不为空
}
2)读栈顶元素
3)入栈
为什么先top++,才放入元素?
注意:先修改top指针的值,再放入元素;这个逻辑很好理解:我们可以从初始状态开始想,初始时栈为空,当前top=-1,如果先放入元素,x都无法找到S.data[-1]的位置,更无法进栈。而如果top先加1,那么当前top更新为top=0,那么x就可以放入S.data[0]的位置,非常合理,接下来也是遵循,先加1,再入栈。
4)出栈
为什么先保存栈顶元素,再top--?
注意:先保存栈顶元素的值,再修改top指针;如果top先自减,那么实际上保存的是
s.data【top-1】的数据,不是我们想要的效果
5)栈的遍历
这里提到栈的遍历,是因为想将栈中元素打印出来,检查入栈和出栈操作是否正确。开始还想去单独求顺序表长,但突然发现top指针天然就是一个指示表长的指标!在top初始指向-1的语境下,s.top其实就是栈顶元素的下标,这对于遍历来说太方便了,不用做任何+1/-1的边界判断,即遍历时循环变量的范围为:0<=i<=s.top(注意右侧可以取等)
//从栈底到栈顶打印当前栈中元素
void Print(SqStack S){
cout<<"栈底->栈顶"<<endl;
for(int i=0;i<=S.top;i++)
cout<<S.data[i]<<" ";
cout<<endl;
}
6)完整代码及运行结果
#include <cstdlib>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <typeinfo>
using namespace std;
//测试用例:12 23 34 45 56 67 78 89 -100
#define MAXSIZE 100//栈中元素最大个数
typedef struct SqStack{
int data[MAXSIZE];//开辟数组存储栈中元素
int top;//栈底指针
}SqStack;
//初始化顺序栈
bool InitStack(SqStack &S){
S.top=-1;
return true;
}
//判栈空
bool StackEmpty(SqStack S){
if(S.top==-1)
return true;//栈空
else
return false;//栈不为空
}
//读栈顶元素,若S非空,返回栈顶元素
bool GetTop(SqStack S,int &x){
if(StackEmpty(S))
return false;
x=S.data[S.top];
return true;
}
//入栈,若栈未满,将x入栈并成为新的栈顶
bool Push(SqStack &S,int x){
if(S.top==MAXSIZE-1)
return false;
S.top++;//先让top指针自增
S.data[S.top]=x;//然后数据入栈
return true;
}
//出栈:若栈非空,弹出栈顶元素,并用x返回
bool Pop(SqStack &S,int &x){
if(StackEmpty(S))
return false;
x=S.data[S.top];
S.top--;
return true;
}
//从栈底到栈顶打印当前栈中元素
void Print(SqStack S){
cout<<"【打印:栈底->栈顶】"<<endl;
for(int i=0;i<=S.top;i++)
cout<<S.data[i]<<" ";
cout<<endl;
}
int main(){
SqStack S;
int TopValue;//栈顶元素的值
int x;//入栈元素
InitStack(S);
StackEmpty(S);
cout<<"当前栈状态为(1-空 0-非空):"<<StackEmpty(S)<<endl;
cout<<endl<<"【1-入栈】"<<endl;
cout<<"请输入要入栈数据,输入为-100时停止入栈"<<endl;
cin>>x;
while(x!=-100&&S.top<MAXSIZE-1){
Push(S,x);
cin>>x;
}
GetTop(S,TopValue);
cout<<"当前栈顶:"<<TopValue<<endl;
Print(S);
cout<<"当前栈状态为(1-空 0-非空):"<<StackEmpty(S)<<endl;
cout<<endl<<"【2-出栈】"<<endl;
int y;//弹出元素
// Pop(S,y);
// cout<<"当前出栈元素为:"<<y<<endl;
// Print(S);
while(StackEmpty(S)!=1)//将栈删空
{
Pop(S,y);
cout<<"当前出栈元素为:"<<y<<endl;
Print(S);
}
cout<<"栈已空,无法继续删除"<<endl;
}
开始运行...
当前栈状态为(1-空 0-非空):1
【1-入栈】
请输入要入栈数据,输入为-100时停止入栈
12 23 34 45 56 67 78 89 -100
当前栈顶:89
【打印:栈底->栈顶】
12 23 34 45 56 67 78 89
当前栈状态为(1-空 0-非空):0
【2-出栈】
当前出栈元素为:89
【打印:栈底->栈顶】
12 23 34 45 56 67 78
当前出栈元素为:78
【打印:栈底->栈顶】
12 23 34 45 56 67
当前出栈元素为:67
【打印:栈底->栈顶】
12 23 34 45 56
当前出栈元素为:56
【打印:栈底->栈顶】
12 23 34 45
当前出栈元素为:45
【打印:栈底->栈顶】
12 23 34
当前出栈元素为:34
【打印:栈底->栈顶】
12 23
当前出栈元素为:23
【打印:栈底->栈顶】
12
当前出栈元素为:12
【打印:栈底->栈顶】
栈已空,无法继续删除
运行结束。
2.指针top初始指向top=0型顺序栈
0)初始化
bool InitStack(SqStack &S){
S.top=0;
return true;
}
1)判断栈是否为空
//判栈空
bool StackEmpty(SqStack S){
if(S.top==0)
return true;//栈空
else
return false;//栈不为空
}
2)读栈顶元素-同top=-1的情况
3)入栈
为什么先入栈,再top++?
因为top初始指向top=0(即top指向下一个将入栈的位置),所以一开始就要先让数据入栈,即S.data[0]=x,再top++;而不是先加1,再入;
//入栈,若栈未满,将x入栈并成为新的栈顶
bool Push(SqStack &S,int x){
if(S.top==MAXSIZE)
return false;
S.data[S.top]=x;//先让数据入栈
S.top++;//再让top指针自增
4)出栈
为什么先top--,再弹出?
初始时,top=0;一些元素入栈后,top指向栈顶元素的下一个位置,所以此时相当于top指向的空间里没有数据,故需要先top--,再返回元素。
//出栈:若栈非空,弹出栈顶元素,并用x返回
bool Pop(SqStack &S,int &x){
if(StackEmpty(S))
return false;
S.top--;//先让top自减
x=S.data[S.top];//再弹出栈顶元素
return true;
}
5)栈的遍历
top=0与top=-1的区别在于:此时0<=i<S.top,右端不能再取等号,因为S.top指向为空
//从栈底到栈顶打印当前栈中元素
void Print(SqStack S){
cout<<"【打印:栈底->栈顶】"<<endl;
for(int i=0;i<S.top;i++)
cout<<S.data[i]<<" ";
cout<<endl;
}
6)完整代码及运行结果
#include <cstdlib>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <typeinfo>
using namespace std;
//测试用例:22 33 44 55 66 77 88 99 -100
#define MAXSIZE 100//栈中元素最大个数
typedef struct SqStack{
int data[MAXSIZE];//开辟数组存储栈中元素
int top;//栈底指针
}SqStack;
//初始化顺序栈
bool InitStack(SqStack &S){
S.top=0;
return true;
}
//判栈空
bool StackEmpty(SqStack S){
if(S.top==0)
return true;//栈空
else
return false;//栈不为空
}
//读栈顶元素,若S非空,返回栈顶元素
bool GetTop(SqStack S,int &x){
if(StackEmpty(S))
return false;
x=S.data[S.top];
return true;
}
//入栈,若栈未满,将x入栈并成为新的栈顶
bool Push(SqStack &S,int x){
if(S.top==MAXSIZE)
return false;
S.data[S.top]=x;//先让数据入栈
S.top++;//再让top指针自增
return true;
}
//出栈:若栈非空,弹出栈顶元素,并用x返回
bool Pop(SqStack &S,int &x){
if(StackEmpty(S))
return false;
S.top--;//先让top自减
x=S.data[S.top];//再弹出栈顶元素
return true;
}
//从栈底到栈顶打印当前栈中元素
void Print(SqStack S){
cout<<"【打印:栈底->栈顶】"<<endl;
for(int i=0;i<S.top;i++)
cout<<S.data[i]<<" ";
cout<<endl;
}
int main(){
SqStack S;
int TopValue;//栈顶元素的值
int x;//入栈元素
InitStack(S);
StackEmpty(S);
cout<<"当前栈状态为(1-空 0-非空):"<<StackEmpty(S)<<endl;
cout<<endl<<"【1-入栈】"<<endl;
cout<<"请输入要入栈数据,输入为-100时停止入栈"<<endl;
cin>>x;
while(x!=-100&&S.top<MAXSIZE-1){
Push(S,x);
cin>>x;
}
GetTop(S,TopValue);
cout<<"当前栈顶:"<<TopValue<<endl;
Print(S);
cout<<"当前栈状态为(1-空 0-非空):"<<StackEmpty(S)<<endl;
cout<<endl<<"【2-出栈】"<<endl;
int y;//弹出元素
// Pop(S,y);
// cout<<"当前出栈元素为:"<<y<<endl;
// Print(S);
while(StackEmpty(S)!=1)//将栈删空
{
Pop(S,y);
cout<<"当前出栈元素为:"<<y<<endl;
Print(S);
}
cout<<"栈已空,无法继续删除"<<endl;
}
开始运行...
当前栈状态为(1-空 0-非空):1
【1-入栈】
请输入要入栈数据,输入为-100时停止入栈
22 33 44 55 66 77 88 99 -100
当前栈顶:771756032
【打印:栈底->栈顶】
22 33 44 55 66 77 88 99
当前栈状态为(1-空 0-非空):0
【2-出栈】
当前出栈元素为:99
【打印:栈底->栈顶】
22 33 44 55 66 77 88
当前出栈元素为:88
【打印:栈底->栈顶】
22 33 44 55 66 77
当前出栈元素为:77
【打印:栈底->栈顶】
22 33 44 55 66
当前出栈元素为:66
【打印:栈底->栈顶】
22 33 44 55
当前出栈元素为:55
【打印:栈底->栈顶】
22 33 44
当前出栈元素为:44
【打印:栈底->栈顶】
22 33
当前出栈元素为:33
【打印:栈底->栈顶】
22
当前出栈元素为:22
【打印:栈底->栈顶】
栈已空,无法继续删除
运行结束。
二、链栈的实现
1.不带头结点的链栈
1)易错点1:
不带头结点的链表也需要malloc 申请空间,并初始为NULL,且不论带头结点还是不带头结点,都是L->next=NULL
bool IniLStack(LinkStack &LS){
LS=(LinkStack)malloc(sizeof(LinkStack));
LS->next=NULL;//初始化为空表
return true;
}
开始出错的写法是:
bool IniLStack(LinkStack &LS){
LS=(LinkStack)malloc(sizeof(LinkStack));
LS=NULL;//初始化为空表return true;
}
2)易错点2:
不带头结点的链表遍历,应该使得p的指向与头指针相同,而不是下一个结点
//顺序打印当前链栈中的数据
void Print(LinkStack LS){
LNode* p;
p=LS->next;//带头结点写法p=LS;//不带头结点
cout<<"top栈顶->栈底bottom"<<endl;
while(p!=NULL){
cout<<p->data<<" ";
p=p->next;
}
cout<<endl;
}
3)完整代码和运结果
//不带头结点的链栈
#include <cstdlib>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <typeinfo>
using namespace std;
typedef struct LNode{
char data;//数据域
struct LNode* next;//指针域:指向LNode的指针
}LNode,*LinkStack;
bool IniLStack(LinkStack &LS){
LS=(LinkStack)malloc(sizeof(LinkStack));
LS->next=NULL;//初始化为空表
return true;
}
//x入栈,不带头结点
bool pushLStack(LinkStack &LS,char x){
LNode* s;//定义指向插入节点的指针
s=(LNode*)malloc(sizeof(LNode));
s->data=x;
//改链操作:尤为注意,此为不带头结点情况!
s->next=LS;
LS=s;
return true;
}
void StackLength(LinkStack &LS){
LNode* p;
int j=0;
p=LS->next;
while(p){
p=p->next;
j++;
}
cout<<"链栈当前的元素个数为:"<<j<<endl;
//return j;
}
//顺序打印当前链栈中的数据
void Print(LinkStack LS){
LNode* p;
p=LS;
cout<<"top栈顶->栈底bottom"<<endl;
while(p!=NULL){
cout<<p->data<<" ";
p=p->next;
}
cout<<endl;
}
//保存栈顶元素的值,并改链出栈
bool PopLStack(LinkStack &LS,char &x){
if(LS->next==NULL)//如果栈为空
return false;
x=LS->data;//保存出栈数据(栈顶元素)
LS=LS->next;//改链
return true;
}
int main(){
char x;//用于接收出栈元素
LinkStack LS;//定义名为LS的链栈
IniLStack(LS);
cout<<"【1-入栈】"<<endl;
cout<<"将字母序列:a c a e d a入链栈"<<endl;
pushLStack(LS,'a');
//cout<<pushLStack(LS,'a')<<endl;
pushLStack(LS,'c');
pushLStack(LS,'a');
pushLStack(LS,'e');
pushLStack(LS,'d');
pushLStack(LS,'g');
Print(LS);
StackLength(LS);
cout<<endl<<"【2-出栈】"<<endl;
cout<<"将栈中前3个字母弹出链栈"<<endl;
for(int i=0;i<3;i++)
{
PopLStack(LS,x);
cout<<"当前出栈元素为:"<<x<<endl;
}
Print(LS);
StackLength(LS);
cout<<endl<<"【3-进进出出】"<<endl;
cout<<"b c d e f g h i j k依次入栈"<<endl;
for(int i=0;i<10;i++)
pushLStack(LS,'b'+i);
Print(LS);
cout<<"出栈两次"<<endl;
PopLStack(LS,x);
PopLStack(LS,x);
Print(LS);
StackLength(LS);
}
运行结果:
开始运行...
【1-入栈】
将字母序列:a c a e d a入链栈
top栈顶->栈底bottom
g d e a c a
链栈当前的元素个数为:6【2-出栈】
将栈中前3个字母弹出链栈
当前出栈元素为:g
当前出栈元素为:d
当前出栈元素为:e
top栈顶->栈底bottom
a c a
链栈当前的元素个数为:3【3-进进出出】
b c d e f g h i j k依次入栈
top栈顶->栈底bottom
k j i h g f e d c b a c a
出栈两次
top栈顶->栈底bottom
i h g f e d c b a c a
链栈当前的元素个数为:11运行结束。
2.带头结点的链栈
链栈的入栈:本质就是头插法插入结点,不过注意带头结点的链栈,需要初始化,创建头结点,并使得头结点的next域置空;
链栈的出栈:先保存栈顶元素值,然后从头部修改链:即LS的next域指向它的下个结点:
LS->next=LS->next->next;如图所示
相关代码
#include <cstdlib>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <typeinfo>
using namespace std;
typedef struct LNode{
int data;//数据域
struct LNode* next;//指针域:指向LNode的指针
}LNode,*LinkStack;
bool IniLStack(LinkStack &LS){
LS=(LinkStack)malloc(sizeof(LinkStack));//创建头节点
LS->next=NULL;//初始化为空表
return true;
}
//x入栈,带头结点
LinkStack headNode_pushLStack(LinkStack &LS,int x){
LNode* s;//定义指向插入节点的指针
s=(LNode*)malloc(sizeof(LNode));
s->data=x;
s->next=LS->next;//改链操作
LS->next=s;
return LS;
}
void StackLength(LinkStack &LS){
LNode* p;
int j=0;
p=LS->next;
while(p){
p=p->next;
j++;
}
cout<<"链栈当前的元素个数为:"<<j<<endl;
//return j;
}
//顺序打印当前链栈中的数据
void Print(LinkStack LS){
LNode* p;
p=LS->next;
cout<<"top栈顶->栈底bottom"<<endl;
while(p!=NULL){
cout<<p->data<<" ";
p=p->next;
}
cout<<endl;
}
//保存栈顶元素的值,并改链出栈
bool PopLStack(LinkStack &LS,int &x){
if(LS->next==NULL)//如果栈为空
return false;
x=LS->next->data;//保存出栈数据(栈顶元素)
LS->next=LS->next->next;//改链
return true;
}
int main(){
int x;//用于接收出栈元素
LinkStack LS;//定义名为LS的链栈
IniLStack(LS);
cout<<"【1-入栈】"<<endl;
cout<<"将数字1~20入链栈"<<endl;
for(int i=1;i<21;i++)
headNode_pushLStack(LS,i);
Print(LS);
StackLength(LS);
cout<<endl<<"【2-出栈】"<<endl;
cout<<"将栈中前5个数字弹出链栈"<<endl;
for(int i=0;i<5;i++)
{
PopLStack(LS,x);
cout<<"当前出栈元素为:"<<x<<endl;
}
Print(LS);
StackLength(LS);
//cout<<"将链栈中数字全部出栈,直到为空"<<endl;
cout<<endl<<"【3-进进出出】"<<endl;
cout<<"-100 8 8 8 8 8依次入栈"<<endl;
headNode_pushLStack(LS,-100);
for(int i=0;i<5;i++)
headNode_pushLStack(LS,8);
Print(LS);
cout<<"出栈两次"<<endl;
PopLStack(LS,x);
PopLStack(LS,x);
Print(LS);
StackLength(LS);
}
运行截图
开始运行...
【1-入栈】
将数字1~20入链栈
top栈顶->栈底bottom
20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
链栈当前的元素个数为:20
【2-出栈】
将栈中前5个数字弹出链栈
当前出栈元素为:20
当前出栈元素为:19
当前出栈元素为:18
当前出栈元素为:17
当前出栈元素为:16
top栈顶->栈底bottom
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
链栈当前的元素个数为:15
【3-进进出出】
-100 8 8 8 8 8依次入栈
top栈顶->栈底bottom
8 8 8 8 8 -100 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
出栈两次
top栈顶->栈底bottom
8 8 8 -100 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
链栈当前的元素个数为:19
运行结束。
三、队列的顺序存储
1.循环队列
循环队列中设计到一个操作的关键点,就是取模操作%,目的是为了整合rear和front大小为一个,所以在做rear、front自加、计算队列长度、判断队满的操作时都要记得取模
1)入队
1.判断队满:(Q.rear+1)%MAXSIZE==Q.front
2.放入数据后,Q.rear的自加也要注意取模
错误写法:Q.rear++;//原因:漏掉了取模
正确写法:Q.rear=(Q.rear+1)%MAXSIZE;
3.上图:左为队满,右为队空
bool EnQueue(SqQueue &Q,int x){
//判断队列是否已满
if((Q.rear+1)%MAXSIZE==Q.front)//注意取余,才表示一整圈
return false;
//未满,则存入元素,rear指针+1
Q.data[Q.rear]=x;
Q.rear=(Q.rear+1)%MAXSIZE;
return true;
}
2)出队
bool Dequeue(SqQueue &Q,int &x){
//判断队列是否为空
if(Q.front==Q.rear)
return false;
x=Q.data[Q.front];
Q.front=(Q.front+1)%MAXSIZE;//队头指针+1取模
return true;
}
3)队列长度
1.单向队列中:rear>=front length=rear-front
2.循环队列中:当rear<front时,length=0+rear+MAXSIZE-front
综合1、2,再加上一圈的取余,length=(0+Q.rear+MAXSIZE-Q.front)%MAXSIZE;
length=(Q.rear-Q.front+MAXSIZE)%MAXSIZE;
2.代码及运行截图
#include <cstdlib>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <typeinfo>
using namespace std;
#define MAXSIZE 100
typedef struct{
int data[MAXSIZE];
int front,rear;//定义队头、队尾
}SqQueue;
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,int x){
//判断队列是否已满
if((Q.rear+1)%MAXSIZE==Q.front)//注意取余,才表示一整圈
return false;
//未满,则存入元素,rear指针+1
Q.data[Q.rear]=x;
Q.rear=(Q.rear+1)%MAXSIZE;
return true;
}
//出队
bool Dequeue(SqQueue &Q,int &x){
//判断队列是否为空
if(Q.front==Q.rear)
return false;
x=Q.data[Q.front];
Q.front=(Q.front+1)%MAXSIZE;//队头指针+1取模
return true;
}
void QueueLength(SqQueue Q){
int length;
//length=(0+Q.rear+MAXSIZE-Q.front)%MAXSIZE;
length=(Q.rear-Q.front+MAXSIZE)%MAXSIZE;
cout<<"当前队列中元素的个数为:"<<length<<endl;
}
void Print(SqQueue &Q){
cout<<"当前队列中的元素有:"<<endl;
for(int i=Q.front;i<Q.rear;i++)
{
cout<<Q.data[i]<<" ";
}
cout<<endl;
}
int main(){
SqQueue Q;
int x;//接收出队元素
InitQueue(Q);
isEmpty(Q);
Print(Q);
for(int i=0;i<10;i++){
EnQueue(Q,i);
}
Print(Q);
QueueLength(Q);
for(int i=100;i>90;i--){
EnQueue(Q,i);
}
Print(Q);
QueueLength(Q);
int j=0;
while(j<3){
Dequeue(Q,x);
cout<<"当前出队元素为:"<<x<<endl;
j++;
}
Print(Q);
QueueLength(Q);
}
开始运行...
当前队列中的元素有:当前队列中的元素有:
0 1 2 3 4 5 6 7 8 9
当前队列中元素的个数为:10
当前队列中的元素有:
0 1 2 3 4 5 6 7 8 9 100 99 98 97 96 95 94 93 92 91
当前队列中元素的个数为:20
当前出队元素为:0
当前出队元素为:1
当前出队元素为:2
当前队列中的元素有:
3 4 5 6 7 8 9 100 99 98 97 96 95 94 93 92 91
当前队列中元素的个数为:17运行结束。