栈
一:定义
栈是限定仅在表尾(栈顶)进行插入和删除操作的线性表。
允许插入和删除的一端称为栈顶,另一端称为栈底,不含任何数据元素的栈称为空栈。
栈又称为后进先出的线性表,简称LIFO结构。
栈的插入操作,叫作进栈,也称压栈、入栈。
栈的删除操作,叫作出栈,也有的叫作弹栈。
二:抽象数据类型
- InitStack (*S):初始化操作,建立一个空栈S。
- DestroyStack ( *S ):若栈存在,则鎖毁它。
- ClearStack ( *S ):将栈清空。
- StackEmpty ( S ):若栈为空,返田true,否则返田false。
- GetTop (S,*e):若栈存在且非空,用e返回S的栈顶元素。
- Push (*S,*e):若栈S存在,插入新元素e到栈S中并成为栈顶元素。
- Pop ( *S,,*e ):刪除栈S中栈顶元素,并用e返回其值。
- StackLength(S):返回栈S的元素个教。
三:存储结构
1. 顺序存储结构
(1)定义:
栈的顺序存储就是线性表顺序存储的简化,将下标为0的一端作为栈底。
(2)属性:
- top:栈顶元素的位置
- StackSize:栈的最大存储容量
(3)操作:
进栈:
- 判断栈是否已满,栈满时无法再进栈。
- top指针加一,并对数组的第top个元素赋值
出栈:
- 判断栈是否空
- 将要删除的栈顶元素赋值给e返回,top指针减一。
(4)实现:
/**
固定容量的栈
*/
public class FixedCapacityStack<T> {
//创建数组保存值
private T[] arr;
//创建顶部指针
private int N = 0;
public FixedCapacityStack(Class<T> tClass,int len){
arr = (T[]) Array.newInstance(tClass, 5);
}
//入栈
public void push(T element) throws IndexOutOfBoundsException{
if(N < arr.length)
arr[N++] = element;
else
throw new IndexOutOfBoundsException("栈满了");
}
//取栈顶元素
public T peek(){
if(N>0)
return arr[N-1];
else
return null;
}
//出栈
public void pop() throws EmptyStackException{
if(N>0)
arr[--N] = null;
else
throw new EmptyStackException();
}
//是否为空栈
public boolean isEmpty(){
return N==0;
}
//栈的大小
public int size(){
return N;
}
}
2. 可动态变化的栈
(1):特点
- 当栈满时,将栈的长度加倍。
- 当栈中元素占四分之一时,将栈的长度压缩为半满状态。
(2):实现
/**
* 可动态变化的栈,实现迭代
* @param <T>:泛型
*/
public class VarietyCapacityStack<T> implements Iterable<T>{
//创建数组保存值
private T[] arr;
//创建顶部指针
private int N = 0;
@Override
public Iterator<T> iterator() {
return new MyIterator();
}
private class MyIterator implements Iterator<T> {
private int i = N;
@Override
public boolean hasNext() {
return i > 0;
}
@Override
public T next() {
return arr[--i];
}
@Override
public void remove() {
arr[--i] = null;
}
}
public VarietyCapacityStack(Class<T> tClass,int len){
arr = (T[]) Array.newInstance(tClass, 5);
}
public void resize(int max){
T[] array = (T[]) new Object[max];
array = Arrays.copyOf(arr,arr.length);
arr = array;
}
//入栈,如果栈满则扩大栈
public void push(T element){
if(N != arr.length)
arr[N++] = element;
else
resize(arr.length*2);
}
//取栈顶元素
public T peek(){
if(N>0)
{
if(N > 0 && N == arr.length/4)
resize(arr.length/2);
return arr[N-1];
}
else
return null;
}
//出栈,如果栈过大则压缩栈
public void pop() throws EmptyStackException {
if(N>0)
{
arr[--N] = null;
if(N > 0 && N == arr.length/4)
resize(arr.length/2);
}
else
throw new EmptyStackException();
}
//是否为空栈
public boolean isEmpty(){
return N==0;
}
//栈的大小
public int size(){
return N;
}
}
3. 相同类型的数据共享栈
(1)定义
两个栈有两个栈底,让一个栈的栈底为数组的始端,即下标为0处,另一个栈为栈的末端,即下标为数组长度n-1处。这样,两个栈如果増加元素,就是两端点向中间延伸。
(2)特点
适合两个栈的空间请求有相反关系
(3)栈空栈满:
- top1 = -1,top2 = 0 栈1为空,此时栈2为满
- top1 = n-1,top2 = n 栈2为空,此时栈1为满
- 当 top1+1 = top2 时,共享栈满
(4)增加
- 判断共享栈是否已满
- 对选择的栈进行增加操作
(5)删除
- 对选择的栈判断是否为空
- 对选择的栈进行删除操作
4. 链式存储结构
把栈顶作为单链表的头部,指向栈底。
(1)进栈(不需要判断满栈)
- 新结点P指向栈顶S的元素
- 栈顶指向新结点P
- 栈的长度增加
(2)出栈
- 判断栈是否为空
- 把栈顶S的数据赋值给e返回
- 栈顶指向S的下一个元素
- 栈的长度减小
(3)实现
/**
* 链栈
* @param <T>
*/
public class LinkedStack<T> {
//头结点
private LinkNode<T> head;
//链表长度
private int size;
public LinkedStack() {
head = new LinkNode<>();
size = 0;
}
public LinkedStack(T ele){
head = new LinkNode<>();
LinkNode<T> node = new LinkNode<>(ele);
head.setNext(node);
size = 1;
}
//获取链表长度
public int getSize() {
return size;
}
//判断链表是否为空
public boolean isEmpty(){
return size == 0;
}
//入栈
public void push(T ele){
LinkNode<T> temp = new LinkNode<>(ele);
LinkNode<T> node = head.getNext();
if(node!=null)
temp.setNext(node);
head.setNext(temp);
size++;
}
//出栈
public LinkNode<T> pop(){
LinkNode<T> node = head.getNext();
if(node != null)
{
LinkNode<T> temp = node.getNext();
head.setNext(temp);
size --;
}
return node;
}
//获取栈顶元素
public LinkNode<T> peek(){
LinkNode<T> node = head.getNext();
return node;
}
}
四:应用
- 递归
- 后缀表达式
- 从左到右遍历表达式,如果是数字,则直接输出。
- 判断符号与栈顶符号的优先级大小,若优先级小(或者是右括号),则栈中所有元素(或者是左括号后的所有元素)出栈并输出,符号进栈。否则,直接进栈。
- 便利完成,则所有元素出栈。
- 四则运算(拿着运算符前面两个数进行运算,然后输出结果)
- 从左到右遍历表达式,如果是数字,则直接进栈。
- 如果是符号,则栈顶两个数字出栈,进行运算后进栈。
队列
一:定义
队列是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。
队列是一种先进先出的线性表,简称FIFO。允许插入的一端称为队尾,允许删除的一端称为队头。
二:抽象数据类型
- InitQueue ( *Q):初始化操作,建立一个空队列Q。
- DestroyQueue ( *Q):若队列Q存在,则销毁它。
- ClearQueue ( *Q ):将队列 Q 清空。
- QueueEmpty(Q):若队列Q为空,返回true,否则返回false。
- GetHead (Q,*e):若队列Q存在且非空,用e返回队列Q的队头元素。
- EnQueue ( *Q, e ):若队列Q存在,插入新元素e到队列Q中并成为队尾元素。
- DeQueue ( *Q, *e ):刪除队列Q中队头元素,并用e返回其值。
- QueueLength(Q):返回队列Q的元素个数
三:存储结构
1. 顺序存储结构(假溢出)
(1)定义:
顺序存储的队列需建立一个大于n的数组,并把队列的所有元素存储在数组的前n个单元,数组下标为0的一端即是队头。
(2)操作:
入队时在队尾直接添加元素。
出队时
- 队列的元素必须存储在数组的前n个单元:所有元素需向前移动,性能不好。
- 队列的元素可以不限制前n个单元:可能造成数组的长度不够而“假溢出”。
/**
* 固定容量的队列
*/
public class FixedCapacityQueue<T> {
private T[] arr;
private int front, rear;
public FixedCapacityQueue(int len){
arr = (T[])new Object[len];
}
//入队列
public void enQueue(T ele) throws ArrayIndexOutOfBoundsException{
if(rear < arr.length)
arr[rear++] = ele;
else
throw new ArrayIndexOutOfBoundsException("队列满了");
}
//出队列
public T deQueue(){
if(front != rear)
return arr[front++];
return null;
}
//队列是否为空
public boolean isEmpty(){
return rear == front;
}
//队列是否为满
public boolean isFull(){
return rear == arr.length;
}
//获得队列容量
public int size(){
return rear - front;
}
}
2. 循环队列
(1)定义:
队列的头尾相接的顺序存储结构称为循环队列。
(2)属性:
- front:指向队头元素的指针
- rear:指向队尾元素的指针
- QueueSize:队列长度
(3)操作:
- 队空:front == rear
- 队满:此时队列中只剩一个空闲单元。即 (rear + 1) % QueueSize == front
- 队长:rear > front : rear - front ;rear < front :(QueueSize - front)+(0+rear)
即 (rear - front +QueueSize)%QueueSize
入队
- 判断队列是否已满
- 把插入的数值赋值给rear指向的数组元素
- rear向后移动:(rear+1)%QueueSize
出队
- 判断队列是否为空
- 把front指向的元素赋值给e返回
- front向后移动:(front+1)%QueueSize
(4)实现:
/**
* 循环队列
* @param <T>
*/
public class CircleQueue<T> {
private T[] arr;
private int front,rear;
public CircleQueue(int len){
arr = (T[])new Object[len];
}
//入队列
public void enQueue(T ele) throws ArrayIndexOutOfBoundsException{
if(!isFull())
{
arr[rear] = ele;
rear = (rear + 1) % arr.length;
}
else
throw new ArrayIndexOutOfBoundsException("队列满了");
}
//出队列
public T deQueue(){
if(!isEmpty())
{
T element = arr[front];
front = (front + 1) % arr.length;
return element;
}
return null;
}
//队列是否为空
public boolean isEmpty(){
return rear == front;
}
//队列是否为满
public boolean isFull(){
return (rear + 1) % arr.length == front;
}
//获得队列容量
public int size(){
if(rear > front)
return rear - front;
return (rear - front + arr.length) % arr.length;
}
}
3. 链式存储结构
(1)定义
尾进头出的单链表
(2)属性:
- front:指向队头元素的指针
- rear:指向队尾元素的指针
- QueueSize:队列长度
(3)操作
入队(不担心内存)
- 插入的结点P指向空
- rear指向P,P为原队尾结点的后继结点。
- rear指针移动到P
出队
- 判断队列是否为空
- 把front 的后继结点也就是要删除的结点,赋值给P返回
- 移动front指针,front指向P的后继结点
- 如果队头就是队尾(也就是rear指向P),则将rear指向头节点
(4)实现:
/**
* 链队
*/
public class LinkedQueue<T> {
//头结点
private LinkNode<T> head;
//尾结点
private LinkNode<T> tail;
//链表长度
private int size;
public LinkedQueue() {
head = new LinkNode<>();
tail = new LinkNode<>();
size = 0;
}
public LinkedQueue(T ele){
head = new LinkNode<>();
tail = new LinkNode<>();
LinkNode<T> node = new LinkNode<>(ele);
head.setNext(node);
tail.setNext(node);
size = 1;
}
//获取链表长度
public int getSize() {
return size;
}
//判断链表是否为空
public boolean isEmpty(){
return size == 0;
}
//入栈
public void enQueue(T ele){
LinkNode<T> temp = new LinkNode<>(ele);
if(head.getNext() == null)
head.setNext(temp);
LinkNode<T> node = tail.getNext();
if(node!=null)
node.setNext(temp);
tail.setNext(temp);
size++;
}
//出栈
public LinkNode<T> deQueue(){
LinkNode<T> node = head.getNext();
if(node != null)
{
LinkNode<T> temp = node.getNext();
head.setNext(temp);
if(tail.getNext() == node)
tail.setNext(temp);
size --;
}
return node;
}
//获取栈顶元素
public LinkNode<T> peek(){
LinkNode<T> node = head.getNext();
return node;
}
}
四:特点
在可以确定队列长度最值的情况下,建议用循环队列,速度快。
无法预估队列的长度时,则用链队列,空间灵活。