栈和队列
通常称,栈和队列是限定插入和删除只能在表的“端点”进行的线性表。
栈和队列是两种操作受限的线性表,是两种常用的数据类型。
4.1 栈
栈的操作遵循后进先出原则LIFO(Last In First Out)或者先进后出的原则FILO
4.1.1 栈抽象数据类型
public interface SStack<T>
{ //栈接口,栈抽象数据类型
boolean isEmpty(); //判断是否空栈
void push(T x); //元素x入栈
T pop(); //出栈,返回当前栈顶元素
T get(); //取栈顶元素,未出栈
}
栈的特性
1.栈属于加了限制条件的线性结构;
2.栈是后进先出的线性表;
3.进栈和出栈只能从栈的一个端点进行;
4.栈中的元素个数可以是0,此时是空栈;
5.栈的元素的个数是可以变化的,可以是多个,但不能是无穷多个;
6.每个栈中的元素的类型相同.
4.1.2 顺序栈
1、顺序存储结构——顺序栈(还是用数组来存储)
利用一组地址连续的存储单元依次存放自栈底到
栈顶的数据元素,同时设置top指示栈顶元素的位置(下标)。
public class SeqStack<T> implements SStack<T>
{
private Object element[]; //存储栈数据元素的数组
private int top; //栈顶元素下标
public SeqStack(int size); //构造容量为size的空栈
public SeqStack(); //构造默认容量的空栈
public boolean isEmpty(); //判断栈是否为空
public void push(T x); //元素x入栈
public T pop(); //出栈,返回栈顶元素
public T get(); //取栈顶元素,不出栈
}
入栈操作push(T x)
a.[判断顺序栈是否为满,若满则扩充栈容量]
b.[若栈不满,则将新元素x 压入栈顶,并修正栈顶]
this.top ++ ; //新的栈顶
this.element[ this.top ] = x ; // 存放新元素
出栈操作pop():
return this.top==-1 ? null:(T) this.element[this.top--];
//返回栈顶元素,若栈空则返回null
取栈顶元素get():
return this.top==-1 ? null:(T) this.element[this.top];
//取栈顶元素,未出栈,若栈空则返回null
4.1.3 链式栈
栈的链式存储结构称为链栈,是运算受限的单链表,其插入和删除操作仅限制在链表的表头位置上进行,故链栈没有必要象单链表一样附加头结点,栈顶指针即为链表的头指针。
public class LinkedStack<T> implements SStack<T>
{
private Node<T> top; //栈顶结点
public LinkedStack(); //构造空栈
public boolean isEmpty(); //判断栈是否为空
public void push(T x); //元素x入栈
public T pop(); //出栈,返回栈顶元素
public T get(); //取栈顶元素,不出栈
}
top == null // 空栈
//入栈操作push(T x):
if (x!=null)
this.top = new Node(x , this.top);
//出栈操作pop():
T temp = this.top.data;
this.top = this.top.next;
return temp;
//取栈顶元素get():
return this.top==null1 ? null: this.top.data;
4.1.4 栈的应用
栈是嵌套调用机制的实现基础
在高级语言编制的程序中,调用函数和被调用函数之间的链接和信息交换也是由编译程序通过栈来实施的。
多个函数嵌套调用的规则是:
后调用先返回 !
此时的内存管理实行“栈式管理”
1.栈是嵌套调用机制的实现基础
2.使用栈以非递归方式实现递归算法
例4.1 判断表达式中圆括号是否匹配
Java程序中分隔符有圆、方、花括号和注释符“/”和“/”。
public class Bracket
{
//检查infix表达式中的圆括号是否匹配,若匹配,返回空串;否则返回错误信息
public static String isMatched(String infix)
{
SeqStack<String> stack = new SeqStack<String>(infix.length());
//声明接口对象stack,引用实现Stack<T>接口的顺序栈类的实例,创建空栈
// Stack<String> stack = new LinkedStack<String>();
for (int i=0; i<infix.length(); i++)
{
char ch=infix.charAt(i);
switch(ch)
{
case '(': stack.push(ch+""); //左括号入栈
System.out.println(stack.toString());
break;
case ')': if (stack.isEmpty() || !stack.pop().equals("(")) //遇见右括号时,出栈
return "期望("; //检查出栈字符是否为左括号
}
}
return (stack.isEmpty()) ? "" : "期望)"; //返回空串表示没有错误
}
public static void main(String args[])
{
String infix="((1+2)*3+4))(";
System.out.println(infix+" ,编译错误:"+Bracket.isMatched(infix));
}
}
算法的设计思想:
1)凡出现左括弧,则进栈;
2)凡出现右括弧,首先检查栈是否空
若栈空,则表明该“右括弧”多余,
否则和栈顶元素比较,
若相匹配,则“左括弧出栈” ,
否则表明不匹配。
3)表达式检验结束时,
若栈空,则表明表达式中匹配正确,
否则表明“左括弧”有余。
例4.2 使用栈计算表达式的值。
中缀表达式1+2*(3-4)+5
中缀:运算符写在两个操作数中间的表达式
前缀、后缀:将运算符写在两个操作数之前、后的表达式
中缀式运算的次序不确定;
后缀式的运算规则为: 运算符在式中出现的顺序恰为表达式的运算顺序; 每个运算符和在它之前出现 且紧靠它的两个操作数构成一个最小表达式。
为此我们先引进一个运算符的“优先级”的概念。给每个运算符赋以一个优先级的数值,如下表所示:
(1)将中缀表达式转换为后缀表达式
从原表达式求得后缀式的操作步骤为:
- 设立一个运算符栈,设置一个后缀表达式字符串
- 若当前字符是操作数,则直接发送给后缀表达式字符串
- 若当前字符为运算符时,
若运算符栈为空
或运算符栈非空且
当前运算符 优先级 高于 栈顶运算符,
则 ①当前运算符 进栈;
若
栈顶运算符 优先级 高于(等于) 当前运算符,
则①栈顶运算符出栈,发送给后缀式,与下一个进行比较
②当前运算符 入栈。
4)若当前字符是**左括号“(”**时,进运算符栈;
- 若当前字符**是右括号“)”**时,反复 将 栈顶符号弹出,并送往后缀表达式, 直到栈顶符号是左括号为止,再将左括号出栈并丢弃。左右括号都丢弃
- 若读取完毕,则将栈中剩余的所有运算符弹出并送往后缀表达式。
(2)后缀表达式求值
后缀式求值的操作步骤为:
1 2 3 4 - * + 5 +
4
-
设立一个操作数栈;
-
从左到右依次读入后缀表达式中的字符:
①若当前字符是操作数,则压入操作数栈。
②若当前字符是运算符,则从栈顶弹出两个操作数参与运算,并将运算结果压入操作数栈内。
4.2 队列
4.2.1 队列抽象数据类型
(1) 队列是只允许在表的一端进行插入,而在表
的另一端进行删除操作的一种特殊线性表。
允许插入的一端称为“队尾”,允许删除的一
端称为“队首”。
(2)队列是“先进先出”的线性表(FIFO)或
“后进后出”的线性表(LILO)
//队列(queue)是一种特殊的线性表,其插入和删除操作分别在线性表的两端进行。
public interface QQueue<T> //队列接口
{
boolean isEmpty(); //判断队列是否为空
void enqueue(T x); //入队
T dequeue(); //出队
}
4.2.2 顺序队列
顺序存储结构
利用一组地址连续的存储单元依次存放自队头到
队尾的数据元素。
头指针front——指示队头元素位置
尾指针rear——指示队尾元素位置
初始空队列:front = rear = -1
第一个元素入队:front = rear = 0
入队:rear++
出队:front++
1.顺序队列,假溢出
假溢出现象:数组前部空出存储单元,但rear下标越界存储单元没有重复使用
循环队列:逻辑上首尾相连的环状空间
rear:下一个入队元素位置
入队:rear =(rear+1)% length
出队:front =(front+1)% length
2.顺序循环队列
front=(front+1) % length;
rear=(rear+1) % length;
3.顺序循环队列类
public class SeqQueue<T> implements QQueue<T>
{
private Object value[];
private int front,rear; //队列头尾下标
}
循环队列
front=(front+1) % length;
rear=(rear+1) % length;
3.顺序循环队列类
public class SeqQueue<T> implements QQueue<T>
{
private Object value[];
private int front,rear; //队列头尾下标
}
4.2.3 链式队列
链式存储结构——链队列
不带头结点的单链表
front:指向队头结点
rear:指向队尾结点
初始空队列:fron = rear = null
队列空:front = null && rear = null
public class LinkedQueue<T> implements QQueue<T> //链式队列类
{ private Node<T> front, rear;
//入队列enqueue(T x)
if(x == null)
return;
Node<T> q = new Node<T>(x , null);
if(this.front == null)
this.front = q;
else this.rear.next=q;
this.rear=q;
//出队列dequeue()
if(isEmpty())
return null;
T temp = this.front.data;
this.front = this.front.next;
if(this.front == null)
this.rear = null;
return temp;
}
4.2.4 队列的应用
【例 】编程实现求解的素数环问题
【问题描述】将1~n的 n个自然数排列成环形,使得每相邻两数之和为素数,从而构成一个素数环。
⑴ 先引入顺序表类Sqlist和链队列类LinkQueue,再创建Sqlist类的一个对象L作为顺序表,用于存放素数环的数据元素;创建LinkQueue类的一个对象Q,作为队列用于存放还未加入到素数环中的自然数。
(2) 初始化顺序表L和队列Q: 将1加入到顺序表L中,将2~n的自然数全部加入到Q队列中。
(3) 将出队的队首元素p与素数环最后一个数据元素q相加。
(a)若两数之和是素数并且p 不是队列中的最后一个数据(或队尾元素),则将p加入到素数环中;否则说明p暂时无法处理,必须再次入队等待,再重复此过程。
(b)若p为队尾元素,则还需判断它与素数环的第一个数据元素相加的和数是否为素数,若是素数,则将p加入到素数环,求解结束;若不是素数,进行回溯,判断素数环的最后一个元素和首元素相加是否为素数。直到队列为空或已对队列中每一个数据元素都遍历了一次且未能加入到素数环为止。
4.3 优先队列
1.优先队列及其存储结构
2.优先队列类
public class PriorityQueue<T extends Comparable>
implements QQueue
例4.4 进程按优先级调度管理。
栈与队列的相同点
1.都是线性结构,即数据元素之间具有“一对一”的逻辑关系。
2.插入操作都是限制在表尾进行。
3.都可在顺序存储结构和链式存储结构上实现。
4.在时间代价上,插入与删除操作都需常数时间;在空间代价上,情况 也相同。
5.多链栈和多链队列的管理模式可以相同。
栈与队列的不同点
-
删除数据元素操作的位置不同
-
两者的应用场合不相同 。
-
顺序栈可实现多栈空间共享,而顺序队列则不同 。
4.4 递归算法(重点)
斐波纳契数(Fibonacci number)表示某一数为其前两个数的和,假设n0 = 1**,n1** = 1**,则**
n2 = n1 + n0 = 1 + 1 = 2
n3 = n2 + n1 = 2 + 1 = 3
…
所以ni = ni–1 + ni–2
其递归程序如下:
public int fibon(int n)
{
int ans;
if(n==0||n==1)
ans=1;
else
ans=fibon(n-1)+fibon(n-2);
return(ans);
}