栈(Java)
栈
前言:博主第一次写博客,用词或一些概念上可能会出现一些错误,希望大家指出来。
下压栈(LIFO)
下压栈(简称栈)是一种具有后进先出特点的集合了类型。
栈的应用性很广泛,使用栈可以解决括号匹配问题,迷宫问题,表达式求值这类问题。
我们先用图形来解释一下什么是“栈”吧。
首先我们假设这里有一个空箱子(空栈),我们手上有3本书,Java,C++,以及PHP。
如果我们想将一本java书放入,需要怎么做呢?
其实非常简单,直接放进入就好了。(压栈,也叫入栈)
当我们依次再将C++、PHP的书放进去,就会形成下面的状态。
我们已经成功的将三本书放入,接下来我们需要考虑如何取出目标书籍。
比如:当我们需要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指向它。
此时再进入一个2,那么栈顶元素更新,变为2,First也就重新指向了2。
此时再存入一个3,操作与上面的方法一致。
弹栈时,则只需将头结点弹出,First指向2(栈顶指向新的结点),此时就恢复到了第二步。
也就是说,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;
}
}