大话数据结构 第4章栈和队列

本文深入探讨了栈和队列这两种基本数据结构的概念、特性及其应用。详细解释了栈的后进先出(LIFO)原理,包括进栈、出栈操作,以及栈在递归中的作用。同时,阐述了队列的先进先出(FIFO)特性,介绍了队列的抽象数据类型和不同存储方式,如顺序存储和链式存储。

1.栈:栈(stack)是限定仅在表尾进行插入和删除操作的线性表
2.允许插入和删除的一端称为栈顶,另一端称为栈底,不含任何数据数据元素的称为空栈。栈又称后进先出的线性表
3.栈的插入操作,叫进栈;栈的删除操作, 叫出栈
4.。不一定最先进栈的元素就只能最后出栈,在不是所有元素都进栈的情况下,事先进去的元素也可以出栈,只要保证栈顶元素出栈即可
5.栈限定了线性表的插入和删除位置

6.栈的抽象数据类型(ADT)

ADT 栈(stack)
Data
	同线性表。元素具有相同的类型,相邻元素具有前驱和后继关系
Operation
	InitStack (*S) : 初始化操作,建立一个空栈S
	DestroyStack(*S) : 若栈存在,则销毁它
	ClearStack(*S): 将栈清空
	StackEmpty(S): 若栈为空,返回True, 否则返回false
	GetTop(S,*e):若栈存在且非空,用e返回S的栈顶元素
	Push(*S,e):若栈S存在,插入新元素e到栈S中并成为栈顶元素
	Pop(*S,*e):删除栈S中栈顶元素,并用e返回其值
	StackLength(S):返回栈S的元素个数
endADT

7.对于数组而言用下标为0的一端表示栈底,top表示栈顶元素在数组中的位置,通常把空栈的判定条件top为-1,当栈中存在一个元素时,则top=0

8.栈的结构定义

typedef int SElemType; //SElemType类型根据实际情况而定,这里假设为int
typedef struct 
{
	SElemType data[MAXSIZE];
	int top;    //用于栈顶的指针
}SqStack;

9.进栈操作push

//插入元素e为新的栈顶元素
Status Push(SqStack *S, SElemType e)
{
	if(S->top == MAXSIZE -1 ) //栈满
		return ERROR;
	S->top++;     //栈顶指针增加1
	S->data[S->top]=e;  //将新插入的元素给栈顶空间
	return OK;
}

10.出栈操作 pop

//若栈不为空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR
Status Pop(SqStack *S, SElemType *e)
{
	if(S->top==-1)
		return ERROR;
	*e=S->data[S->top];   //将要删除的栈顶元素赋值给e
	S->top--;             //栈顶指针减1
	return OK;
}

两栈共享空享空间:
使用前提:
1.相同的数据类型
2.当两个章的空间需求有相反关系时,一个栈增加,一个栈减少
3.说白了 就是用一个数组存放两个栈,数组的两端为两个栈底
在这里插入图片描述
两栈共享空间的结构代码:

//两栈共享空间结构
typedef struct
{
	SElemType data[MAXSIZE];
	int top1;  //栈1栈顶指针
	int top2;  //栈2栈顶指针
}SqDoubleStack;

两栈共享空间的push方法(插入元素)

//插入元素e为新的栈顶元素
Status Push(SqDoubleStack *S, SElemType e, int stackNumber) //stackNumber判断是栈1还是栈2
{
	if(S->top1+1==S->top2)  //栈满,不能在push新的元素
	return ERROR;
	if(stackNumber==1)  //栈1有元素进栈
		S->data[++S->top1]=e;     //若栈1则先top1+1后给数组元素赋值
	else if (stackNumber==2)   //栈2有元素进栈
		S->data[--S->top2]=e;    //若栈2则先top2-1后给数组元素赋值
	return OK;
}

两栈共享空间pop方法
//若栈不为空,则删除S的栈顶元素,stackNumber是判断栈1还是栈2

Status Pop(SqDoubleStack *S, SElemType * e, int stackNumber)
{
	if(stackNumber == 1)
	{
		if(S->top1==-1)         //说明栈1是空栈,则退出
			return ERROR;
		*e=S->data[S->top1--];   //将栈1的栈顶元素出栈
	}
	else if(stackNumber==2)
	{
		if(S->top2==MAXSIZE)   //说明栈2已经是空栈,溢出
			return ERROR;
		*e=S->data[S->top2++];  //将栈2的栈顶元素出栈
	}
	return ok;
}

