大话数据结构---(三)栈与队列

1.栈

栈的定义:栈是限定仅在表尾进行插入和删除操作的线性表,是线性表内的一个小分支。我们把允许插入和删除的一端称为栈顶(top),另一端称为栈底(buttom),不含任何数据元素的栈称为空栈。栈又称为后进先出的线性表,简称LIFO结构。在日常生活中,栈的魅力无处不在,比如Ctrl+Z回退快捷键,它会马上返回到你上一步操作,而不是随机的一步历史操作。
栈的插入操作,叫做进栈,也称压栈、入栈。类似于子弹弹入弹夹,如图所示。
在这里插入图片描述栈的删除操作,叫做出栈,也有的叫做弹栈。如同弹夹中的子弹出夹,如图所示。
在这里插入图片描述这里有个问题,最先进栈的元素,是不是就只能是最后出栈呢?不是说先进后出吗,最先进的当然最后出?
答:不一定,要看什么情况。虽然栈对线性表的插入和删除的位置进行了限制,但没有对元素进出的时间进行限制,也就是说,在不是所有元素都进栈的情况下,事先进去的元素也可以出栈,只要保证是栈顶元素出栈就可以了。举个栗子,现在有排好序的1,2,3三个数先后入栈,1入栈后可以马上出栈,2入栈时既是栈顶又是栈底,依次类推。
栈的顺序存储结构及实现:若存储栈的长度为StackSize,则栈顶位置top必须小于StackSize。当栈存在一个元素时,top等于0,因此通常把空栈的判定条件定为top等于-1。
栈的结构定义:

typedef int SElemType;
typedef struct{
SElemType data[MAXSIZE];
int top;//用于栈顶指针
}SqStack;

栈的顺序存储结构—进栈操作。

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

栈的顺序存储结构—出栈操作。

Status Pop(SqStack *S,SElemType *e){
  if(S->top==-1)
     return ERROR;
     *e=S->data[S->top];
     S->top--;//栈顶指针减一
}

栈的链式存储结构及实现:对于链栈来说,基本不存在栈满的情况,除非内存已经没有可以使用的空间。对于空栈来说,链表原定义是头指针指向空,通常对于链栈来说,是不需要头结点的,那么链栈的空其实就是top=NULL的时候。
链栈的结构代码如下:

typedef struct StackNode{
SElemType data;
struct StackNode *next;
}StackNode,*LinkStackPtr;
typedef struct Stack{
LinkStackPtr top;
int count;
}*LinkStack;

链表的操作都和单链表类似,只是在插入和删除上特殊些。
入栈:

//入栈,头插法
void Push(LinkStack &L, char e) {
	LinkStackPtr new_node = (LinkStackPtr)malloc(sizeof(LinkStackPtr));
	new_node->character = e;
	new_node->next = L->top;
	L->top = new_node;
	L->count++;
}

出栈:

//出栈
bool Pop(LinkStack &L, char &e) {
	LinkStackPtr p;
	if (L->count > 0) {
		p = L->top;
		e = p->character;
		L->top = L->top->next;
		L->count--;
		free(p);//释放内存
	}
	else {
		cout << "栈已空!!!" << endl;
		return false;
	}
}

2.队列

队列的定义:是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。队列是一种先进先出的线性表,简称FIFO。允许插入的一端称为队尾,允许删除的一端称为队头。队列在日常生活中用的也挺频繁,比如用键盘进行各种字母或数字的输入,到显示器上入记事本软件上的输出,加入你本来和女友聊天,想输入的是god,而发出去的却是dog。额。。。准备跪键盘吧
在这里插入图片描述其实讲到这,不知道大家有没有疑问,明明单纯链表的功能更为强大,我们为什么还要通过限制一些操作,来区分不同的数据结构。其实这和我们明明有两只脚可以走路,干嘛还要乘汽车、火车、飞机一样。理论上,陆地上的任何地方你都是可以通过双脚走到的,可那需要多少时间和精力呢?我们更关注的是到达而不是如何去的过程。
循环队列:我们把队列的这种头尾相接的顺序存储结构称为循环队列。下面演示一个例子,如图一,是先后入队长为5,出队a1,a2后的图。
在这里插入图片描述接着a6入队,将它放置于下标为0处,rear指针指向下标为1处。若再入队a7,则rear指针就与front指针重合,同时指向下标为2的位置,如图2所示。
在这里插入图片描述
此时问题来了,空队列时,front=rear,现在当队列满时,也是front等于rear,那么如何判断此时的队列究竟是空还是满呢?

  • 方法一:很容易想到,只要新增一个变量flag,当front=rear,且flag=0时队列空,当front=rear,且flag=1时队列满。(自我感觉这种方法会简单好多)。
  • 方法二:当队列空时,条件就是front==rear,当队列满时,我们修改其条件,保留一个元素空间。也就是说,队列满时,数组中还有一个空闲单元。如图三所示,我们就认为此队列已经满了。
    在这里插入图片描述在这里插入图片描述通用的计算队列长度公式为:(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;
}

队列的链式存储结构,其实就是线性表的单链表,只不过它只能尾进头出而已,我们把它简称为链队列,操作和链表类似,这里就不赘述了,之后会通过一些栈和队列的实例来加深理解。
引用他人的一句话:认识,是一个又一个小小的队列重现。春夏秋冬轮回年年,早中晚夜循环天。变化的是时间,不变的是你对未来执着的信念。

发布了7 篇原创文章 · 获赞 7 · 访问量 349

猜你喜欢

转载自blog.csdn.net/Achenming1314/article/details/105004224
今日推荐