栈(Java)

前言:博主第一次写博客,用词或一些概念上可能会出现一些错误,希望大家指出来。

下压栈(LIFO)

下压栈(简称栈)是一种具有后进先出特点的集合了类型。
栈的应用性很广泛,使用栈可以解决括号匹配问题,迷宫问题,表达式求值这类问题。

我们先用图形来解释一下什么是“栈”吧。
首先我们假设这里有一个空箱子(空栈),我们手上有3本书,Java,C++,以及PHP。
空栈以及3个元素

如果我们想将一本java书放入,需要怎么做呢?
其实非常简单,直接放进入就好了。(压栈,也叫入栈)
放入Java的栈
当我们依次再将C++、PHP的书放进去,就会形成下面的状态。
放入3个元素的栈
我们已经成功的将三本书放入,接下来我们需要考虑如何取出目标书籍。

比如:当我们需要Java书时,该如何做呢?
我们发现,此时Java是在箱子底部,而我们只能取出箱子顶部(栈顶)的书(元素)。所以我们想取出Java书,就必须将PHP与C++取出。而C++又在PHP的下面,所以我们的第一步就是将箱子顶部的PHP取出(弹栈,也叫出栈)。

经过分析,我们需要从箱子顶部(栈顶)取出第一本书(栈顶元素)。取出后,C++书就到了箱子顶部(栈顶),而Java依旧在箱子底,所以我们还需要一次取出箱子顶部的书(栈顶元素)。
取出一个栈顶元素

接着上一步,我们需要从箱子顶部(栈顶)再次取出第一本书(栈顶元素)。取出后,Java书就到了箱子顶部(栈顶)。
取出一个栈顶元素
此时我们只需从箱子里拿出Java即可。(弹栈)
取出一个栈顶元素

根据上述过程,我们很直观的发现了栈的特点:后进先出(LIFO,Last In First Out),也可称作先进后出(FILO)。

顺序表实现栈

栈的API

下压栈(FILO)
public class Stack<Item>
Stack()创建一个空栈
void push(Item data)压栈
Item pop()弹栈
boolean isEmpty()判断栈是否为空
int size()栈中元素数量
void resize(int max)修改栈大小

栈类中成员变量:

为了不被外部类所修改,栈中成员变量我们用private关键字来进行封装:

private Item[] a = (Item[]) new Object[1];//由于某种原因,创建泛型数组不被允许,因此需要进行类型转换
private int N = 0;//标记栈顶,同样也存储了栈的大小
方法的实现
1.Stack()
public Stack() { }
2.size()
 public int size() {
  return N;
 }
3.boolean isEmpty()
 public boolean isEmpty() {
  return N == 0;
 }
4.void push(Item data)
 public void push(Item data) {
  if (a.length == N) {//栈满,将栈长修改为原长2倍
   resize(a.length * 2);
  }
  a[N++] = data;//压栈
 }
5.Item pop()
public Item pop() {
  Item data = a[--N];//弹栈,并缩短栈的大小
  a[N] = null;//将原栈顶赋null,防止对象游离
  if (N > 0 && N == a.length / 4)//顺序表利用率低于25%,缩短顺序表长度
   resize(a.length / 2);
  return data;//返回原栈顶元素
 }
6.void resize(int max)
 public void resize(int max) {
  Item[] temp = (Item[]) new Object[max];//创建新顺序表
  for (int loop = 0 ; loop < N ; loop++)//将原表数据存储至新表
   temp[loop] = a[loop];
  a = temp;//重新指向
 }

这里需要说明一下:由于使用顺序表实现栈,顺序表长度就得因栈的大小而改变。所以添加了一个名为resize的方法来重置顺序表长度。当顺序表利用率低于25%时,顺序表主动缩小。

整体代码如下:

public class Stack<Item> {
 private Item[] a = (Item[]) new Object[1];//由于某种原因,创建泛型数组不被允许,因此需要进行类型转换
 private int N = 0;//标记栈顶,同样也存储了栈的大小
 
 public Stack() { }
 
 public boolean isEmpty() {
  return N == 0;
 }
 
 public int size() {
  return N;
 }
 
 public void resize(int max) {
  Item[] temp = (Item[]) new Object[max];//创建新顺序表
  for (int loop = 0 ; loop < N ; loop++)//将原表数据存储至新表
   temp[loop] = a[loop];
  a = temp;//重新指向
 }
 
 public void push(Item data) {
  if (a.length == N) {//栈满,将栈长修改为原长2倍
   resize(a.length * 2);
  }
  a[N++] = data;//压栈
  }
 
 public Item pop() {
  Item data = a[--N];//弹栈,并缩短栈的大小
  a[N] = null;//将原栈顶赋null,防止对象游离
  if (N > 0 && N == a.length / 4)//顺序表利用率低于25%,缩短顺序表长度
    resize(a.length / 2);
  return data;//返回原栈顶元素
 }
}

链表实现栈

链表

链表是一种递归的数据结构,它或者为空(null),或者是指向一个结点(node)的引用,该结点含有一个泛型的元素和一个之相关另一条链表的引用。
–摘自《Algorithms》第四版(Robert Sedgewick & Kevin Wayne)

下面我们依旧使用图示来理解。

首先一个结点,拥有两个区域,一个存储数据,一个存储指向。
结点

我们先使用一个结点的next域存储另一个结点,那么就得到了一个链式存储单元,前一个结点的next域指向下一个结点。
链表

当我们将最后一个结点的next域指向null,我们就得到了一个链表。
链表

链表结点的定义

 private class Node {  
  Item data;
  Node next;
  
  public Node() {//创建结点
   
  }
  
  public Node(Item data, Node next) {//创建指定数据以及指向的结点
   this.data = data;
   this.next = next;
  }
 }

链式栈的实现原理

首先创建一个结点,并赋值5,此时该结点就是栈顶元素,用First指向它。
5入栈

此时再进入一个2,那么栈顶元素更新,变为2,First也就重新指向了2。
2入栈

此时再存入一个3,操作与上面的方法一致。
3入栈

弹栈时,则只需将头结点弹出,First指向2(栈顶指向新的结点),此时就恢复到了第二步。
3弹栈

也就是说,First一直在更新为栈顶元素。
在你将一个元素压栈,First也就指向你的新结点,而原来的栈顶结点则被新结点的next保存。
在你将一个元素弹栈,First也就指向你的新结点,而原来的站定结点则被弹出并返回。

这就是链式栈的实现原理。

栈的API

下压栈(FILO)
public cllass LinkStack<Item>
LinkStack()创建一个空栈
void push(Item data)压栈
Item pop()弹栈
boolean isEmpty()判断栈是否为空
int size()栈中元素数量
1.LinkStack()
 public LinkStack() { }
2.int size()
 public int size() {
  return N;
 }
3.boolean isEmpty()
 public boolean isEmpty() {
  return first == null;
 }
4.void push(Item data)
 public void push(Item data) {
  Node oldFirst = first;//存储原来的栈顶结点
  first = new Node();//构建新的栈顶结点
  first.data = data;//压栈
  first.next = oldFirst;//栈顶结点指向旧栈顶结点
  N++;//栈的大小变大
 }
5.Item pop()
 public Item pop() {
  Item data = first.data;//弹栈
  first = first.next;//栈顶变化
  N--;//栈的大小缩小
  return data;
 }

完整代码如下:

public class LinkStack<Item>{
 private Node first;
 private int N;
 
 public LinkStack() { }
 
 private class Node {  
  Item data;
  Node next;
  
  public Node() {//创建结点
   
  }
  
  public Node(Item data, Node next) {//创建指定数据以及指向的结点
   this.data = data;
   this.next = next;
  }
 }
 
 public boolean isEmpty() {
  return first == null;
 }
 
 public int size() {
  return N;
 }
 
 public void push(Item data) {
  Node oldFirst = first;//存储原来的栈顶结点
  first = new Node();//构建新的栈顶结点
  first.data = data;//压栈
  first.next = oldFirst;//栈顶结点指向旧栈顶结点
  N++;//栈的大小变大
 }
 
 public Item pop() {
  Item data = first.data;//弹栈
  first = first.next;//栈顶变化
  N--;//栈的大小缩小
  return data;
 }
}
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值