您的位置:首页 > 理论基础 > 数据结构算法

数据结构学习笔记——栈和队列

2009-09-19 10:57 211 查看
栈和队列

栈(先进后出线性表)

栈是一种只能在一端进行插入或删除操作的线性表。表中允许进行插入、删除操作的一端称为栈顶。

栈顶的当前位置是动态的,栈顶的当前位置由一个称为栈顶指针的位置指示器指示。表的另一端称为栈底。

当栈中没有数据元素时,称为空栈。

栈的插入操作通常称为进栈或入栈,栈的删除操作通常称为退栈或出栈。

栈的顺序存储结构称为顺序栈,通常利用一个一维数组实现和一个记录格顶位置的变量组成。

栈的链接实现称为栈链,通常利有一个单链表实现,其中单链表可带一个头节点,也可不带一个头结点。

--------------------------------------------------------------------------------------------------------------

例 假设表达式中允许包含三种括号:圆括号、方括号和大括号。编写一个算法判断表达式中的括号是否正确配对。

解: 设置一个括号栈,扫描表达式:遇到左括号(包括(、[和{)时进栈,遇到右括号时,若栈是相匹配的左括号,则出栈,否则,返回0。

若表达式扫描结束,栈为空,返回1表示括号正确匹配,否则返回0。

----------------------

栈在实际中有广泛的应用

栈的应用举例:

一、 表达式求值

概念:后缀表达式

所谓后缀表达式,就是将运算符放在操作数后面,如1+2*3(中缀表达式)的后缀表达为123*+,在后缀表达式中已经考虑了运算符的优先级,没有括号,只有操作数和运算符。

在程序语言中,运算符位于两个操作数中间的表达式称为中缀表达式。例如:

1+2*3

就是一个中缀表达式,中缀表达式是最常用的一种表达式方式。对中缀表达式的运算一般遵循“先乘除,后加减,从左到右计算,先括号内,后括号外”的规则。因此,中缀表达式不仅要依赖运算符优先级,而且还要处理括号。

所谓后缀表达式,就是运算符在操作数的后面,如1+2*3的后缀表达式为123*+。在后缀表达式中已考虑了运算符的优先级,没有括号,只有操作数和运算符。

对后缀表达式求值过程是:从左到右读入后缀表达式,若读入的是一个操作数,就将它入数值栈,若读入的是一个运算符op,就从数值栈中连续出栈两个元素(两个操作数),假设为x和y,计算x op y之值,并将计算结果入数值栈;对整个后缀表达式读入结束时,栈顶元素就是计算结果。

算术表达式求值过程是:先将算术表达式转换成后缀表达式,然后对该后缀表达式求值。

二、 求解迷宫问题(穷举求解) 亦采用顺序栈(当然亦可用栈链)

特点:不一定是最优解

求迷宫问题就是求出从入口到出口的路径。在求解时,通常用的是“穷举求解”的方法,即从入口出发,顺某一方向向前试探,若能走通,则继续往前走;否则沿原路退回,换一个方向再继续试探,直至所有可能的通路都试探完为止。

三、 进制转换(运用短除法)

队列(后进先出线性表)

队列简称队,它也是一种运算受限的线性表,其限制仅允许在表的一端进行插入,而在表的另一端进行删除。

我们把进行插入的一端称做队尾(rear),进行删除的一端称做队首(front)。

向队列中插入新元素称为进队或入队,新元素进队后就成为新的队尾元素;从队列中删除元素称为出队或离队,元素出队后,其后继元素就成为队首元素。

队列的顺序存储结构

为了能够充分地使用数组中的存储空间,把数组的前端和后端连接起来,形成一个环形的顺序表,即把存储队列元素的表从逻辑上看成一个环,称为环形队列。

怎样区分这两者之间的差别呢?在入队时少用一个数据元素空间,以队尾指针加1等于

队首指针判断队满,即队满条件为:
(q->rear+1) % MaxSize==q->front
队空条件仍为:
q->rear==q->front
入队运算:
q->rear = (q->rear+1) % MaxSize

出队运算:
q->front = (q-> front +1) % MaxSize
例 什么是队列的上溢现象和假溢出现象?解决它们有哪些方法?

答: 在队列的顺序存储结构中,设头指针为front,队尾指针rear,队的容量(存储空间的大小)为MaxSize。当有元素加入到队列时,若 rear=MaxSize(初始时rear=0)则发生队列的上溢现象,该元素不能加入队列。

特别要注意的是队列的假溢出现象:队列中还有剩余空间但元素却不能进入队列,造成这种现象的原因是由于队列的操作方法所致。

解决队列上溢的方法有以下几种:

(1) 建立一个足够大的存储空间,但这样做会造成空间的使用效率降低。

(2) 当出现假溢出时可采用以下几种方法:

①采用平移元素的方法:每当队列中加入一个元素时,队列中已有的元素向队头移动一个位置(当然要有空闲的空间可供移动);

②每当删除一个队头元素时,则依次移动队中的元素,始终使front指针指向队列中的第一个位置;

③采用环形队列方式:把队列看成一个首尾相接的环形队列,在环形队列上进行插入或删除运算时仍然遵循“先进先出”的原则。

对于顺序队列来说,如果知道队首元素的位置和队列中元素个数,则队尾元素所在位置显然是可以计算的。也就是说,可以用队列中元素个数代替队尾指针。编写出这种循环顺序队列的初始化、入队、出队和判空算法。
当已知队首元素的位置front和队列中元素个数count后:

队空的条件为:count==0

队满的条件为:count==MaxSize

计算队尾位置rear:

rear=(front+count+MaxSize)%MaxSize

对应的结构如下:
typedef struct
{ ElemType data[MaxSize];
int front; /*队首指针*/
int count; /*队列中元素个数*/
} QuType; /*队列类型*/

例:如果允许在环形队列的两端都可以进行插入和删除工作(这样的队列称为双端对列),若采用前面定义的SqQueue队列类型,编写“从队尾删除”和从队头插入的算法。

队列的链式存储结构

队链,采用一个单链表结构,有一个指向表头和表尾的指针组成

特点:无内存限制。

扩展:想想既然是一个单链表结构,可否用静态链表代替呢?(不行,因为静态链表有空间大小限制,因此不能实现队列的链式存储结构,可以将其构成一个循环结构,但这又没有循环队列,算法复杂度低)。

链队亦可带一个头结点,也可不带一个头结点(一般情况下是带一个头节点的)

队列的应用:

一、 运用队列求解约瑟夫环问题(报到出列的出列,未报到出列的出列并加到队列最右边继续)[整个过程,以出列操作来驱动循环]

二、 求解迷宫问题(得出的是最优解[最短路径])亦采用顺序队列(当然亦可用队链)

使用一个队列Qu记录走过的方块,该队列的结构如下:
struct
{ int i,j; /*方块的位置*/
int pre; /*本路径中上一方块在Qu中的下标*/
} Qu[MaxSize];
int front=-1,rear=-1; /*队首指针和队尾指针*/

这里使用的队列Qu不是环形队列(因为要利用出队的元素找路径),因此在出队时,不会将出队元素真正从队列中删除,因为要利用它输出路径。

搜索从(1,1)到(M-2,N-2)路径

(1) 首先将(1,1)入队;

(2) 在队列Qu不为空时循环:出队一次(由于不是环形队列,该出队元素仍在队列中),称该出队的方块为当前方块,front为该方块在Qu中的下标。

①如果当前方块是出口,则输出路径并结束。

②否则,按顺时针方向找出当前方块的四个方位中可走的相邻方块(对应的mg数组值为0),将这些可走的相邻方块均插入到队列Qu中,其pre设置为本搜索路径中上一方块在Qu中的下标值,也就是当前方块的front值,并将相邻方块对应的mg数组值置为-1,以避免回过来重复搜索。

(3) 若队列为空仍未找到出口,即不存在路径。

[与栈的比较]

栈中用方向来控制查找路径,而队列是固定的控制查找路径

栈中从下标0,打印到尾,是找到的路径,而队列中,从尾通过pre回溯到头并重将设置pre一个特殊置,再从队列头到队列尾搜索出特殊值所在的路径。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: