栈是一种仅允许在表尾进行插入和删除操作的线性表;
队列是只允许在一端进行插入操作、在另一端进行删除操作的线性表;
栈本质上是一个线性表,因此栈分为栈的顺序存储结构和栈的链式存储结构;但是一般都是使用栈的顺序存储结构;
使用数组表示的话:下标为0的一端作为栈底比较好;
空栈的判定条件:top = -1;
// ElemType 元素类型可以通过Typedef进行定义
typedef int ElemType;
typedef struct
{
ElemType *base; // 栈底指针变量
ElemType *top; // 栈顶指针变量
int stackSize; // 栈当前可以使用的最大容量
}sqStack;
对应的结构图:
typedef int ElemType;
typedef struct
{
ElemType data[MAXSIZE];
int top; // 用于标注栈顶的位置
int stackSize;
}
#define STACK_INIT_SIZE = 100
initStack(sqStack *s)
{
s->base = (ElemType *)malloc( STACK_INIT_SIZE * sizeof(ElemType) );
if( !s->base ){
exit(0);
}
s->top = s->base; // 最开始,栈顶就是栈底
s->stackSize = STACK_INIT_SIZE;
}
方案一:
//插入新元素e为新的栈顶元素;
Status Push(SqStack *S, SElemType e){
if(s->top == MAXSIZE -1){
return ERROR;
}
S->top++;
S->data[S->top] = e; //将新插入的元素赋值给栈顶空间
return OK;
}
方案二:
#define SATCKINCREMENT 10
Push(sqStack *s, ElemType e)
{
// 如果栈满,追加空间
if( s->top – s->base >= s->stackSize )
{
s->base = (ElemType *)realloc(s->base, (s->stackSize + STACKINCREMENT) * sizeof(ElemType));
if( !s->base )
exit(0);
s->top = s->base + s->stackSize; // 设置栈顶
s->stackSize = s->stackSize + STACKINCREMENT; // 设置栈的最大容量
}
*(s->top) = e;
s->top++;
}
栈的删除操作(Pop),叫做出栈,也称为弹栈。如同弹夹中的子弹出夹。
从栈顶取出数据,然后将栈顶的指针-1,同时栈当前的容量-1;
Pop(sqStack *s, ElemType *e)
{
if( s->top == s->base ) // 栈已空空是也
return;
*e = *--(s->top); //栈的top值先--然后指针
}
上面代码等价于:
Status Pop(SqStack *S, SElemType *e){
if(S->top == -1){
return ERROR;
}
*e = S->data[S->top]; //将要删除的元素赋值给e;
S->top--;
return OK;
}
题目:利用栈的特点,将用户输入的二进制数转换为十进制数。
清空一个栈
•就是将栈中的元素全部作废,但栈本身物理空间并不发生改变(不是销毁)。
因此我们只要将s->top的内容赋值为s->base即可,这样s->base等于s->top,也就表明这个栈是空的了。
ClearStack(sqStack *s){
s->top = s->base;
}
DestroyStack(sqStack *s){
int i, len;
len = s->stackSize;
for( i=0; i < len; i++ ){
free( s->base );
s->base++;
}
s->base = s->top = NULL;
s->stackSize = 0;
}
即是计算栈中元素的个数,只要返回s.top - s.base即可;
注意与栈的最大容量相区分,栈的最大容量是指该栈占据内存空间的大小,大小是s.stackSize
int StackLen(sqStack s)
{
return(s.top – s.base); // 初学者需要重点讲解
// 指针之间的相减即是地址的相减,得到的是指针指向的数据类型的数据量,就是个数,前提是两个指针指向的数据类型是一致的
}
当两个栈的空间需求是相反的关系时候,例如股票操作,并且只针对两个具有相同数据类型的栈的设计;
一个栈的栈底为数组下标为0处,另一个栈的栈底为数组下标为n-1处,栈满的条件为栈顶指针top1 + 1 == top2
typedef struct{
SElemType data[MAXSIZE];
int top1; //栈1的栈顶指针;
int top2;
}SqDoubleStack;
Status Push(SqDoubleStack *S, SElemType e, int stackNumber){
//stackNumber =1 or 2 表示对栈1or2进行插入操作;
if(S->top1 +1 == S->top2){
return ERROR;
}
if(stackNumber == 1){
S->top1++;
S->data[S->top1] = e;
}
if(stackNumber == 2){
S->top2--;
S->data[S->top2] = e;
}
return OK;
}
如果栈的使用过程中元素变换不可预料,有时很小,有时非常大,那么最好是用链栈;
将栈顶放在单链表的头部,同时合并掉头结点(即不在需要头结点)
基本不存在栈满的情况,除非内存没有空间;同时空链栈为:top = NULL
typedef struct StackNode{
SElemType data;
struct StackNode *next;
}StackNode, *LinkStackPtr;
typedef struct LinkStack{
LinkStackPtr top;
int count;
}LinkStack;
//插入元素e为新的栈顶元素
Status Push(LinkStack *S, SElemType e){
LinkStackPtr s = (LinkStackPtr)malloc(sizeof(StackNode));
s->data = e;
s->next = S->top;
S->top = s;
S->count++;
return OK;
}
//若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR;
Status Pop(LinkStack *S,SElemType *e){
LinkStackPtr p;
if(StackEmpty(*S)){
return ERROR;
}
*e = S->top-data;
p = S->top;
S->top = S->top->next;
free(p);
S->count--;
return OK;
}
递归含义:
直接调用自己或通过一系列的调用语句间接地调用自己的函数,称做递归函数。
每个递归定义必须至少有一个条件,满足时递归不再进行,即不再引用自身而是返回值退出;
迭代和递归的区别:
迭代使用的是循环结构,递归使用的是选择结构;迭代则不需要反复调用函数和占用额外的内存;递归使程序根据简洁,但是大量的递归调用会耗费大量的时间和内存;
递归与栈的关系说明
【1,1,2,3,5,8,13,21,34,55,89,144】
一般用法:迭代
int main(){
int i;
int a[40];
a[0] = 0;
a[1] = 1;
printf("%d",a[0]);
printf("%d",a[1]);
for(i = 2;i < 40;i++){
a[i] = a[i-1] + a[i-2];
printf("%d",a[i]);
}
return 0;
}
使用递归函数:
//斐波那契数列的递归函数
int Fbi(int i){
if(i < 2){
return i == 0?0:1;
}
return Fbi(i-1)+Fbi(i-2);// 这里Fbi就是函数自己,它在调用自己;
}
int main(){
int i;
for(int i = 0; i < 40; i++){
printf("%d", Fbi(i));
}
return 0;
}
一般处理情况:表达式遇到左括号就进栈,遇到右括号就让栈顶的左括号出栈;
该表示法是不需要括号的后缀表达法:例如:9+(3-1)*3 + 10/2
使用后缀表示法:9 3 1 - 3 * + 10 2 / +
由中缀表达式(标准的四则运算表达式)生成后缀表达式:
规则:从左到右遍历中缀表达式的每个数字和符号,若是数字就输出,即成为后缀表达式的一部分;若是符号,则判断其与栈顶符号的优先级,是右括号或者优先级不高于栈顶符号(乘除优先于加减)则栈顶元素依次出栈并且输出,并将当前符号进栈,一直到最终输出后缀表达式为止。
后缀表达式计算结果: P106 待补充
队列是只允许在一端进行插入操作,而在另一端进行删除操作的线性表;
先进先出,允许插入的一端称为队尾,允许删除的一端称为队头;
因此入队列的时间复杂度为O(1),出队列的时间复杂度为O(n);
在确定队列长度最大值的情况下,建议使用循环队列,无法估计队列的长度的时候,建议使用链队列;
首先为了解决假溢出的问题,也就是队列的开始不再以0为开始,而是通过头尾指针确认队列的开始和结束为止,这样会造成后面队列满了,而前面还空着的情况;
使用循环列表,也就是rear尾指针可以从最后一个位置满了之后在从第一个位置开始指;
计算公式:设队列最大尺寸为:QueueSize
(rear + 1) % QueueSize == front
等式成立则为 满;
计算队列长度的公式为:(rear - front + QueueSize)
循环队列的顺序存储结构代码:
typedef int QElemType;
typedef struct{
QElemType data[MAXSIZE];
int front; //头指针
int rear; //尾指针,若队列不空,指向队列尾元素的下一个位置;
}sqQueue;
循环队列的初始化代码:
// 初始化一个空队列Q
Status InitQueue(sqQueue *Q){
Q->front = 0;
Q->rear = 0;
return OK;
}
循环队列求队列长度代码:
//返回队列的当前长度,返回Q的元素个数
int QueueLength(sqQueue Q){
return (Q.rear - Q.front + MAXSIZE)%MAXSIZE;
}
循环队列的入队列操作代码:
//若队列未满,则插入元素e为Q新的队尾元素
Status EnQueue(sqQueue *Q,QElemType e){
if((Q->rear + 1)%MAXSIZE ==Q->front) { //队列满的判断
return ERROR;
}
Q->data[Q->rear] = e; //将元素e赋值给队尾
Q->rear = (Q->rear + 1)%MAXSIZE; //rear指针向后移一个位置,若到最后则转到数组头部; 默认为Q->rear + 1;
return OK;
}
循环队列的出队列操作代码:
//若队列不空,则删除Q中队头元素,用e返回其值;
Status EnQueue(sqQueue *Q,QElemType e){
if(Q->front == Q->rear){ //判断队列是否为空
return ERROR;
}
*e = Q->data[Q->front]; //将队头元素赋值给e
Q->front = (Q->front + 1)%MAXSIZE; //front指针后移一位,最后则转到头部;
return OK;
}
本质上是线性表的单链表,只不过是只能尾进头出而已;
链队列的结构代码:
typedef int QElemType;
typedef struct QNode{ //结点结构
QElemType data;
struct QNode *next;
}QNode,*QueuePtr;
typedef struct{ //队列的链表结构
QueuePtr front,rear; //队头、队尾指针;
}LinkQueue;
入队操作代码:
//插入元素e为Q的新的队尾元素
Status EnQueue(LinkQueue *Q,QElemType e){
QueuePtr s = (QueuePtr)malloc(sizeof(QNode));
if(!s){ //存储分配失败
exit(OVERFLOW);
}
s->data = e;
s->next = NULL;
Q->rear->next = s; //把拥有元素e新节点s赋值给原队尾结点的后继;
Q->rear = s; //把当前的s设置成队尾结点;
return OK;
}
出栈操作代码:
//若队列不空,删除Q的队头元素,用e返回其值,并返回OK,否则返回ERROR;
Status DeQueue(LinkQueue *Qk, QElemType *e){
QueuePtr p;
if(Q->front == Q->rear){
return ERROR;
}
p = Q->front->next; //将欲删除的队头结点暂存给p;
*e = p->data; //将欲删除的队头结点的值赋值给e;
Q->front->next = p->next; //将原队头结点后继p->next赋值给头结点后继
if(Q->rear == p){ //若队头是队尾,则删除后将rear指向头结点;
Q->rear = Q->front;
}
free(p);
return OK;
}