ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

大话数据结构笔记——第四章:栈与队列

2019-09-14 20:04:55  阅读:152  来源: 互联网

标签:return 队列 大话 元素 栈顶 front 数据结构 rear


栈与队列


栈是限定仅在尾表进行插入和删除操作的线性表。
队列是只允许在一端进行插入操作、而在另一端进行删除操作的线性表。

栈的定义

定义:栈是限定仅在尾表进行插入和删除操作的线性表。允许插入和删除的一端称为栈顶(top),另一端称为栈底(bottom),不含任何数据元素的栈称为空栈。栈又称为后进先出(Last In First Out)的线性表,简称LIFO结构。
栈是一种特殊的线性表,只能在线性表的表尾进行插入和删除操作,表尾指的就是栈顶。
栈的插入操作,叫做进栈,压栈,入栈。栈的删除操作,叫做出栈,弹栈。

进栈出栈的变化形式

最先进栈的元素不一定是只能最后出栈的元素,栈对线性表中元素的进出时间没有限制。三个元素有五种出栈次序。

栈的抽象数据类型

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

栈的顺序存储及实现

结构定义:

/*栈的结构定义*/
typedef int SElemType;
typedef struct 
{
	SElemType data[MAXSIZE];
	int top; //用于栈顶指针,空栈是为-1
}SqStack;

进栈操作:

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

出栈操作:

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

两栈共享空间

栈顶指针在数组两端,向中间靠拢。当栈1空时,也就是top1= -1,当栈2空时,即top2=n。当top1+1=top2时栈满
两站共享空间结构:

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

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

/*若栈不空,则删除S栈顶元素,用e返回其值,返回OK,否则返回ERROR*/
Status Pop(SqDoubleStack *S,SElemType *e,int stackNumber)
{
	if (stackNumber == 1)
	{
		if (S->top1 == -1)
		{
			return ERROR; //说明栈1已经是空栈,溢出
		}
		*e = S->data[S->top1--]; //将栈一的栈顶元素出栈,并使top1-1
	}
	else if (stackNumber == 2)
	{
		if (S->top2==MAXSIZE)
		{
			return ERROR; //说明栈2已经是空栈,溢出
		}
		*e = S->data[S->top2++]; //将栈二的栈顶元素出栈,并使top2+1
	}
	return OK;
}

栈的链式存储及其实现

单链表有头指针,栈顶指针也是必须的,所以可以让它们俩合二为一,所以好的方法是把栈顶放在单链表的头部。因此也不需要头结点了。
链栈的结构代码:

typedef struct StackNode
{
	SElemType data;
	struct StackNode *next;
}StackNode,*LinkStackPtr;

typedef struct LinkStack
{
	LinkStackPtr top;
	int count;
}LinkStack;

进栈操作:

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

出栈操作:

Status Pop(LinkStack *S,SElemType *e)
{
	LinkStackPtr p;
	if (StackEmpty(*S))
	{
		return ERROR;
	}
	*e = S->top->data;
	p=S->top; //将栈顶结点赋值给P
	S->top=S->top->next; //使得栈顶指针下移一位,指向后一结点
	free(p);
	S->count--;
	return OK;
}

如果栈的使用过程中元素的变化不可预料,最好使用链栈,反而如果它的变化在可控范围内,建议使用顺序栈。时间复杂度都是O(1)。

栈的作用

栈的引入简化了程序设计的问题,划分了不同关注层次,使得思考范围缩小,更加聚焦于我们要解决问题的核心。

递归

把一个直接调用自己或通过一系列的调用语句间接调用自己的函数,称为递归函数。

/*斐波那契的递归函数*/
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;
}

四则运算表达式求值

逆波兰表示法,不需要括号的后缀表达式法,所有的符号都是在要运算数字的后面出现,利用栈可以对后缀表达式进行处理。
规则:从左到右遍历表达式的每个数字和符号,遇到数字就进栈,遇到符号,就将处于栈顶的两个数字出栈,进行运算,运算结果进栈,一直到最终获得结果。
中缀表达式:平时所用的标准四则运算表达式。
中缀表达式转后缀表达式:从左到右遍历中缀表达式的每个数字和符号,遇到数字就输出,即成为后缀表达式的一部分;若是符号,则判断与栈顶符号的优先级,是右括号或优先级不高于栈顶符号则栈顶元素依次出栈并输出,并将当前符号进栈,一直到最终输出后缀表达式为止。
计算机处理四则运算表达式

  1. 将中缀表达式转化为后缀表达式(栈用来进出运算的符号)。
  2. 将后缀表达式进行运算得出结果(栈用来进出运算的数字)。

队列

队列是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。队列是一种先进先出(First In First Out)的线性表,简称FIFO。允许插入的一端称为队尾,允许删除的一端称为队头。

队列的抽象数据类型

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

循环队列

为了避免当只有一个元素时,对头和队尾重合时处理变得麻烦,所以引入两个指针,front指向队头元素,rear指向队尾元素的下一个位置。把队列的这种头尾相接的顺序存储结构称为循环队列
通用的计算队列长度公式为
(rear - front +QueueSize) %QueueSize

typedef int QElemType; //根据实际清空确定时int还是其他类型
/*循环队列的顺序存储结构*/
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 QElemType(SqQueue)
{
	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指针向后移一位
	//若到最后则转到数组的头部
	return OK;
}

/*若队列不空,则删除Q中对头元素,用e返回其值*/
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;
}

队列的链式存储结构

队列的链式存储结构,其实就是线性表的单链表,只不过它只能尾进头出,简称为链队列。队头指针(front)指向链队列的头结点,队尾指针(rear)指向终端结点。空对列时front和rear都指向头结点。
链队列的结构:

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设置为队尾结点,rear指向s
	return OK;
}

出队操作:

/*若队列不空,删除Q的对头元素,用e返回其值,并返回OK,否则返回ERROR*/
Status DeQueue(LinkQueue *Q,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;
}

总结

主要讲解了栈和队列,它们均可以用线性表的顺序存储结构来实现,但都存在着顺序存储的一些弊端,因此它们各自使用技巧来解决这个问题。
对于栈来说,如果是两个相同数据类型的栈,则可以用数组的两端作栈底的方法来让两个栈共享数据,这就可以最大化地利用数组的空间。
对于队列来说,为了避免数组插入和删除时需要移动数据,于是就引入循环队列,使得对头和队尾可以在数组中循环变化。使得本来插入和删除是O(n)的时间复杂度变成了O(1)。

标签:return,队列,大话,元素,栈顶,front,数据结构,rear
来源: https://blog.csdn.net/weixin_43155076/article/details/100821463

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有