目录
一、数组模拟栈
在使用链表模拟栈之前,先把用数组模拟栈的代码粘贴一下,如果是看过这个课程的就可以跳过这一部分了,这里就是韩老师的代码。数组也是比较简单,没看韩老师课的同学可以自己看一下代码。
package stack;
import java.util.Scanner;
public class ArrayStackDemo {
public static void main(String[] args) {
//测试一下ArrayStack 是否正确
//先创建一个ArrayStack对象->表示栈
ArrayStack stack = new ArrayStack(4);
String key = "";
boolean loop = true; //控制是否退出菜单
Scanner scanner = new Scanner(System.in);
while(loop) {
System.out.println("show: 表示显示栈");
System.out.println("exit: 退出程序");
System.out.println("push: 表示添加数据到栈(入栈)");
System.out.println("pop: 表示从栈取出数据(出栈)");
System.out.println("请输入你的选择");
key = scanner.next();
switch (key) {
case "show":
stack.list();
break;
case "push":
System.out.println("请输入一个数");
int value = scanner.nextInt();
stack.push(value);
break;
case "pop":
try {
int res = stack.pop();
System.out.printf("出栈的数据是 %d\n", res);
} catch (Exception e) {
// TODO: handle exception
System.out.println(e.getMessage());
}
break;
case "exit":
scanner.close();
loop = false;
break;
default:
break;
}
}
System.out.println("程序退出~~~");
}
}
//定义一个 ArrayStack 表示栈
class ArrayStack {
private int maxSize; // 栈的大小
private int[] stack; // 数组,数组模拟栈,数据就放在该数组
private int top = -1;// top表示栈顶,初始化为-1
//构造器
public ArrayStack(int maxSize) {
this.maxSize = maxSize;
stack = new int[this.maxSize];
}
//栈满
public boolean isFull() {
return top == maxSize - 1;
}
//栈空
public boolean isEmpty() {
return top == -1;
}
//入栈-push
public void push(int value) {
//先判断栈是否满
if(isFull()) {
System.out.println("栈满");
return;
}
top++;
stack[top] = value;
}
//出栈-pop, 将栈顶的数据返回
public int pop() {
//先判断栈是否空
if(isEmpty()) {
//抛出异常
throw new RuntimeException("栈空,没有数据~");
}
int value = stack[top];
top--;
return value;
}
//显示栈的情况[遍历栈], 遍历时,需要从栈顶开始显示数据
public void list() {
if(isEmpty()) {
System.out.println("栈空,没有数据~~");
return;
}
//需要从栈顶开始显示数据
for(int i = top; i >= 0 ; i--) {
System.out.printf("stack[%d]=%d\n", i, stack[i]);
}
}
}
一、单向链表模拟栈
1)简单版
那接下来我们使用单链表来模拟栈,只需要在原来数组模拟的基础上稍作修改,我们把top当作计数器来使用,用来判断栈空、栈满,然后单链表中写了add()方法用来添加数据,getLastNode()方法用来定位到最后一个节点,输出打印这个节点的数据,getAndRemoveLastNode()方法用来出栈,这时我们定位到倒数第二个节点temp,然后输出temp.next的数据,并且使temp.next=null,以此来断开最后一个节点,表示出栈了,然后top--。
package stack;
import java.util.Scanner;
public class SingleLinkedListStackDemo {
public static void main(String[] args) {
//测试一下SingleLinkedListStack 是否正确
//先创建一个SingleLinkedListStack对象->表示栈
SingleLinkedListStack stack = new SingleLinkedListStack(4);
String key = "";
boolean loop = true; //控制是否退出菜单
Scanner scanner = new Scanner(System.in);
while(loop) {
System.out.println("show: 表示显示栈");
System.out.println("exit: 退出程序");
System.out.println("push: 表示添加数据到栈(入栈)");
System.out.println("pop: 表示从栈取出数据(出栈)");
System.out.println("请输入你的选择");
key = scanner.next();
switch (key) {
case "show":
stack.list();
break;
case "push":
System.out.println("请输入一个数");
int value = scanner.nextInt();
stack.push(value);
break;
case "pop":
try {
int res = stack.pop();
System.out.printf("出栈的数据是 %d\n", res);
} catch (Exception e) {
// TODO: handle exception
System.out.println(e.getMessage());
}
break;
case "exit":
scanner.close();
loop = false;
break;
default:
break;
}
}
System.out.println("程序退出~~~");
}
}
class SingleLinkedListStack{
private int maxSize; // 栈的大小
private SingleLinkedList stack=new SingleLinkedList(); // 单链表,单链表模拟栈,数据就放在该单链表中
private int top = -1;// top表示栈顶,初始化为-1
//构造器
public SingleLinkedListStack(int maxSize){
this.maxSize=maxSize;
}
//栈满
public boolean isFull() {
return top == maxSize - 1;
}
//栈空
public boolean isEmpty() {
return top == -1;
}
//入栈-push
public void push(int value) {
//先判断栈是否满
if(isFull()) {
System.out.println("栈满");
return;
}
Node node=new Node(value);
stack.add(node);
top++;
}
//出栈-pop, 将栈顶的数据返回
public int pop() {
//先判断栈是否空
if(isEmpty()) {
//抛出异常
throw new RuntimeException("栈空,没有数据~");
}
int value = stack.getAndRemoveLastNode().num;
top--;
return value;
}
//显示栈的情况[遍历栈], 遍历时,需要从栈顶开始显示数据
public void list() {
if(isEmpty()) {
System.out.println("栈空,没有数据~~");
return;
}
//需要从栈顶开始显示数据
//因为头节点,不能动,因此我们需要一个辅助变量来遍历
Node temp = stack.getHead().next;
for(int i = top; i >= 0 ; i--) {
System.out.printf("stack[%d]=%d\n", i, stack.getLastNode().num);
}
}
}
//定义SingleLinkedList 管理我们的节点
class SingleLinkedList {
//先初始化一个头节点, 头节点不要动, 不存放具体的数据
private Node head = new Node(0);
//返回头节点
public Node getHead() {
return head;
}
//添加节点到单向链表
//思路
//1. 找到当前链表的最后节点
//2. 将最后这个节点的next 指向 新的节点
public void add(Node node) {
//因为head节点不能动,因此我们需要一个辅助遍历 temp
Node temp = head;
//遍历链表,找到最后
//
while (temp.next != null) {
//找到链表的最后
//如果没有找到最后, 将将temp后移
temp = temp.next;
}
//当退出while循环时,temp就指向了链表的最后
//将最后这个节点的next 指向 新的节点
temp.next = node;
}
public Node getAndRemoveLastNode() {
if (head.next == null) { //空链表
return null;
}
//定义一个辅助的变量
Node cur = head;
//先找到倒数第二个节点
while (cur.next.next != null) {
cur = cur.next; //遍历
}
Node last = cur.next;//保存最后一个节点
cur.next = null;//断开最后一个节点
return last;
}
public Node getLastNode() {
if (head.next == null) { //空链表
return null;
}
//定义一个辅助的变量
Node cur = head;
//先找到倒数第一个节点
while (cur.next != null) {
cur = cur.next; //遍历
}
return cur;
}
}
//定义Node , 每个Node 对象就是一个节点
class Node {
public int num; //存放节点数据
public Node next; //指向下一个节点
//构造器
public Node(int num) {
this.num = num;
}
}
2)升级版
以上是对数组模拟稍作修改,但是发现有一点小的弊端就是,每次添加新的节点时(入栈),调用add()方法都需要从头开始遍历,效率非常低,并且SingleLinkedList这个类中写了比较多的方法,可读性比较差,更像是在联系单链表,偏离了我们要熟悉栈的这个目的,因此,对SingleLinkedListStack这个类进行了改进,添加了Node类型的head、cur成员,int类型的number成员。通过cur成员保存最后一个节点的数据,这样才更加形象的表示了栈,添加数据时也不用从头遍历,直接cur.next=newNode就可以了。这时候SingleLinkedList这个类中的方法也全都不需要了。
package stack;
import java.util.Scanner;
import java.util.Stack;
public class SingleLinkedListStackDemo {
public static void main(String[] args) {
//测试一下SingleLinkedListStack 是否正确
//先创建一个SingleLinkedListStack对象->表示栈
SingleLinkedListStack stack = new SingleLinkedListStack(4);
String key = "";
boolean loop = true; //控制是否退出菜单
Scanner scanner = new Scanner(System.in);
while(loop) {
System.out.println("show: 表示显示栈");
System.out.println("exit: 退出程序");
System.out.println("push: 表示添加数据到栈(入栈)");
System.out.println("pop: 表示从栈取出数据(出栈)");
System.out.println("请输入你的选择");
key = scanner.next();
switch (key) {
case "show":
stack.list();
break;
case "push":
System.out.println("请输入一个数");
int value = scanner.nextInt();
stack.push(value);
break;
case "pop":
try {
int res = stack.pop();
System.out.printf("出栈的数据是 %d\n", res);
} catch (Exception e) {
// TODO: handle exception
System.out.println(e.getMessage());
}
break;
case "exit":
scanner.close();
loop = false;
break;
default:
break;
}
}
System.out.println("程序退出~~~");
}
}
class SingleLinkedListStack{
private int maxSize; // 栈的大小
private SingleLinkedList stack=new SingleLinkedList(); // 单链表,单链表模拟栈,数据就放在该单链表中
private Node head = stack.getHead();// 链表的头节点
private Node cur= head;//因为头节点不能动,因此需要一个辅助节点,指向链表的最后
private int number = 0;//计数器,代表存储的元素的数量,初始化为0
//构造器
public SingleLinkedListStack(int maxSize){
this.maxSize=maxSize;
}
//栈满
public boolean isFull() {
return number == maxSize;
}
//栈空
public boolean isEmpty() {
return number == 0;
}
//入栈-push
public void push(int value) {
//先判断栈是否满
if(isFull()) {
System.out.println("栈满");
return;
}
Node node=new Node(value);//创建一个新的节点,存放要添加的数据
cur.next=node;
cur=cur.next;//后移
number++;//计数
}
//出栈-pop, 将栈顶的数据返回
public int pop() {
//先判断栈是否空
if(isEmpty()) {
//抛出异常
throw new RuntimeException("栈空,没有数据~");
}
int value = cur.num;
//由于单向链表不能往前移动,只能重新遍历一下,使cur指向倒数第二个节点
//这里可以使用计数器进行遍历,要不然的话,需要判断cur.next.next!=null
// 需要判断是不是只有一个节点,或者只有一个头节点的情况
cur=head;
for (int i=0;i<number-1;i++){
cur=cur.next; //此时如果只有头节点或者只有一个节点,cur都是指向头节点的;
}
cur.next=null;//将后面的节点断开,等待gc回收
number--;
return value;
}
//显示栈的情况[遍历栈], 遍历时,需要从栈顶开始显示数据
public void list() {
if(isEmpty()) {
System.out.println("栈空,没有数据~~");
return;
}
//需要从栈顶开始显示数据,使用reversePrint()方法
reverPrint(head);
}
private void reverPrint(Node head) {
//创建一个栈,将链表节点压入栈中,逆序打印
Stack<Node> ints = new Stack<Node>();
//头节点不能动,创建一个辅助变量,局部变量cur
Node cur =head.next;
//压栈
while (cur!=null){
ints.push(cur);
cur=cur.next;
}
//出栈
int i=number;
while (ints.size()>0){
System.out.printf("stack[%d]=%d\n",i,ints.pop().num);
i--;
}
}
}
//定义SingleLinkedList 管理我们的节点
class SingleLinkedList {
//先初始化一个头节点, 头节点不要动, 不存放具体的数据
private Node head = new Node(0);
//返回头节点
public Node getHead() {
return head;
}
}
//定义Node , 每个Node 对象就是一个节点
class Node {
public int num; //存放节点数据
public Node next; //指向下一个节点
//构造器
public Node(int num) {
this.num = num;
}
}
三、双向链表模拟栈
在上面的代码中,我们更加形象的表示了栈,但是我们发现,在逆序打印栈中的数据时,其实还是需要从头遍历number次,或者反转链表再打印,或者调用了java的stack库类,而且出栈之后都需要重新遍历,把cur定位到倒数第二个节点。这些都是因为SingelLinkedList只能单向搜索。因此,我们最后使用双向链表进行模拟栈。
package stack;
import java.util.Scanner;
public class DoubleLinkedListStackDemo {
public static void main(String[] args) {
//测试一下SingleLinkedListStack 是否正确
//先创建一个SingleLinkedListStack对象->表示栈
DoubleLinkedListStack stack = new DoubleLinkedListStack(4);
String key = "";
boolean loop = true; //控制是否退出菜单
Scanner scanner = new Scanner(System.in);
while(loop) {
System.out.println("show: 表示显示栈");
System.out.println("exit: 退出程序");
System.out.println("push: 表示添加数据到栈(入栈)");
System.out.println("pop: 表示从栈取出数据(出栈)");
System.out.println("请输入你的选择");
key = scanner.next();
switch (key) {
case "show":
stack.show();
break;
case "push":
System.out.println("请输入一个数");
int value = scanner.nextInt();
stack.push(value);
break;
case "pop":
try {
int res = stack.pop();
System.out.printf("出栈的数据是 %d\n", res);
} catch (Exception e) {
// TODO: handle exception
System.out.println(e.getMessage());
}
break;
case "exit":
scanner.close();
loop = false;
break;
default:
break;
}
}
System.out.println("程序退出~~~");
}
}
class DoubleLinkedListStack {
private int maxSize;//栈的大小
private DoubleLinkedList stack = new DoubleLinkedList();//栈
private Node2 head = stack.getHead();//头节点
private Node2 cur = head;//辅助变量
private int num = 0;//节点数量
//构造器
public DoubleLinkedListStack(int maxSize) {
this.maxSize = maxSize;
}
//判断栈满
public boolean isFull() {
return num == maxSize;
}
//判断栈空
public boolean isEmpty(){
return num==0;
}
//入栈
public void push(int value){
//判断是否栈满
if(isFull()){
System.out.println("栈满");
return;
}
Node2 newNode = new Node2(value);
cur.next=newNode;
newNode.pre=cur;
cur=cur.next;
num++;
}
//出栈
public int pop(){
//判断是否栈空
if(isEmpty()){
throw new RuntimeException("栈空,没有数据");
}
int value = cur.value;//保存要返回的值
//自我删除
//cur一直指向最后一个节点,因此删除之前不用判断是否是最后一个节点
/*
如果是最后一个节点,删除时操作为cur.pre.next=null;cur.pre=null;
如果不是最后一个节点,删除时操作为cur.pre.next=cur.next;cur.next.pre=cur.pre;
*/
//删除之前需要先将cur前移
cur=cur.pre;//此时指向倒数第二个节点,需要判断cur是不是头节点
if(num==1){//头节点
cur.next.pre=null;
cur.next=null;
}else {//自我删除
cur.pre.next = cur.next;
cur.next.pre = cur.pre;
}
num--;
return value;
}
//显示栈
public void show(){
//判断是否栈空
if(isEmpty()){
System.out.println("栈空,没有数据");
return;
}
//cur也不要变,再定义一个辅助变量,倒序遍历
Node2 temp = cur;
for(int i=num;i>0;i--){
System.out.printf("stack[%d]=%d\n",i,temp.value);
temp=temp.pre;
}
}
}
class DoubleLinkedList {
//头节点
private Node2 head = new Node2(0);
//返回头节点
public Node2 getHead() {
return head;
}
}
//定义Node , 每个Node 对象就是一个节点
class Node2 {
public int value; //存放节点数据
public Node2 pre; //指向前一个节点
public Node2 next; //指向下一个节点
//构造器
public Node2(int value) {
this.value = value;
}
}