使用数组和单链表来模拟栈
本篇博文通过B站尚硅谷《Java数据结构与算法》课程所做,在此非常感谢韩老师!
基本介绍
- 栈的英文为(stack)
- 栈是一个先入后出(FILO-First In Last Out)的有序列表。
- 栈(stack)是限制线性表中元素的插入和删除只能在线性表的同一端进行的一种特殊线性表。允许插入和删除的一端,为变化的一端,称为栈顶(Top),另一端为固定的一端,称为栈底(Bottom)。
- 根据栈的定义可知,最先放入栈中元素在栈底,最后放入的元素在栈顶,而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除
入栈和出栈的图解
分析:栈的栈底指针是一直不变的,所有我们不需要在编码中显示的指定它,而栈顶指针在入栈时会先进行++操作,然后进入元素
、出栈时会先将元素出栈,在将栈顶指针--
,同时入栈时会将入栈的元素放在最上面,出栈会将最上面的元素出栈(因此栈这种数据结构是先进后出)
使用数组模拟栈
package edu.hebeu.stack.array_stack;
/**
* 用数组模拟的栈结构
* @author 13651
*
*/
public class ArrayStack {
private int top = -1; // 模拟栈顶
private int[] array; // 存放栈数据的数组
private int capacity; // 栈的容量
public ArrayStack(int capacity) {
this.capacity = capacity;
array = new int[capacity];
}
/**
* 这个方法用来实现入栈
* @param data
*/
public void push(int data) {
if(isFull()) {
System.err.println("栈满!");
return;
}
array[++top] = data;
}
/**
* 这个方法用来实现弹栈
* @return
*/
public int pop() {
if(isEmpty()) {
System.err.println("栈空!");
return 0;
}
return array[top--];
}
/**
* 判断栈是否满
* @return
*/
public boolean isFull() {
return top == capacity - 1;
}
/**
* 判断栈是否空
* @return
*/
public boolean isEmpty() {
return top == -1;
}
/**
* 这个方法用来实现栈内元素的打印
*/
public void show() {
if(isEmpty()) {
System.err.println("栈空!");
return;
}
System.out.println("从栈顶到栈底元素如下:");
for(int i = top; i >= 0; i--) {
System.out.printf("stack[%d] = %d \n", i, array[i]);
}
}
}
使用单链表模拟栈
使用单链表模拟栈时(这里我们使用的是带头节点的单链表),我们需要注意因为要符合栈的先进后出,所以我们有如下两种方式模拟:
- 方式一、入栈时,使用头插法,此时最先插入的元素在链表最后,因此使用普通的遍历方式即可实现最先插入的最后打印;出栈时就是将头结点之后的第一个节点删除并反回出去即可(最后插入的最先出);
- 方式二、入栈时,使用尾插法,此时最先插入的元素在最前,使用普通的遍历方式会导致最先插入的最先打印(不符合栈的先进后出),所以我们应该使用反向遍历方式遍历链表;出栈时就是将最后一个节点(尾节点)的元素删除并返回出去即可(最后插入的最先出);
这里我们将两种方式都写上,使用时按照上面的分析来搭配使用链表内的方法,节点类Node
如下:
package edu.hebeu.stack.linkedlist_stack;
public class Node {
public Integer data;
public Node next;
public int stackPointer; // 在栈中的指针
public Node(Integer data, int stackPointer) {
this.data = data;
this.stackPointer = stackPointer;
}
@Override
public String toString() {
return "Node [data=" + data + ", stackPointer=" + stackPointer + "]";
}
}
链表类LinkedList
如下:
package edu.hebeu.stack.linkedlist_stack;
import java.util.Stack;
public class LinkedList {
private Node head;
public LinkedList() {
head = new Node(null, -1); // 创建一个不存放数据的头结点
}
/**
* 头插法
* @param node
*/
public void headInsert(Node node) {
node.next = head.next;
head.next = node;
}
/**
* 尾插法
* @param node
*/
public void tailInsert(Node node) {
// TODO 用来存放当前链表的最后一个节点
Node lastNode = head.next;
// TODO 链表为空时如何插入
if(lastNode == null) { // 表示当前链表为空
head.next = node;
return;
}
// TODO 找到链表的最后一个节点
while(true) {
if(lastNode.next == null) { // 如果lastNode的next域为空,表示lastNode就是链表最后一个节点
break;
}
lastNode = lastNode.next; // 节点后移
}
// TODO 链表不为空时进行插入
lastNode.next = node;
}
/**
* 用来删除第一个节点(头结点后的第一个节点)
* @param node
*/
public Node delFirstNode() {
// TODO 如果链表为空
if(head.next == null) {
System.err.println("链表为空!");
return null;
}
Node delNode = head.next; // 获取要删除的节点
// TODO 删除头结点后的第一个节点
head.next = head.next.next;
return delNode;
}
/**
* 该方法用来删除最后一个节点
* @return
*/
public Node delLastNode() {
// TODO 保存最后一个节点的前一个节点
Node preLastNode = head;
if(head.next == null) {
System.err.println("链表空!");
return null;
}
// TODO 找到最后一个节点的前一个节点
while(true) {
// TODO 如果当前节点为最后一个节点的前一个节点
if(preLastNode.next.next == null) {
break;
}
preLastNode = preLastNode.next; // 节点后移
}
Node delNode = preLastNode.next; // 获取要删除的节点
// TODO 进行删除最后一个节点
preLastNode.next = preLastNode.next.next; // 或者 preLastNode.next = null
return delNode;
}
/**
* 该方法用来打印链表
*/
public void show() {
// TODO 用来存放当前进行打印的节点
Node currentNode = head.next;
// TODO 如果链表为空
if(currentNode == null) {
System.err.println("链表为空!");
return;
}
// TODO 进行打印
while(true) {
// TODO 如果当前节点为空(即链表以及遍历完了)
if(currentNode == null) {
break;
}
System.out.println("栈指针 --> " + currentNode.stackPointer + ", data --> " + currentNode.data);
currentNode = currentNode.next; // 节点后移
}
}
/**
* 该方法用来反向打印链表(利用栈的性质(先进后出),将链表的各个节点压入栈,然后再进行弹栈,以此来实现链表的反向遍历)
*/
public void reverseShow() {
if(head.next == null) { // 如果头节点的next域 为null,即头结点为最后一个节点(链表为空)
System.err.println("链表为空!");
return;
}
Stack<Node> nodeStack = new Stack<>(); // 创建Node泛型的栈结构
Node currentNode = head.next; // 因为头节点不存储数据,所以将头节点的下一个节点设置为当前的节点
while(currentNode != null) { // 如果当前节点不为空,即链表还没有遍历完
nodeStack.push(currentNode); // 将当前的节点入栈
currentNode = currentNode.next; // 将节点后移
}
while(nodeStack.size() > 0) { // 如果栈中还有元素
Node node = nodeStack.pop(); // 将栈顶元素出栈
System.out.println("栈指针 --> " + node.stackPointer + ", data --> " + node.data); // 输出当前节点的数据信息
}
}
}
栈LinkedListStack
如下:
package edu.hebeu.stack.linkedlist_stack;
/**
* 这个类用链表模拟栈
*
* 使用单链表模拟栈时(这里我们使用的是带头节点的单链表),我们需要注意因为要符合 栈的先进后出,所以我们有如
* 下两种方式模拟:
方式一、入栈时,使用 头插法,此时最先插入的元素在链表最后,因此 使用普通的遍历方式即可实现最先
插入的最后打印;出栈时就是 将头结点之后的第一个节点删除并反回出去即可(最后插入的最先出);
方式二、入栈时,使用 尾插法,此时最先插入的元素在最前,使用普通的遍历方式会导致最先插入的最先打
印(不符合栈的先进后出),所以我们 应该使用反向遍历方式遍历链表;出栈时就是 将最后一个节点(尾节点)的
元素删除并返回出去即可(最后插入的最先出);
* @author 13651
*
*/
public class LinkedListStack {
private int capacity; // 链表的容量
private int top = -1; // 栈顶指针
private LinkedList linkedList; // 存放栈数据的链表
public LinkedListStack(int capacity) {
this.capacity = capacity;
linkedList = new LinkedList();
}
/**
* 该方法用来实现入栈
* @param data
*/
public void push(int data) {
if(isFull()) {
System.err.println("栈满!");
return;
}
top++; // 栈顶指针++
linkedList.headInsert(new Node(data, top)); // 使用头插法插入链表
// linkedList.tailInsert(new Node(data, top)); // 使用尾插法插入链表
}
/**
* 该方法用来实现弹栈
* @return
*/
public Integer pop() {
if(isEmpty()) {
System.err.println("栈空!");
return null;
}
Integer delData = linkedList.delFirstNode().data; // 删除第一个节点(头结点的后一个节点)
// Integer delData = linkedList.delLastNode().data; // 删除最后一个节点
top--; // 栈顶指针--
return delData;
}
/**
* 判断链表是否满
* @return
*/
public boolean isFull() {
return top == capacity - 1;
}
/**
* 判断链表是否空
* @return
*/
public boolean isEmpty() {
return top == -1;
}
public void show() {
if(top == -1) {
System.err.println("栈空!");
return;
}
linkedList.show(); // 正常遍历链表
// linkedList.reverseShow(); // 反向遍历链表
}
}
编写测试类
针对以上两种栈,为了更连贯的运行程序,这里我们编写如下测试类:
package edu.hebeu;
import java.util.Scanner;
import edu.hebeu.stack.array_stack.ArrayStack;
import edu.hebeu.stack.linkedlist_stack.LinkedListStack;
public class Test {
private static Scanner SCANNER = new Scanner(System.in);
private static boolean ISCONTINUE = true;
public static void main(String[] args) {
System.out.println("请选择使用那种类型的数据结构测试栈?");
System.out.println("数组(a)");
System.out.println("链表(l)");
Character keyword = SCANNER.next().charAt(0);
if(keyword.equals('a')) {
arrayStackMenu();
} else if(keyword.equals('l')) {
linkedListStackMenu();
}
}
private static void arrayStackMenu() {
System.out.println("----数组模拟的栈结构----");
System.out.print("请输入栈的容量:"); int capacity = SCANNER.nextInt();
ArrayStack STACK = new ArrayStack(capacity); // 创建使用单链表模拟的,容量为capacity的栈对象实例
while(true) {
if(!ISCONTINUE) {
SCANNER.close();
System.out.println("bye~~~");
break;
}
System.out.println();System.out.println();
System.out.println("入栈(1)");
System.out.println("出栈(2)");
System.out.println("打印栈(3)");
System.out.println("结束(其他)");
int keyword = SCANNER.nextInt();
switch (keyword) {
case 1:
System.out.print("请输入入栈的数据:"); int pushData = SCANNER.nextInt();
STACK.push(pushData);
break;
case 2:
Integer popData = STACK.pop();
System.out.println("出栈的数据:" + popData);
break;
case 3:
STACK.show();
break;
default:
ISCONTINUE = false;
break;
}
}
}
private static void linkedListStackMenu() {
System.out.println("----链表模拟的栈结构----");
System.out.print("请输入栈的容量:"); int capacity = SCANNER.nextInt();
LinkedListStack STACK = new LinkedListStack(capacity); // 创建使用单链表模拟的,容量为capacity的栈对象实例
while(true) {
if(!ISCONTINUE) {
SCANNER.close();
System.out.println("bye~~~");
break;
}
System.out.println();System.out.println();
System.out.println("入栈(1)");
System.out.println("出栈(2)");
System.out.println("打印栈(3)");
System.out.println("结束(其他)");
int keyword = SCANNER.nextInt();
switch (keyword) {
case 1:
System.out.print("请输入入栈的数据:"); int pushData = SCANNER.nextInt();
STACK.push(pushData);
break;
case 2:
Integer popData = STACK.pop();
System.out.println("出栈的数据:" + popData);
break;
case 3:
STACK.show();
break;
default:
ISCONTINUE = false;
break;
}
}
}
}
测试结果
我们选择使用数组模拟栈,如下:
我们选择使用链表模拟栈,如下: