一、栈的定义及抽象数据类型
- 栈(stack)又称堆栈,它是运算受限的线性表,其限制是仅允许在表的一端进行插入和删除操作,不允许在其他任何位置进行插入、查找、删除等操作。表中进行插入、删除操作的一端称为栈顶(top),栈顶保存的元素称为栈顶元素。相对的,表的另一端称为栈底(bottom)。
- 基本名词概念:
- 空栈:栈中没有数据元素
- 进栈(入栈):向一个栈中插入元素
- 出栈(退栈):从一个栈中删除元素
- LIFO(Last in First out后进先出表):栈的插入和删除只能在栈顶进行,只能先进后出。
- 栈的基本操作:
stack.getSize():返回堆栈的大小,即数据元素的个数
stack.isEmpty():如果堆栈为空返回 true,否则返回 false
stack.push(e):数据元素 e 入栈
stack.pop():栈顶元素出栈
stack.peek():获取栈顶元素,但不出栈
二、栈的顺序存储实现
- 顺序存储结构:使用顺序存储结构实现的堆栈,即利用一组地址连续的存储单元依次存放堆栈中的数据元素。
- 由于栈操作都是在栈顶完成的,实现时需要一个指针top 动态指示栈顶元素在数组中的位置。top可以用栈顶元素所在下标来表示,
top = -1 表示空栈
。 - 关于堆栈最大容量问题,只需要为其分配一个基本容量(可以设置),容量不够时再倍增存储空间,所需时间复杂度均摊到每个元素时间为O(1)。
- 由于栈操作都是在栈顶完成的,实现时需要一个指针top 动态指示栈顶元素在数组中的位置。top可以用栈顶元素所在下标来表示,
- 链式存储结构:链栈即采用链表作为存储结构实现的栈。
- 单链表存储线性表:选择单链表的头部作为栈顶,此时,入栈、出栈等操作可以在Ο(1)内完成。使用带头结点的单链表时,结点的插入和删除都在头结点之后进行;使用不带头结点的单链表时,结点的插入和删除都在链表的首结点上进行。不带头结点的单链表如下图:
出栈:不管堆栈在删除栈顶元素之后,栈是否为空,出栈操作都是将top 后移。
入栈:
SLNode q = new SLNode(e,top); //结点 q 的 next 域指向 top,不管 top 是否为 Null
top = q;
三、队列
1、概念:队列(queue)简称队,它同堆栈一样,也是一种运算受限的线性表,其限制是仅允许
在表的一端进行插入,而在表的另一端进行删除。在队列中把插入数据元素的一端称为队尾
(rear),删除数据元素的一端称为队首(front)。向队尾插入元素称为进队或入队,新元素入队后成为新的队尾元素;从队列中删除元素称为离队或出队,元素出队后,其后续元素成为新的队首元素。
2、基本操作:
queue.getSize():返回队列的大小,即数据元素的个数
queue.isEmpty():如果队列为空返回 true,否则返回 false
queue.enqueue(e):数据元素 e 入队
queue.dequeue():队首元素出队
queue.peek():获取队首元素,但不出队
3、队列的顺序存储:(4-5)
- 普通队列:用普通数组实现的队列,特点是以用一个指针 last 来指示队尾,使得 enqueue 运算可在Ο(1)时间内,在执行 dequeue 时,为了删除队首元素,必须将数组中其他所有元素都向前移动
一个位置,执行 dequeue 就需要Ο(n)时间。 - 循环队列:想数组A[0… capacity-1]中的单元不是排成一行,而是围成一个圆环,即 A[0]接在 A[capacity-1]的后面称之为循环数组。用循环数组实现的队列称为循环队列。
4、循环队列:中从队首到队尾的元素按逆时针
方向存放在循环数组中一段连续的单元中。并且直接用队首指针 front 指向队首元素所在的
单元,用队尾指针 rear 指向队尾元素所在单元的后一个单元。如图所示,front = 0, rear = 3。出栈入栈时间复杂度:O(1)。
5、满队列与空队列:如图(a)所示,当删除元素时,队首追上队尾,front = rear,此时为空队列;如图(c)所示,当队尾追上队首时,front = rear,此时为满队列。如何表征满队列与空队列: - 少使用一个存储空间,当队尾指针的下一个单元就是队首指针所指单元时,则停止入队,此时队尾指针就追不上队首指针,亦不会有front = rear,
队满条件(rear + 1) % capacity = front,队空条件是 front = rear
; - 增设一个标志,以区别队列是”空“还是”满“;
综上所述,见下表:
6、链式存储:使用单链表来实现。采用带头结点的单链表结构,为了操作方便,设置两个指针,一个队首指针和一个队尾指针,如图(a)所示,队首指
针指向队首元素的前一个结点,即始终指向链表空的头结点,队尾指针指向队列当前队尾元素所在的结点。当队列为空时,队首指针与队尾指针均指向空的头结点。
四、堆栈的应用
- 进制转换:换是一种常见的数值计算问题。实现进制转换的一种简单方法是重复以下两步,直到 N 等于 0。
X = N mod d //其中 mod 为求余运算
N = N div d //其中 div 为整除运算
//十进制正整数---> 八进制
public void baseConversion(int i){
Stack s = new StackSLinked();
while (i>0){
s.push(i%8+"");
i = i/8;
}
while (!s.isEmpty()) System.out.print((String)s.pop());
}
- 括号匹配检测:假设表达式中包含三种括号:圆括号、方括号和花括号,并且它们可以任意相互嵌套。该问题可按“期待匹配消解”的思想来设计算法:算法需要一个堆栈,在读入字符的过程中,
- 如果是左括号,则直接入栈,等待相匹配的同类右括号;
- 如果是右括号,且与当前栈顶左括号匹配,则将栈顶左括号出栈,如果不匹配则属于不合法的情况。另外如果碰到一个右括号,而堆栈为空,
说明没有左括号与之匹配,属于非法情况; - 字符读完,而堆栈不为空,说明有左括号没
有得到匹配,也属于非法情况。 - 当字符读完同时堆栈为空,并且在匹配过程中没有发现不匹配的情况,说明所有的括号是匹配的。
public boolean