4.6
1.栈的链式存储结构,简称链栈,栈顶放在头结点。对于链栈而言没有满栈的情况,对于空栈而言,对于空栈而言top=NULL
在这里插入图片描述
链栈的结构代码:

typedef struct StackNode (链表结构的定义)
{
	SElemType data;
	struct StackNode *next;
}StackNode, *LinkStackPtr;

typedef struct LinkStack  (将栈顶指针和元素总数count构成一个结构)
{
	LinkStackPtr top;
	int count;
}LinkStack;

栈的链式存储结构–进栈操作

Status Push(LinkStack *S,SElemType e)
{
	LinkStackPtr s=(LinkStackPtr) malloc (sizeof(StackNode));  //分配一个结构,并将该结构地址改为LinkStackPtr类型
	s->data=e; //将要插入的元素放到新的结构的data中
	s->next=S->top;   //将原来的栈顶的地址,放到新的结构中的next中
	S->top=s;      //将新的结点s做为栈顶指针
	S->count++;
	return OK;
}    时间复杂度为o(1)

栈的链式存储结构–出栈操作

//若栈不为空,则是删除S的栈顶元素,用e返回其值,并返回OK; 否则放回ERROR
Status Pop(LinkStack *S,SElemType *e)
{
	LinkStackPtr p;
	if(StackEmpty (*S))
		return ERROR;
	*e=S->top->data;   //将栈顶的数据放到*e中
	p=S->top;
	S->top=S->top->next;    //将栈顶指针下移一位,指向后一结点
	free(p);       //释放掉结点p
	S->count--;
	return OK;
}   时间复杂度为o(1)

4.7对于链栈和顺序栈的使用建议:
如果栈的使用过程中元素变化不可预料,有时很小,有时很大则使用链栈,如果变化在可控的范围内,建议使用顺序栈会更好。 我认为栈的顺序存储和链式存储相比于普通数组和链表在 (有一个结构存放栈顶元素指针和一个数组)(有一个结构存放栈顶元素指针和链表结构的个数)

4.8.1递归实现裴波那契数列

//裴波那契数列递归函数
int Fbi(int i)
{
	if(i<2)
		return i == 0 ? 0:1;
	return Fbi(i-1)+Fbi(i-2);   
}

int main()
{
	int i;
	for(int i=0; i<40; i++)
		printf("%d",Fbi(i));
	return 0;
}

迭代的方式计算裴波那契数列

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;
}

如图Fbi(5)的函数执行过程
在这里插入图片描述

4.8为什么要用栈实现递归:
在前行阶段,对于每一层递归,函数的局部变量,参数值以及返回地址都被压入栈中。在退回阶段,位于栈顶的局部变量,参数值和返回地址被弹出,用于返回调用层次中执行执行代码的其余部分,也就是恢复了调用的状态。

4.9.2
后缀表达式法:
从右至左遇到一个符号,计算该符号前的两个数字

中缀表达式转后缀表达式
规则:
从左至有遍历中缀表达式的每个数字和符号,若是数字就输出,即成为后缀表达式的一部分;若是符号,则判断其与栈顶符号的优先级,是有括号或优先级不高于栈顶符号(惩处优先加减)则栈顶元素一次出栈并输出,并将当前符号进栈,一直到最终输出后缀表达式位置
如:9+(3-1)*3+10/2 最终结果 : 9 3 1 -3 * + 10 2 / +
在这里插入图片描述
在这里插入图片描述
4.10
队列是只允许在一端进行插入插入操作,而在另一端进行删除操作的线性表
队列是先进先出的线性表,而栈是先进后出的线性表,允许插入的一端称为对为,允许删除的一端称为对头

4.11
队列的抽象数据类型:

ADT 队列(Queue)
Data 
	同线性表。元素具有相同的类型,相邻元素具有前驱和后继关系。
