栈(Stack)是限制在表的一端进行插入和删除运算的线性表,通常称插入、删除的这一端
为栈顶(Top),另一端为栈底(Bottom)。当表中没有元素时称为空栈。
假设栈S=(a1,a2,a3,…an),则a1称为栈底元素,an为栈顶元素。栈中元素按a1,a2,a3
,…an的次序进栈,退栈的第一个元素应为栈顶元素。因此,栈的修改是按后进先出的原
则进行的,所以,栈称为后进先出(先进后出)表(LIFO,FILO)。
本质是顺序表,并且只能在链表的一端进行增加和删除
(1)定义
栈是线性表的特例,因此线性表的存储结构对栈也适应。
栈的顺序存储结构简称为顺序栈,可用数组来实现顺序栈。因为栈底位置是固定不变的,
所以可以将栈底位置设置在数组的两端的任何一个端点;栈顶位置是随着进栈和退栈操作
而变化的,故需用一个整型变量top(栈顶指针)来指出栈顶。
(2)实现的基本操作
package chapter3.stack;
import java.util.Arrays;
/**
* 顺序栈
* @author Administrator
*
*/
public class SeqStack {
/**
* 1.成员变量
*/
private Object[] elementData;//定义一个数组用于保存顺序栈的元素,最后一个元素是栈顶
private int capacity;//保存数组的长度,栈的容量
private int top ;//栈顶指针,保存栈顶元素的下标,-1表示没有元素
/**
* 2.构造方法:初始化栈
*/
public SeqStack() { //不指定栈的初始容量,采用默认的初始容量
capacity = 10 ;
elementData = new Object[capacity];//默认长度为10的栈
top=-1;
}
public SeqStack(int initSize) { //指定栈的初始容量
capacity = initSize ;
elementData = new Object[capacity];//默认长度为10的栈
top = -1 ;
}
public SeqStack(Object element) { //不指定栈的初始容量,用默认的,并且存一个元素
this();
top++;
elementData[top] = element;
}
public SeqStack(Object element, int initSize) { //指定栈的初始容量,并且指定第一个元素
this(initSize);
elementData[top] = element;
}
/**
* 判断是否有空间可以存放元素:没有的话进行数据扩容
*/
private void ensureCapacity(int minCapacity) {
//如果数组的原有长度小于目前所需的长度
int oldCapacity = capacity;
if (minCapacity > oldCapacity) { //判断原来的容量是否满足最小容量要求
int newCapacity = (oldCapacity * 3) / 2 + 1; //依据某个规则进行扩容
if (newCapacity < minCapacity)
newCapacity = minCapacity; //根据需求进行扩容
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
capacity = newCapacity ;
}
}
/**
* 判断是否是空栈
* @return
*/
public boolean isEmpty() {
return top==-1;
}
/**
* 3.进栈
* @param element
*/
public void push(Object element){
ensureCapacity(top + 2);//top是位置,从0开始,原来的元素个数是top+1
//这里表示加入一个元素后所需的容量
elementData[top+1] = element;
top++;
}
/**
*4.出栈
*/
/**
* @Title: pop
* @Description: 出栈
* @return
*/
public Object pop() {
if (!isEmpty()) {
Object oldValue = (Object) elementData[top];
//释放栈顶元素
elementData[top] = null;
top-- ;
return oldValue;
} else {
throw new IndexOutOfBoundsException("空栈异常");
}
}
/**
* 5.取栈顶元素,不删除
*/
public Object peek() {
if (!isEmpty()) {
return (Object) elementData[top];
} else {
return null ;
}
}
/**
* 6.清空顺序栈
*/
/**
* @Title: clear
* @Description: 清空顺序栈
*/
public void clear() {
//将底层数组所有元素赋为null
Arrays.fill(elementData, null);
top = -1 ;
}
/**
*7.打印栈,输出栈的内容
*/
public String toString() {
if (top==-1) {
return "[]";
} else {
StringBuilder sb = new StringBuilder("[");
for (int i =top;i > -1; i--) {
sb.append(elementData[i].toString() + ", ");
}
int len = sb.length();
return sb.delete(len -2, len).append("]").toString(); //先删掉最后一个逗号(len-1位置)
}
}
/**
* 8.测试
* @param args
*/
public static void main(String[] args){
SeqStack st = new SeqStack(10) ;
for(int i = 0 ; i < 30 ; i++){
st.push(i);
}
System.out.println(st.top+1);
System.out.println(st.toString());
}
}
本质是单向非循环链表,并且只能在链表的一端进行增加和删除
(1)定义
栈的链式存储结构称为链栈,插入和删除操作仅限制在链头位置上进行。栈顶指针就是链
表的头指针。
(2)实现的基本操作
package chapter3.stack;
/**
* 链栈
* @author Administrator
*
*/
public class LinkStack {
/**
* 1.节点
*/
private class Node{
private Object data; //保存节点的数据
private Node next; //指向下个节点的引用
public Node(){
}
public Node(Object data, Node next){
this.data = data;
this.next = next;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
}
/**
* 2.成员变量
*/
private Node top; //保存该链栈的栈顶元素
/**
* 3.构造方法:初始化栈
*/
public LinkStack(){ //空栈
top = null;
}
public LinkStack(Object element) { //有一个元素的栈
this() ;
top = new Node(element , null);
}
/**
* 判断栈是否是空的
* @return
*/
private boolean isEmpty() {
return top==null ;
}
/**
* @Title: push
* @Description: 4.入栈
* @param element
*/
public void push(Object element){
if(isEmpty()){
top = new Node(element , null);
}else{
Node node= new Node(element , null);
node.next = top ;
top = node ;
}
}
/**
* @Title: pop
* @Description:5. 出栈
* @return
*/
public Object pop(){
if(isEmpty()){
throw new IndexOutOfBoundsException("空栈异常");
}else{
Node oldTop = top;
top = top.next;
oldTop.next = null;
return oldTop.data;
}
}
/**
* 6.访问栈顶元素,但是不删除
*/
public Object peek(){
return top.data;
}
/**
* 7.清空链栈
*/
public void clear() {
top = null;//将栈所有元素赋为null
}
/**
* 8.输出栈元素
*/
public String toString() {
//链栈为空链栈时
if (isEmpty()) {
return "[]";
} else {
StringBuilder sb = new StringBuilder("[");
for (Node current = top ; current != null ; current = current.next ) {
sb.append(current.data.toString() + ", ");
}
int len = sb.length();
return sb.delete(len - 2 , len).append("]").toString();
}
}
/**
* 9.测试
* @param args
*/
public static void main(String[] args){
LinkStack lt = new LinkStack() ;
for(int i=0 ; i<10 ; i++){
lt.push(i) ;
}
System.out.println(lt);
}
}
队列(Queue)也是一种运算受限的线性表。它只允许在表的一端进行插入,而在另一端进行删除
。允许删除的一端称为队头(front),允许插入的一端称为队尾(rear)。例如:排队购物。操作系
统中的作业排队。先进入队列的成员总是先离开队列。因此队列亦称作先进先出(First In First Out)
的线性表,简称FIFO表。当队列中没有元素时称为空队列。在空队列中依次加入元素a1,a2,…an之
后,a1是队头元素,an是队尾元素。显然退出队列的次序也只能是a1,a2,…an ,也就是说队列的修
改是依先进先出的原则进行的。
本质是顺序表,并且允许在顺序表的一端插入(队尾),在另一段删除(队头)
(1)定义
队列的顺序存储结构称为顺序队列,用一个向量空间来存放当前队列中的元素。由于队列
的队头和队尾的位置是变化的,因而要设两个指针分别指示队头和队尾元素在队列中的位
置,它们的初始值在队列初始化时均应置为-1。入队时将新元素插入所指的位置,然后队
尾指针加1。出队时,删去所指的元素,然后头指针加1并返回被删元素。由此可见,当
头尾指针相等时队列为空。注意,在非空队列里,头指针始终指向队头元素的前一个位
置,而尾指针始终指向队尾元素
(2)操作的基本实现
package chapter3.queue;
import java.util.Arrays;
/**
* 顺序队列
* @author Administrator
*
*/
public class SeqQueue {
/**
* 1.成员变量
*/
private Object[] elementData; //定义一个数组用于保存顺序队列的元素
private int capacity; //队列的容量
private int front ;//对头指针,不指向元素的时候为-1 ,指向的是对头元素的前一个位置
private int rear; //队尾指针,不指向元素的时候为-1,指向队尾元素
/**
* 2.构造方法:初始化队列
*/
public SeqQueue() //空队列,使用队列默认容量
{
capacity = 10;
elementData = new Object[capacity];
front = -1 ;
rear = -1 ;
}
public SeqQueue(int initSize){//空队列,指定队列容量
capacity =initSize ;
elementData = new Object[capacity];
front = -1 ;
rear = -1 ;
}
public SeqQueue(Object element) //有一个元素的队列,使用队列默认容量
{
this();
rear++ ;
elementData[rear] = element;
}
/**
* 以指定长度的数组来创建顺序队列
* @param element 指定顺序队列中第一个元素
* @param initSize 指定顺序队列底层数组的长度
*/
public SeqQueue(Object element , int initSize) //有一个元素的队列,指定队列初始容量
{
this(initSize) ;
rear++ ;
elementData[rear] = element;
}
/**
* 判断是否有空间可以存放元素:没有的话进行数据扩容
*/
private void ensureCapacity(int minCapacity) {
//如果数组的原有长度小于目前所需的长度
int oldCapacity = capacity;
if (minCapacity > oldCapacity) { //判断原来的容量是否满足最小容量要求
int newCapacity = (oldCapacity * 3) / 2 + 1; //依据某个规则进行扩容
if (newCapacity < minCapacity)
newCapacity = minCapacity; //根据需求进行扩容
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
capacity = newCapacity ;
}
}
//判断顺序队列是否为空队列
private boolean empty()
{
return rear == front;
}
/**
* 3.入队
*rear指向的是最后一个元素
*/
public void add(Object element)
{
ensureCapacity(rear+2);//near是位置,从0开始,rear+1是原来所需的容量
//这里表示加入一个元素后所需的容量
elementData[rear+1] = element;
rear++ ;
}
/**
* 4.出队
* font指向的是对头元素的前面一个位置
*/
public Object remove()
{
if (empty())
{
throw new IndexOutOfBoundsException("空队列异常");
}
//保留队列的rear端的元素的值
Object oldValue = (Object)elementData[front+1];
//释放队列的rear端的元素
elementData[front+1] = null;
front++ ;
return oldValue;
}
/**
* 5.取对头元素,不删除
*/
public Object element()
{
if (empty())
{
return null ;
}
return (Object)elementData[front+1];
}
/**
* 6.清空顺序队列
*/
public void clear()
{
//将底层数组所有元素赋为null
Arrays.fill(elementData , null);
front =-1;
rear = -1;
}
/**
* 7.打印队列,输出
*/
public String toString()
{
if (empty())
{
return "[]";
}
else
{
StringBuilder sb = new StringBuilder("[");
for (int i = front+1 ; i <= rear ; i++ )
{
sb.append(elementData[i].toString() + ", ");
}
int len = sb.length();
return sb.delete(len - 2 , len).append("]").toString();
}
}
/**
* 8.测试
*/
public static void main(String[] args){
SeqQueue sq = new SeqQueue() ;
for(int i=0; i<20; i++){
sq.add(i) ;
}
System.out.println(sq);
}
}
本质是单向循环顺序表,表的最后一个元素的下一个元素是第一个元素
(1)定义
因为删除头元素后,会出现很多空闲的空间,为充分利用向量空间。克服上述现象,
把向量空间想象为一个首尾相接的圆环,并称这种向量为循环向量,存储在其中的
队列称为循环队列(Circular Queue)。在循环队列中进行入队操作时,尾指针仍要加
1,出队操作,对头加1,朝前移动。只不过当头尾指针指向向量上界(MaxSize-1)时
,其加1操作的结果是指向向量的下界0。显然,因为循环队列元素的空间可以全被
利用,除非向量空间真的被队列元素全部占用,否则不会上溢。因此,除一些简单
的应用外,真正实用的顺序队列是循环队列。入队时尾指针向前追赶头指针,出队
时头指针向前追赶尾指针,故队空和队满时头尾指针均相等。因此,我们无法通过
front=rear来判断队列“空”还是“满”。解决此问题的方法至少有三种:
其一:另设一个布尔变量以区分队列的空和满;
其二:少用一个元素的空间,约定入队前,测试尾指针在循环意义下加1后是否等于头指针,
若相等则认为队满;(但此时实际还有一个空位置)
其三 :是使用一个计数器记录队列中元素的总数(实际上是队列长度)。
通常采用第二种方案,并且注意对头指针指向队头元素的前面一个位置,尾指针指向
队尾元素
(2)基本操作的实现
package chapter3.queue;
import java.util.Arrays;
/**
* 循环队列
* @author Administrator
*
*/
public class CycleQueue {
/**
* 1.成员变量
*/
private int capacity; //保存数组的长度。
private Object[] elementData; //定义一个数组用于保存循环队列的元素
private int front ;//对头指针,不指向元素的时候为-1 ,指向的是对头元素的前一个位置
private int rear; //队尾指针,不指向元素的时候为-1,指向队尾元素
/**
* 2.构造方法:初始化队列
*/
public CycleQueue() //空队列,指定队列容量
{
capacity =10;
elementData = new Object[capacity];
front = -1 ;
rear = -1 ;
}
public CycleQueue(int initSize){//空队列,指定队列容量
capacity =initSize ;
elementData = new Object[capacity];
front = -1 ;
rear = -1 ;
}
public CycleQueue(Object element) // 有一个元素的队列,使用队列默认容量
{
this();
rear++;
elementData[rear] = element;
}
/**
* 以指定长度的数组来创建循环队列
* @param element 指定循环队列中第一个元素
* @param initSize 指定循环队列底层数组的长度
*/
public CycleQueue(Object element , int initSize) //有一个元素的队列,指定队列初始容量
{
this(initSize) ;
rear++ ;
elementData[rear] = element;
}
//判断循环队列是否为空队列
private boolean empty()
{
//rear==front且front处的元素为null
return rear == front
&& elementData[front] == null;
}
/**
* 可以将这个队列改造成队满的时候自动扩容
*/
private void ensureCapacity(int minCapacity) {
}
/**
* 3.入队
*/
//插入队列
public void add(Object element)
{
if (rear == front
&& elementData[front+1] != null)
{
new Exception("队列已满异常!") ;
}
//如果rear已经到头,那就转头
rear = ((rear+1) == capacity ? 0 :( rear+1));
elementData[rear] = element;
}
/**
* 4.出队
*/
public Object remove()
{
if (empty())
{
throw new IndexOutOfBoundsException("空队列异常");
}
front = ((front+1) == capacity ? 0 :( front+1)); //如果front已经到头,就转头
Object oldValue = (Object)elementData[front];
//释放队列的rear端的元素
elementData[front] = null;
return oldValue;
}
/**
* 5.返回栈顶元素,但是不删除
*/
//返回队列顶元素,但不删除队列顶元素
public Object element()
{
if (empty())
{
return null ;
}
front = ((front+1) == capacity ? 0 :( front+1)); //如果front已经到头,就转头
Object oldValue = (Object)elementData[front];
//释放队列的rear端的元素
return oldValue;
}
/**
* 7.清空队列
*/
//清空循环队列
public void clear()
{
//将底层数组所有元素赋为null
Arrays.fill(elementData , null);
front = -1;
rear = -1;
}
/**
* 8.打印队列内容
*/
public String toString()
{
if (empty())
{
return "[]";
}
else
{
//如果front < rear,有效元素就是front到rear之间的元素
if (front < rear)
{
StringBuilder sb = new StringBuilder("[");
for (int i = front+1 ; i <=rear ; i++ )
{
sb.append(elementData[i].toString() + ", ");
}
int len = sb.length();
return sb.delete(len - 2 , len).append("]").toString();
}
//如果front >= rear,有效元素为front->capacity之间、0->front之间的
else
{
StringBuilder sb = new StringBuilder("[");
for (int i = front+1; i < capacity ; i++ )
{
sb.append(elementData[i].toString() + ", ");
}
for (int i = 0 ; i <=rear ; i++)
{
sb.append(elementData[i].toString() + ", ");
}
int len = sb.length();
return sb.delete(len - 2 , len).append("]").toString();
}
}
}
/**
* 9.测试
*/
public static void main(String[] args){
CycleQueue cq = new CycleQueue() ;
for(int i=0 ; i<10;i++){
cq.add(i) ;
}
System.out.println(cq);
}
}
本质是单向非循环链表,允许在链表的一端插入,另一端删除
(1)定义
链式存储结构作为为链队列,它是限制仅在表头删除和表尾插入的单链表。显然仅有单链表
的头指针不便于在表尾做插入操作,为此再增加一个尾指针,指向链表的最后一个结点,
注意此时的头指针指向的是头节点,和顺序队列不同
(2)基本操作的实现
package chapter3.queue;
/**
* 链队列
* @author Administrator
*
*/
public class LinkQueue {
/**
* 1.节点
*/
//定义一个内部类Node,Node实例代表链队列的节点。
private class Node
{
//保存节点的数据
private Object data;
//指向下个节点的引用
private Node next;
//无参数的构造器
public Node()
{
}
//初始化全部属性的构造器
public Node(Object data , Node next)
{
this.data = data;
this.next = next;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
}
/**
* 2.成员变量
*/
private Node front; //指向头节点
private Node rear; //指向尾节点
/**
* 3.构造方法:初始化链队列
*/
//创建空链队列
public LinkQueue()
{
//空链队列,front和rear都是null
front = null;
rear = null;
}
//以指定数据元素来创建链队列,该链队列只有一个元素
public LinkQueue(Object element)
{
Node node = new Node(element , null);
//只有一个节点,front、rear都指向该节点
front = node ;
rear = node ;
}
/**
* 判断队列是否是空的
* @return
*/
private boolean isEmpty() {
return front==null&& rear==null ;
}
/**
* 4.入队
*/
public void add(Object element)
{
//如果该链队列还是空链队列
if (isEmpty())
{
front = new Node(element , null);
rear= front;
}
else
{
//创建新节点
Node newNode = new Node(element , null);
//让尾节点的next指向新增的节点
rear.next = newNode;
//以新节点作为新的尾节点
rear = newNode;
}
}
/**
* 5.出队
*/
public Object remove()
{
if(isEmpty()){
throw new IndexOutOfBoundsException("空队列异常");
}else{
Node oldFront = front;
front = front.next;
oldFront.next = null;
return oldFront.data;
}
}
/**
* 6.取对头元素,但是不删除
*/
//访问链式队列中最后一个元素
public Object element()
{
if(isEmpty()){
return null ;
}
return rear.data;
}
/**
* 7.清空队列
*/
//清空链队列
public void clear()
{
//将front、rear两个节点赋为null
front = null;
rear = null;
}
/**
* 8.打印队列内容
*/
public String toString()
{
//链队列为空链队列时
if (isEmpty())
{
return "[]";
}
else
{
StringBuilder sb = new StringBuilder("[");
for (Node current = front ; current != null ; current = current.next )
{
sb.append(current.data.toString() + ", ");
}
int len = sb.length();
return sb.delete(len - 2 , len).append("]").toString();
}
}
/**
* 9.测试
*/
public static void main(String[] args){
LinkQueue lq = new LinkQueue() ;
for(int i=0;i<10;i++){
lq.add(i);
}
System.out.println(lq);
}
}