《数据结构》学习笔记(3)——栈和队列

一:定义

栈是限定仅在表尾(栈顶)进行插入和删除操作的线性表。

允许插入和删除的一端称为栈顶,另一端称为栈底,不含任何数据元素的栈称为空栈。
栈又称为后进先出的线性表,简称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)操作:
进栈:
  1. 判断栈是否已满,栈满时无法再进栈。
  2. top指针加一,并对数组的第top个元素赋值
出栈:
  1. 判断栈是否空
  2. 将要删除的栈顶元素赋值给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)增加
  1. 判断共享栈是否已满
  2. 对选择的栈进行增加操作
(5)删除
  1. 对选择的栈判断是否为空
  2. 对选择的栈进行删除操作
4. 链式存储结构

把栈顶作为单链表的头部,指向栈底。

(1)进栈(不需要判断满栈)
  1. 新结点P指向栈顶S的元素
  2. 栈顶指向新结点P
  3. 栈的长度增加
(2)出栈
  1. 判断栈是否为空
  2. 把栈顶S的数据赋值给e返回
  3. 栈顶指向S的下一个元素
  4. 栈的长度减小
(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;
    }
}    

四:应用

  • 递归
  • 后缀表达式
  1. 从左到右遍历表达式,如果是数字,则直接输出。
  2. 判断符号与栈顶符号的优先级大小,若优先级小(或者是右括号),则栈中所有元素(或者是左括号后的所有元素)出栈并输出,符号进栈。否则,直接进栈。
  3. 便利完成,则所有元素出栈。
  • 四则运算(拿着运算符前面两个数进行运算,然后输出结果
  1. 从左到右遍历表达式,如果是数字,则直接进栈。
  2. 如果是符号,则栈顶两个数字出栈,进行运算后进栈。

队列

一:定义

队列是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。

队列是一种先进先出的线性表,简称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
入队
  1. 判断队列是否已满
  2. 把插入的数值赋值给rear指向的数组元素
  3. rear向后移动:(rear+1)%QueueSize
出队
  1. 判断队列是否为空
  2. 把front指向的元素赋值给e返回
  3. 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)操作
入队(不担心内存)
  1. 插入的结点P指向空
  2. rear指向P,P为原队尾结点的后继结点。
  3. rear指针移动到P
出队
  1. 判断队列是否为空
  2. 把front 的后继结点也就是要删除的结点,赋值给P返回
  3. 移动front指针,front指向P的后继结点
  4. 如果队头就是队尾(也就是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;
    }
}    

四:特点

在可以确定队列长度最值的情况下,建议用循环队列,速度快。
无法预估队列的长度时,则用链队列,空间灵活。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值