# 栈的定义
栈是一个只允许一端进行插入或删除操作的线性表
重要术语:栈顶、栈底、空栈
- 栈顶:允许插入和删除的一端
- 栈底:不允许插入和删除的一端
- 特点:后进先出(LIFO)
- 逻辑结构:与普通的线性表相同
- 数据的运算:插入、删除操作有区别
# 栈的基本操作
InitStack (&S):初始化栈。构造一个空栈 S,分配内存空间
DestroyStack (&S):销毁栈。销毁并释放栈 S 所占用的内存空间
Push (&S,x):进栈,若栈 S 未满,则将 x 加入使之称为新栈顶
Pop (&S,x):出栈,若栈 S 非空,则弹出栈顶元素,并用 x 返回
GetTop (&S):读栈顶元素,若栈 S 非空,则用 x 返回栈顶元素
StackEmpty (S):判断一个栈 S 是否为空。若 S 为空,则返回 true,否则返回 false
n 个不同的元素进栈,出栈元素不同的排列的个数位\frac{1}{n}C_{2n}^
# 顺序栈
顺序栈的定义:
#define MaxSize 10 //定义栈中元素的最大个数
typedef struct{
ElemType data[MaxSize]; //静态数组存放栈元素
int top; //栈顶指针
}SqStack;
顺序存储,给各个数据元素分配连续的存储空间,大小为 MaxSize*sizeof (ElemType)
栈的基本操作:
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.top=S.top+1; //指针+1
S.data[S.top]=x; //新元素入栈
//或者直接S.data[++S.top]=x;
return true;
}
bool Pop(SqStack &S,ElemType &x){ //出栈操作
if(S.top==-1) //如果栈为空
return false;
x=S.data[S.top];
S.top--;
//等价于x=S.data[S.top--];
return true;
}
//出栈操作数据还在内存当中,只是逻辑上已经被移除了
bool GetTop(SqStack &S,ElemType &x){
if(S.top==-1)
return false;
x=S.data[S.top];
return true;
}
顺序栈的缺点:栈的大小不可变
# 共享栈
#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;
}
栈满的条件:top0+1==top1
# 链栈
链栈定义:
typedef struct Linknode{
ElemType data; //数据域
struct Linknode *next; //指针域
} *LiStack;
至于基本操作,请参考带头结点的链表和不带头结点的链表的增加删除操作。
链栈仅仅只是所有的插入都是头插法,删除都是删除头结点的特殊情况,比起单独链表还好写
# 队列的定义
队列是只允许在一端进行插入,在另一端删除的线性表
特点:先进入队列的元素先出队
重要术语:队头、队尾、空队列
- 队头:允许删除的一端
- 队尾:允许插入的一端
队列特点:先进先出 (FIFO)
# 队列的基本操作
InitQueue (&Q):初始化队列,构造一个空队列 Q
DestroyQueue (&Q):销毁队列,销毁并释放队列 Q 所占用的内存空间
EnQueue (&Q,x):入队,若队列 Q 未满,将 x 加入,使之称为新的队尾
DeQueue (&Q,x):出队,若队列 Q 非空,删除队头元素,并用 x 返回
GetHead (Q,&x):获取队头元素,若队列非空,则将队头赋值给 x
QueueEmpty (Q):判队列空,若队列 Q 位空,返回 true,否则返回 false
# 队列的顺序实现
#define MaxSize 10
typedef struct{
ElemType data[MaxSize]; //用静态数组存放队列元素
int front,rear; //队头指针和队尾指针
} SqQueue;
void InitQueue(SqQueue &Q){ //初始化
//初始化时,队头、队尾指针指向0
Q.rear=Q.front=0;
}
bool QueueEmpty(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;
return true;
}
bool DeQueue(SqQueue &Q,ElemType x){ //出队(删除一个元素,并用x返回)
if(Q.rear==Q.front)
return false;
x=Q.data[Q.front];
Q.front=(Q.front+1)%MaxSize;
return true;
}
bool GetHead(SqQueue Q,ElemType &x){ //获取队头
if(Q.rear==Q.front)
return false;
x=Q.data[Q.front];
return true;
}
队列元素个数:(rear+MaxSize-front)% MaxSize
判断队列已满 / 为空:
常规方法:(Q.rear+1)% MaxSize==Q.front,但是会浪费一个空间
增加 size,用 size 来记录队列元素个数以判断是否为满 \ 空
增加 tag,tag 表示最近进行的是删除 / 插入,初始为 0,每次删除操作成功时,都令 tag=0,每次插入操作成功时,都令 tag=1,只有删除操作,才会导致队列为空,同样只有插入操作,才会导致队列为满,因此:
- 队列为满:Q.rear==Q.front && tag == 1
- 队列为空:Q.rear==Q.front && tag == 0
注意:有的题目队尾指针可能指向的是队尾元素,上面讨论结果都是队尾指针指向队尾元素的下一个元素
# 队列链式实现
typedef struct LinkNode{ //链式队列节点
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 InitQueue(LinkQueue &Q){ //不带头结点
Q.front=NULL;
Q.rear=NULL;
}
bool IsEmpty(LinkQueue &Q){ //不带头结点
if(Q.front==NULL)
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; //新节点插入到rear之前
Q.rear=s; //修改表尾指针
}
void Enqueue(LinkQueue &Q,ElemType x){ //新元素入队(不带头结点)
LinkNode *s=(LinkNode*)malloc(sizeof(LinkNode));
s->data=x;
s->next=NULL;
if(Q.front==NULL){
Q.front=s;
Q.rear=s;
} else{
Q.rear->next=s; //新节点插入到rear之前
Q.rear=s; //修改表尾指针
}
}
bool Dequeue(LinkQueue &Q,ElemType &x){ //队头出队(带头结点)
if(Q.front==Q.rear)
return false; //空队
LinkNode *p=Q.front->next;
x=p->data; //用变量x返回队头元素
Q.front->next=p->next; //修改头结点的next指针
if(Q.rear==p) //此次是最后一个节点
Q.rear=Q.front; //修改rear指针
free(p); //释放空间
return true;
}
bool Dequeue(LinkQueue &Q,ElemType &x){ //队头出队(不带头结点)
if(Q.front==NULL)
return false; //空队
LinkNode *p=Q.front->next;
x=p->data; //用变量x返回队头元素
Q.front=p->next; //修改头结点的next指针
if(Q.rear==p){ //此次是最后一个节点
Q.rear=NULL;
Q.front=NULL;
}
free(p); //释放空间
return true;
}
# 双端队列
队列:只允许从一端插入、另一端删除的线性表
双端队列:只允许从两端插入、两端删除的线性表
输入受限的双端队列:只允许从一端插入、两端删除的线性表
输出受限的双端队列:只允许从两端插入,一端删除的线性表
考点:判断输出序列的合法性
输入受限的双端队列:栈中合法的序列,双端队列中一定也合法
输出受限的双端队列:栈中合法的序列,双端队列中一定也合法