接着上一篇博客,总的来说,线性表的两种结构其实是后面其他数据结构的基础。也就是说,栈与队列也与线性表有着密切关系。
1.栈的定义:
栈:栈是限定仅在表尾进行插入和删除操作的线性表。
首先,他是一个线性表,也就是说,栈元素具有线性关系,即前驱后继关系。只不过他是一种特殊的线性表而已。
最先进栈的元素,不一定是最后出栈。栈对线性表的插入和删除的位置进行了限制,并没有对元素进出的时间进行限制,也就是说,在不是所有元素都进栈的情况下,事先进去的元素也可以出栈,只要保证是栈顶元素出栈就可以。
2.栈的顺序存储结构
栈的顺序存储其实也是线性表顺序存储的简化,称之为顺序栈。定义一个top变量来指示栈顶元素在数组中的位置,当top等于-1时(数组下标),称之为空栈。
进栈和出栈的代码实现:
class Stack {
private int final MAXSIZE = 10;
private Object[] array;
public stack() {
array = new Object[MAXSIZE];
top = -1;
}
//扩容方法
private void resize() {
Object newArray = new Object[MAXSIZE*2];
for(int i=0;i<array.length;i++){
newArray[i] = array[i];
}
array = newArray;
}
//进栈
public void push(Object obj) {
if( top == MAXSIZE-1)
resize();
array[++top] = obj;
}
//出栈
public Object pop() {
if(top == -1)
system.out.println("此栈已空!");
Object temp = array[top];
array[top--] = null;
return temp;
}
}
3.两栈共享空间
数组有两个端点,两个栈有两个栈底,让一个栈的栈底为数组的始端,即下标为0处,另一个栈为数组的末端,即下标为n-1处,这样,两个栈如果增加元素,就是两端点向中间延伸。
对于两栈空享空间的push和pop方法,我们只需判断是栈1还是栈2,其余与普通顺序栈的操作类似。
使用这样的数据结构,通常都是两个栈的空间需求有相反关系时,也就是一个栈增长时另一个栈在缩短的情况,这样使用两栈空间共享的方法才有比较大的意义。否则,两栈都在增长,很快就会溢出了。而且,如果不是相同数据类型的栈,这种办法不但不能更好地处理问题。反而会使问题更加复杂。
4.栈的链式存储结构
栈的链式存储结构,简称链栈。
比较常规的实现方法,是把栈顶放在单链表的头部,通常对于链栈来说,是不需要头节点的。当top=null时,链栈为空。
链栈代码实现:
public class LinkStack<T> {
private class Node {
private T data;// 保存节点的数据元素
private Node next;// 保存下一个节点的引用
public Node() {
}
public Node(T data, Node next) {
this.data = data;
this.next = next;
}
}
private Node top;// 存放栈顶节点
private int size = 0;// 存放栈中已有的元素个数
// 创建空链栈
public LinkStack() {
top = null;
}
// 返回链栈的长度
public int length() {
return size;
}
// 进栈
public void push(T elemnt) {
// 让top指向新节点,新节点的next指向原来的top
top = new Node(elemnt, top);
size++;
}
// 出栈
public T pop() {
// 若当前为空栈,则返回null
if (size == 0) {
return null;
}
Node oldTop = top;
// 让top指向原栈顶的下一个节点
top = top.next;
// 释放原栈顶元素的引用
oldTop.next = null;
size--;
return oldTop.data;
}
// 获取栈顶元素
public T getTop() {
// 若当前为空栈,则返回null
if (size == 0) {
return null;
}
return top.data;
}
// 判断是否为空
public boolean isEmpty() {
return size == 0;
}
// 清空栈
public void clear() {
top = null;
size = 0;
}
}
5.队列的定义
队列:队列是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。
6.队列的顺序存储结构
为了解决队列顺序存储结构假溢出的问题,我们把头尾相接,构成循环队列。
循环队列代码实现:
public class LoopQueue<T>{
private int DEFAULT_SIZE = 10;
private int capacity;//保存数组的长度
private Object[] elementData;//定义一个数组用于保存循环队列的元素
private int front = 0;//队头
private int rear = 0;//队尾
//以默认数组长度创建空循环队列
public LoopQueue() {
capacity = DEFAULT_SIZE;
elementData = new Object[capacity];
}
/**
* 以指定长度的数组来创建循环队列
* @param element 指定循环队列中第一个元素
* @param initSize 指定循环队列底层数组的长度
*/
public LoopQueue(T element, int initSize) {
this.capacity = initSize;
elementData = new Object[capacity];
elementData[0] = element;
rear++;
}
//获取循环队列的大小
public int size() {
if (isEmpty()) {
return 0;
}
return rear > front ? rear - front : capacity - (front - rear);
}
//插入队列
public void add(T element) {
if (rear == front && elementData[front] != null) {
throw new IndexOutOfBoundsException("队列已满的异常");
}
elementData[rear++] = element;
//如果rear已经到头,那就转头
rear = rear == capacity ? 0 : rear;
}
//移除队列
public T remove() {
if (isEmpty()) {
throw new IndexOutOfBoundsException("空队列异常");
}
//保留队列的rear端的元素的值
T oldValue = (T) elementData[front];
//释放队列的rear端的元素
elementData[front++] = null;
//如果front已经到头,那就转头
front = front == capacity ? 0 : front;
return oldValue;
}
//返回队列顶元素,但不删除队列顶元素
public T element() {
if (isEmpty()) {
throw new IndexOutOfBoundsException("空队列异常");
}
return (T) elementData[front];
}
//判断循环队列是否为空队列
public boolean isEmpty() {
//rear==front且rear处的元素为null
return rear == front && elementData[rear] == null;
}
//清空循环队列
public void clear() {
//将底层数组所有元素赋为null
Arrays.fill(elementData, null);
front = 0;
rear = 0;
}
public String toString() {
if (isEmpty()) {
return "[]";
} else {
//如果front < rear,有效元素就是front到rear之间的元素
if (front < rear) {
StringBuilder sb = new StringBuilder("[");
for (int i = front; 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; 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();
}
}
}
}
7.队列的链式存储结构
队列的链式存储结构,其实就是线性表的单链表,只不过他只能尾进头出而已,简称之为链队列。
public class LinkQueue<T>{
//定义一个内部类Node,Node实例代表链队列的节点。
private class Node {
private T data;//保存节点的数据
private Node next;//指向下个节点的引用
//无参数的构造器
public Node() {
}
//初始化全部属性的构造器
public Node(T data, Node next) {
this.data = data;
this.next = next;
}
}
private Node front;//保存该链队列的头节点
private Node rear;//保存该链队列的尾节点
private int size;//保存该链队列中已包含的节点数
/**
* <p>Title: LinkQueue </p>
* <p>Description: 创建空链队列 </p>
*/
public LinkQueue() {
//空链队列,front和rear都是null
front = null;
rear = null;
}
/**
* <p>Title: LinkQueue </p>
* <p>Description: 以指定数据元素来创建链队列,该链队列只有一个元素</p>
*/
public LinkQueue(T element) {
front = new Node(element, null);
//只有一个节点,front、rear都指向该节点
rear = front;
size++;
}
/**
* @Title: size
* @Description: 获取顺序队列的大小
* @return
*/
public int size() {
return size;
}
/**
* @Title: offer
* @Description: 入队
* @param element
*/
public void offer(T element) {
//如果该链队列还是空链队列
if (front == null) {
front = new Node(element, null);
rear = front;//只有一个节点,front、rear都指向该节点
} else {
Node newNode = new Node(element, null);//创建新节点
rear.next = newNode;//让尾节点的next指向新增的节点
rear = newNode;//以新节点作为新的尾节点
}
size++;
}
/**
* @Title: poll
* @Description: 出队
* @return
*/
public T poll() {
Node oldFront = front;
front = front.next;
oldFront.next = null;
size--;
return oldFront.data;
}
/**
* @Title: peek
* @Description: 返回队列顶元素,但不删除队列顶元素
* @return
*/
public T peek() {
return rear.data;
}
/**
* @Title: isEmpty
* @Description: 判断顺序队列是否为空队列
* @return
*/
public boolean isEmpty() {
return size == 0;
}
/**
* @Title: clear
* @Description: 清空顺序队列
*/
public void clear() {
//将front、rear两个节点赋为null
front = null;
rear = null;
size = 0;
}
}
总的来说,在可以确定队列长度最大值的情况下,建议用循环队列,如果无法预估队列的长度时,使用链队列。
栈和队列,都是特殊的线性表,只不过对插入和删除操作做了限制。
栈通过两栈空间共享,队列通过循环队列来解决线性表的顺序存储结构的弊端。他们也都可以通过链式存储结构来实现,实现原则上于线性表基本相同。