一、栈的构建及其基本操作
栈是一种后进先出或先进后出的线性表,其插入和删除操作只允许在表的尾端进行。
栈中允许进行插入和删除操作的一端称为栈顶(top),另一端称为栈底(bottom)。
通常,人们将栈的插入操作称为入栈(push),而将删除操作称为出栈(pop)。
1. 顺序栈(使用数组实现)
顺序栈的存储结构示意图
//顺序栈
public class SqStack {
private int[] stackElem; //对象数组
private int top; //在非空栈中,top始终指向栈顶元素的下一个存储位置;当栈为空时,top=0
public static void main(String[] args) throws Exception {
//测试
SqStack st = new SqStack(5); //构建一个长度为5的顺序栈
System.out.println(st.isEmpty()); //判空
System.out.println(st.length()); //返回栈的长度
st.push(1); //入栈
st.push(2);
st.push(3);
st.display(); //打印
System.out.println();
st.pop(); //出栈
st.display();
System.out.println(); //打印
System.out.println(st.isEmpty());
System.out.println(st.length());
}
public SqStack(int maxSize) {
top = 0; //初始化
stackElem = new int[maxSize]; //为栈分配maxSize个存储单元
}
//1. 置栈空
public void clear() {
top = 0;
}
//2. 判断栈是否为空
public boolean isEmpty() {
return top == 0;
}
//3. 求栈中数据元素个数
public int length() {
return top;
}
//4. 取栈顶元素
public int peek() {
if(!isEmpty())
return stackElem[top-1];
else
return -1;
}
//5. 入栈
public void push(int x) throws Exception{
if(top == stackElem.length) //判断是否栈满
throw new Exception("栈已满"); //抛出异常
else
stackElem[top++] = x; //先将新的数据元素x压入栈顶,再top增加1
}
//6. 出栈
public int pop(){
if(isEmpty()) //判断栈是否为空
return -1;
else
return stackElem[--top];
}
//7. 输出栈中所有元素
public void display() {
for(int i=top-1;i>=0;i--) {
System.out.print(stackElem[i] + " "); //输出
}
}
}
【注:
top的定义方式有两种——
一种是将其设置为指向栈顶元素存储位置的下一个存储单元的位置,则空栈时,top=0;
另一种是将top设置为指向栈顶元素的存储位置,则空栈时,top=-1。
再次采用前一种方式来表示栈顶。】
2. 链栈(使用不带头结点的单链表实现)
链栈的存储结构示意图
//使用泛型构造结点类
public class Node<T> {
public T data; //存放结点值
public Node<T> next; //后继结点的引用
//无参时的构造函数
public Node(){
this(null,null);
}
//带一个参数时的构造函数
public Node(T data){
this(data,null);
}
//带两个参数时的构造函数
public Node(T data,Node<T> next)
{
this.data=data;
this.next=next;
}
}
//链栈
public class LinkStack<T> {
private Node<T> top; //栈顶元素的应用
public static void main(String[] args) throws Exception {
//测试
LinkStack<Integer> ls = new LinkStack<Integer>();
System.out.println(ls.isEmpty()); //判空
System.out.println(ls.length()); //返回栈的长度
ls.push(1); //入栈
ls.push(2);
ls.push(3);
ls.display(); //打印
System.out.println();
ls.pop(); //出栈
ls.display();
System.out.println(); //打印
System.out.println(ls.isEmpty());
System.out.println(ls.length());
}
//1. 置栈空
public void clear() {
top = null;
}
//2. 判断栈是否为空
public boolean isEmpty() {
return top == null;
}
//3. 求栈中数据元素个数
public int length() {
Node<T> p = top; //初始化,p指向栈顶元素
int length = 0; //length为长度计数器
while(p != null) { //从栈顶元素开始向后查找,直到p指向空
p = p.next; //p指针后移
++length; //长度加1
}
return length; //返回长度
}
//4. 取栈顶元素
public T peek() {
if(!isEmpty()) //栈非空
return top.data; //返回栈顶元素
else
return null;
}
//5. 入栈
public void push(T x) throws Exception{
Node<T> p = new Node<T>(x); //构造一个新结点
p.next = top;
top = p; //新结点成为当前的栈顶元素
}
//6. 出栈
public T pop(){
if(isEmpty())
return null;
else {
Node<T> p = top; //p指向被删结点(即栈顶元素)
top = top.next; //修改链指针,使栈顶结点从链栈中移去
return p.data; //返回被删结点数据域的值
}
}
//7. 输出栈中所有元素
public void display() {
Node<T> p = top; //初始化,p指向栈顶元素
while(p != null) { //输出所有非空结点的数据元素值
System.out.print(p.data.toString() + " ");
p = p.next; //p指针后移
}
}
}
二、队列的构建及其基本操作
队列是一种先进先出或后进后出的线性表,只允许在表尾插入数据,在表头删除数据。
允许进行插入的一端称为是队尾(rear),允许进行删除的一端称为是队首(front)。
队列的插入操作通常称为入队操作(push),而删除操作通常称为出队操作(pop)。
1. 顺序队列(使用数组实现)
顺序队列的存储结构示意图
//循环顺序队列
public class SqQueue {
private int[] queueElem; //队列存储空间
private int front; //队首的引用,若队列不空,指向队首元素
private int rear; //队尾的引用,若队列不空,指向对队尾元素的下一个存储位置
public static void main(String[] args) throws Exception{
//测试
SqQueue sq = new SqQueue(5);
sq.display();
sq.offer(1); //入队
sq.offer(2);
sq.offer(3);
sq.display(); //打印
System.out.println();
System.out.println(sq.poll()); //出队
sq.display(); //打印
System.out.println();
System.out.println(sq.isEmpty()); //判空
System.out.println(sq.length()); //长度
}
//构造函数
public SqQueue(int maxSize) {
front = rear = 0; //队首、队尾初始化为0
queueElem = new int[maxSize]; //为队列分配maxSize个存储单元
}
//1. 队列置空
public void clear() {
front = rear = 0;
}
//2. 判断队列是否为空
public boolean isEmpty() {
return front == rear;
}
//3. 求队列的长度
public int length() {
return (rear-front + queueElem.length) % queueElem.length;
}
//4. 读取队首元素
public int peek() {
if(front == rear) //队列为空
return -1;
else
return queueElem[front]; //返回队首元素
}
//5. 入队
public void offer(int x) throws Exception{
if((rear+1) % queueElem.length == front) //队列满
throw new Exception("队列已满"); //抛出异常
else {
queueElem[rear] = x; //x存入rear所指的数组存储位置中,使其成为新的队尾元素
rear = (rear+1) % queueElem.length; //修改队尾指针
}
}
//6. 出队
public int poll() {
if(front == rear) //队列空
return -1;
else {
int t = queueElem[front];
front = (front+1) % queueElem.length; //修改队首指针
return t; //返回队首元素
}
}
//7. 输出队列中的所有数据元素(从队首到队尾)
public void display() {
if(!isEmpty()) {
for(int i=front;i!=rear;i=(i+1)%queueElem.length)
System.out.print(queueElem[i] + " ");
}else {
System.out.println("队列为空");
}
}
}
【注:
顺序队列会因数组下标越界而引起“假溢出”,故在此将顺序队列所使用的存储空间看成是一个逻辑上首尾相连的循环队列。】
2. 链队列(使用不带头结点的单链表实现)
链队列的存储结构示意图
//链队列
public class LinkQueue<T> {
private Node<T> front; //队首指针
private Node<T> rear; //队尾指针
public static void main(String[] args) throws Exception{
//测试
LinkQueue<Integer> lq = new LinkQueue<Integer>();
lq.display();
lq.offer(1); //入队
lq.offer(2);
lq.offer(3);
lq.display(); //打印
System.out.println();
System.out.println(lq.poll()); //出队
lq.display(); //打印
System.out.println();
System.out.println(lq.isEmpty()); //判空
System.out.println(lq.length()); //长度
}
//构造函数
public LinkQueue() {
front = rear = null;
}
//1. 队列置空
public void clear() {
front = rear = null;
}
//2. 判断队列是否为空
public boolean isEmpty() {
return front == null;
}
//3. 求队列的长度
public int length() {
Node<T> p = front; //p指针指向队首元素
int length = 0; //计数器
while(p != null) { //只要p不为空
p = p.next; //指针后移
++length; //计数器+1
}
return length; //返回长度
}
//4. 读取队首元素
public T peek() {
if(front != null) //队列不为空
return front.data; //返回队首结点的数据域值
else
return null;
}
//5. 入队
public void offer(T x) throws Exception{
Node<T> p = new Node<T>(x); //初始化新结点
if(front != null) { //队列非空
rear.next = p;
rear = p; //修改队尾的位置
}else
front = rear = p;
}
//6. 出队
public T poll() {
if(front != null) { //队列非空
Node<T> p = front; //p指向队首结点
front = front.next; //队首结点出列
if(p == rear) //若被删除的结点是队尾结点时
rear = null;
return p.data; //返回队首结点的数据域值
}
else
return null;
}
//7. 输出队列中的所有数据元素(从队首到队尾)
public void display() {
if(!isEmpty()) {
Node<T> p = front;
while(p != null) {
System.out.print(p.data.toString() + " ");
p = p.next;
}
}else {
System.out.println("队列为空");
}
}
}
三、比较栈和队列
1. 栈和队列的相同点
- 都是线性结构,即数据元素之间具有“一对一”的逻辑关系;
- 插入操作都是限制在表尾进行;
- 都可在顺序存储结构和链式存储结构上实现
- 在时间代价上,插入和删除操作都需常数时间;在空间代价上,情况也相同;
- 多链栈和多链队列的管理模式可以相同。
2. 栈对队列的不同点
- 删除数据的元素操作位置不同;
- 两者的应用场合不同;
- 顺序栈可实现多栈空间共享,而顺序队列则不同。