栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。栈最大的特点就是先进后出
上面是栈存取数据的示意图,栈主要分为两种结构:基于数组实现的数组栈和基于链表实现的链栈,总的来说都是栈结构,实现的都是栈的基本操作,而栈的操作需要具体的结构去实现,因此数组栈和链栈都是实现的栈接口,我们先定义一个栈接口:
interface Stack {
public abstract void push(int e); //入栈操作
public abstract int pop(); //弹栈
public abstract int length(); //有效元素个数
public abstract boolean isEmpty(); //判空操作
}
数组栈/顺序栈:由数组实现的,但是不提供角标的访问,只在栈顶进行操作。
/*
基于数组实现的栈
*/
class ArrayStack implements Stack{
private static int DEFAULT_SIZE=10; //默认容量
private int top; //栈顶指针
private int[] data; //存储元素的容器用数组存储
//创建一个栈使用默认容量
public ArrayStack(){
this(DEFAULT_SIZE);
}
//创建一个栈使用用户指定容量capacity
public ArrayStack(int capacity){
super();
this.top=-1; //初始化栈顶元素
this.data=new int[capacity]; //初始化容量
}
//入栈一个元素e
public void push(int e){
if(top==data.length-1){ //入栈前要判满,如果栈顶指针达到容量最大,表示无法入栈
System.out.println("栈已满!无法入栈元素");
}
data[++top]=e; //否则先将栈顶指针先向上移动一个,将元素添加到该指针位置
}
//出栈一个元素
public int pop(){
if(isEmpty()){ //出栈前先判空
System.out.println("栈已空!无法出栈元素"); //栈内为空,没有元素
return -1;//特殊含义 表示错误
}
return data[top--]; //栈不为空,先将元素弹栈,再将栈顶指针下移
}
//判断栈是否为空
public boolean isEmpty(){
return this.top==-1; //如果当前栈顶元素为空,为true否则为false
}
//获取有效元素的个数
public int length(){
return this.top+1; //获取当前栈顶指针加一,因为是用数组实现的
}
public String toString(){ //以字符串形式打印当前栈内元素
StringBuilder sb=new StringBuilder();
sb.append('[');
for(int i=0;i<=top;i++){
sb.append(data[i]);
if(i==top)
return sb.append(']').toString();
sb.append(',');
sb.append(' ');
}
return "[]";
}
//获取当前栈的总容量
public int getCapacity(){
return data.length; //总容量就是数组的大小
}
}
链栈,是由链表实现的,链表由一系列结点(链表中每一个元素称为结点)组成,每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。这里是链栈结构,则存取完全可以根据栈结构对头结点进行操作。
class LinkedStack extends Stack{ //单向链表
private Node head; //链表的头结点
private int size; //链表长度
public LinkedStack(){
this.head=new Node();
this.size=0;
}
//加入结点是不用判满的,因为链表没有容量限制
public void push(int e){ //在头结点后添加一个新的元素结点
Node n=new Node(); //新创建一个结点
n.data=e; //将创建的结点中的数据域用来存放传入的元素
n.next=head.next; //将创建的结点中的指针域用来存放当前头结点的下一个结点的地址
head.next=n; //然后将新创建的结点的地址赋给头结点的指针域
//head.next=new Node(e,head.next); 上面三行代码可简化成这一句
size++; //链表长度加一
}
public int pop(){ //元素出栈
if(isEmpty()){ //出栈判空
System.out.println("栈已空!不能出栈元素!");
return -1;
}
Node n=head.next; //元素不为空,将头元素的下一个元素提取出来
head.next=n.next; //将头元素下一个结点的下一个结点的地址赋给头元素的指针域
n.next=null; //将头元素的下一个结点的指针域置为null,与原链表断离
size--; //相应的链表长度减一
return n.data; //将删除的结点的数据返回
}
public int length(){ //获取链表的长度
return size; //直接返回size,因为这个变量记录了链表的长度
}
public boolean isEmpty(){ //判空
return size==0&&head.next==null; //如果链表长度为0和头结点的后面没有其他结点,则为空
}
public String toString(){ //获取链表得字符串形式输出
if(isEmpty()){
return "[]";
}
Node p=head.next; //定义一个结点变量存放头结点的下一个结点的地址
StringBuilder sb=new StringBuilder();
sb.append('[');
while(true){
sb.append(p.data); //将该结点的数据域添加到字符串中
if(p.next==null){
sb.append(']');
break;
}else{
sb.append(", ");
p=p.next; //再将下一个结点的地址赋给当前结点变量
}
}
return sb.toString();
}
//内部类:在类里面定义的类
private class Node{ //结点类
int data; //数据域
Node next; //指针域
Node(){
this(0,null);
}
Node(int data,Node next){ //带参的构造函数
this.data=data;
this.next=next;
}
}
}