在我的前两篇算法笔记中,复习回顾了数组和列表的实现,现在谈谈栈和队列。
首先要明确一点,数组和列表都是属于物理存储结构,而栈和队列属于逻辑存储结构,他们的物理实现既可以用数组也可以用链表来实现。
目录
一、栈的实现
栈,是一种先进后出(FILO)的线性数据结构,主要操作为入栈和出栈。
栈底:最早进入的元素存放的位置,栈顶:最后进入元素存放的位置(有些栈中将栈顶表示为栈顶元素的下一位置)
1.栈的数组的实现
public class MyStack {
private int[] array;
private int top = -1;//用top来表示栈顶,指向栈顶元素
public MyStack(int capacity){
array = new int[capacity];
}
public void push(int data) throws Exception{
if(top >= array.length-1)
throw new IndexOutOfBoundsException("边界超过范围!");
else
array[++top] = data;//先将指针上移,后赋值
}
public int pop() throws Exception{
int temp;
if(top < 0)
throw new IndexOutOfBoundsException("栈为空,不能再删除元素!");
else{
temp = array[top];
array[top--] = 0;
}
return temp;
}
public void output(){
for(int i = 0; i <= top; i++){
System.out.println(array[i]);
}
}
public static void main(String[] args) throws Exception{
MyStack myStack = new MyStack(5);
myStack.push(1);
myStack.push(3);
myStack.push(2);
myStack.pop();
myStack.push(4);
myStack.pop();
myStack.output();
}
}
2.栈的链表实现
栈用链表来实现时,和数组实现不同的是,在出栈时,因为我们只有一个top节点来指向栈顶,因此需要从头到尾遍历一遍,来找到栈顶的前一个位置。具体的实现如下:
public class MyStack_LinkList {
private static class Node{
int data;
Node next;
Node(int data){
this.data = data;
}
}
private Node head;//定义链表的头结点
private Node top;
private int size;//定义栈中的元素个数
private int maxSize;
private MyStack_LinkList(int capacity){
maxSize = capacity;
}
public void push(int data) throws Exception{
if(size >= maxSize){
throw new IndexOutOfBoundsException("栈已满,不能再入栈!");
}
Node pushedNode = new Node(data);
if(size == 0){
head = pushedNode;
top = pushedNode;
pushedNode.next = null;
}
else{
top.next = pushedNode;
pushedNode.next = null;
top = pushedNode;
}
size++;
}
public int pop() throws Exception{
int temp = 0;
if(size <= 0)
throw new IndexOutOfBoundsException("栈为空,不能再出栈!");
else if(size == 1){//当栈中元素个数为1时,单独讨论,需将头节点置为空
temp = top.data;
top = null;
}
else{
temp = top.data;
top = get(size - 1);//此时需遍历一遍链表,用top指向需出栈的上一个节点
top.next = null;
}
size--;
return temp;
}
/*
从头到尾查找元素
*/
public Node get(int index){
Node temp = head;
for(int i = 1; i < index; i++){
temp = temp.next;
}
return temp;
}
public void output(){
Node temp = head;
for(int i = 0; i < size; i++){
System.out.println(temp.data);
temp = temp.next;
}
}
public static void main(String[] args) throws Exception{
MyStack_LinkList myStack_linkList = new MyStack_LinkList(5);
myStack_linkList.push(1);
myStack_linkList.push(2);
myStack_linkList.pop();
myStack_linkList.push(3);
myStack_linkList.push(5);
myStack_linkList.output();
}
}
3.栈的应用场景
(1)括号匹配判断,用于判断()、{}等是否匹配
(2)进制转换时,逆向输出转换后的数
(3)实现递归的逻辑可以用栈来实现
(4)栈还可以用于面包屑导航,使用户在浏览页面时可以轻松地回溯到上一级或更上一级页面
二、队列的实现
队列是一种先进先出(FIFO)的线性的数据结构,队列的主要操作为入队和出队。
队头:队列的出口端,队尾:队列的入口端,通常在数组中表示为最后入队元素的下一个位置。
和栈相同,我们也可以用数组和链表来实现队列,但是在用数组实现时,有一点差别:若队头不断有元素出队,那么队列的可用空间就会变小,所以我们通常用循环队列来实现,此时队尾也可能出现在队头的前面。
1.队列的数组实现
队列的数组实现这里的队列一般都是循环队列!
特别注意:
(1)队列满时的判断条件为(队尾下标+1) % 数组长度 = 队头下标
(2)队尾指针指向的位置空出一位,因此队列最大容量比数组长度小1。
public class MyQueue {
private int[] array;
private int front;
private int rear;
public MyQueue(int capacity){
array = new int[capacity];
}
/*
入队时,只需判断队列是否已满,若队列已满,则抛出异常,其他情况(包括队列为空)都正常插入
*/
public void enQueue(int data) throws Exception{
if((rear+1) % array.length == front)
throw new Exception("队列已满,不能入队!");
array[rear] = data;
rear = (rear+1) % array.length;
}
/*
出队时,判断队列是否为空,若队列为空,抛出异常
*/
public int deQueue() throws Exception{
if(front == rear)
throw new Exception("队列为空,不能出队!");
int temp = array[front];
front = (front+1) % array.length;
return temp;
}
// public void output(){
// for(int i = front; ((i+1) % array.length) <= rear; i++){//一直在循环输出,严重错误!不能把取模判断语句写在条件里面!
// i %= array.length;
// System.out.println(array[i]);
// }
// }
public void output(){
for(int i = front; i != rear; i = (i+1) % array.length){
System.out.println(array[i]);
}
}
public static void main(String[] args) throws Exception{
MyQueue myQueue = new MyQueue(5);//长度为5的队列只能插入4个元素
myQueue.enQueue(1);
myQueue.enQueue(3);
myQueue.enQueue(2);
myQueue.enQueue(4);
myQueue.deQueue();
myQueue.deQueue();
myQueue.enQueue(5);
myQueue.enQueue(6);
myQueue.output();
}
}
2.队列的链表实现
队列用链表实现时,用头指针指向队列的第一个节点,用尾指针指向队列的最后一个节点。
public class MyQueue_LinkList {
private static class Node{
int data;
Node next;
Node(int data){
this.data = data;
}
}
private Node front;
private Node rear;
private int size;//队列中实际元素的个数
private int maxsize;
public MyQueue_LinkList(int capacity){
maxsize = capacity;
}
public void enQueue(int data) throws Exception{
if(size >= maxsize)
throw new Exception("队列已满,无法入队");
Node insertedNode = new Node(data);
if(size == 0){
front = insertedNode;
rear = insertedNode;
}
else{
rear.next = insertedNode;
rear = insertedNode;
}
size++;
}
public int deQueue() throws Exception{
if(front == null)
throw new Exception("队列为空,无法出队!");
int temp;
if(front == rear)//队列中只有一个节点
rear = null;
temp = front.data;
front = front.next;
size--;
return temp;
}
public void output(){
Node temp = front;
for(int i = 0 ; i < size; i++){
System.out.println(temp.data);
temp = temp.next;
}
}
public static void main(String[] args) throws Exception{
MyQueue_LinkList myQueue_linkList = new MyQueue_LinkList(5);
myQueue_linkList.enQueue(1);
myQueue_linkList.enQueue(3);
myQueue_linkList.enQueue(2);
myQueue_linkList.deQueue();
myQueue_linkList.deQueue();
myQueue_linkList.enQueue(5);
myQueue_linkList.enQueue(7);
myQueue_linkList.output();
}
}
3.队列的应用场景
(1)银行排队,先来先服务
(2)多线程中,争夺公平锁的等待队列,就是按照访问顺序来决定线程在队列中的次序的
(3)网络爬虫实现网站抓取,就是把待抓取的网站URL存入队列中,再按照存入队列的顺序来依次抓取和解析