Operation
	InitQueue (*Q): 初始化操作,建立一个空队列Q
	DestroyQueue(*Q): 若队列Q存在,则销毁它
	ClearQueue(*Q):将队列清空
	QueueEmpty(Q):若队列为空,返回True,否则返回false
	GetHead(Q,*e):若队列Q存在且非空,用e返回队列Q的对头元素
	EnQueue(*Q,e):若队列Q存在,插入新元素e到队列Q中并成为队尾元素
	DeQueue(*Q,*e):删除队列Q中对头元素,并用e返回其值
	QueueLength(Q):返回队列Q的元素个数
endADT

4,12队列插入时往队尾插所以时间复杂度为o(1),将元素出队时,必须从下标为0的地方处,所以时间复杂度为o(n)
注意队列的顺序存储时队头不一定在下标为0的地方,front指向对头元素,rear指针指向队尾元素的下一个位置,同时当font=rear时,队列为空,不是只有一个元素

4.12.2循环队列的顺序存储结构
循环队列的定义:( 普通数组没有front和rear指针)
队列的这种头尾相连的顺序存储结构为循环队列。
当要插入的元素溢出,则可以向前插入元素就是rear可以放到队列前面。
在这里插入图片描述
在此时

此时要出入a6直接在rear之前插入,时间复杂度为0(1),不然a6就插入到数组外面,则溢出

判断队满的公式为 (rear+1)%QueueSize == front
计算队列的长度公式为(rear-front+QueueSize)%QueueSize

循环队列的顺序存储结构代码:

typedef int QElemType;
typedef struct 
{
	QElemType data[MAXSIZE];
	int front;   //头指针
	int rear;    //尾指针,若队列不为空,指向队列尾元素的下一个位置
}SqQueue;

循环对列初始化的代码:

Status InitQueue(SqQueue *Q)
{
	Q->front = 0;
	Q-> rear = 0;
	return OK;
}

循环队列求队列长度代码:

int QueueLength(SqQueue Q)
{
	return (Q.rear-Q.front+MAXSIZE) % MAXSIZE;
}

循环队列的入队操作代码:

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指针向后移一位,若到最后则转到数组头部
	
	return OK:
}

循环队列的出队列操作:

Status DeQueue (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;
}

4.13循环队列的链式存储结构
就是线性表的单链表,只不过他只能尾进头出而已,简称为链队列 (普通链表就是尾进尾出
front指向头结点,rear指向队尾结点,如图
空队列时,front和rear都指向头结点
![在这里在这里插入图片描述
链队列的结构为:

typedef int QElemType;  //QElemType 类型根据实际情况而定,这里假设为int

typedef struct QNode   //结点结构
{
	QElemType data;
	struct QNode *next;
}QNode, *

typedef struct     //队列的链表结构
{
	QueuePtr front,rear;   //队头,队尾指针
}LinkQueue;

链队列的插入代码:

//插入元素e为Q的新的队尾元素
Status EnQueue (LinkQueue *Q, QElemType e)
{
	QueuePtr s=(QueuePtr)malloc(sizeof(QNode))  //分配一个新结点
	if(!d)
		exit(OVERFLOW)  //存储分配失败
	s->data=e;    //把要存储的元素e放到新的结点的数据域中
	s->next=NULL; 
	Q->rear->next=s;      //Q是一个结构,里面有队头,队尾指针。而队头,队尾指针指向结点见  见图中2
	Q->rear=s;       //把当前结点s设为队尾结点,rear指向。 见图中2
}

在这里插入图片描述
链对列的出队操作
出队操作时,就是头结点的后继结点出队,将头结点的后继改为他后面的结点,若链表除头结点外只剩下一个元素时,则需将rear指向头结点,如图

在这里插入图片描述
链队列的出队操作代码

Status DeQueue (LinkQueue *Q,QElemType *e)
{
	QueuePtr p;
	if(Q->front==Q->rear)    //若队列为空
		return ERROR;
	p=Q->front->next;        //将欲删除的对头结点暂存给p,见上图1
	*e=p->data;        //将欲删除的队头结点的值赋值给e
	Q->front->next=p->next;     //将原队头结点后继p->next赋值给头结点后继,见上图2
	if(Q->rear==p)      //若链表头除头结点外只剩下一个元素时,用front撞rear,将要删除的结点撞出去,嘿嘿伪代码,就这样理解即可
		Q->rear=Q->front;
	free(p);
	return OK;
}

总之,如果能确定队列长度最大值的情况下,建议使用循环队列,无法预估队列长度时,则用链队列

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值