数据结构:队列

队列的定义

队列是只允许在队尾进行插入操作,在队头进行删除操作、先进先出的线性表。
如图:
队列的定义
其中A1为队头元素,A7为队尾元素。

队列的数据存储结构

顺序存储结构

队列的顺序存储结构,需要创建数组来实现:

public class ArrayQueue<T> {
    private int defalut_size = 10;//默认大小
    private Object[] mObjects;//当前存放数据的数组
    private int front;//队头下标
    private int rear;//队尾下标

    public ArrayQueue() {
        //初始化对列数组
        mObjects = new Object[defalut_size];
    }

    //入队
    public void enQueue(T t) {
        //队尾下标不大于默认长度
        if (rear < defalut_size) {
            mObjects[rear] = t;
            rear++;
        }
    }
    //出队
    public T deQueue() {
        //队头下标不超过队尾下标
        if (front < rear) {
            T t = (T) mObjects[front];
            mObjects[front] = null;
            front++;
            return t;
        }
        return null;
    }
    //打印队列状态
    public void getQueuqData() {
        for (int i = 0; i < mObjects.length; i++) {
            System.out.println("数组下标: " + i + " data: " + mObjects[i]);
        }
    }
}

当调用出队方法后,会把原来数组front的位置空出来,但假如此时队尾的下标已到达最大值,再往后入队的时候,就会导致数组越界,但队头明明还存在一个空位置,这就是“假溢出”现象。如下图:
假溢出
要解决假溢出,就用到循环对列。只要对列尾指针rear达到数组最大时,把队尾指针指向队头,达到头尾循环。

 //入队
    public void enQueue(T t) {
        //是否对列已满
        if ((rear + 1) % defalut_size == front) {
            rear = (rear + 1) % defalut_size;
            mObjects[rear] = t;
        }else {
            try {
                throw new Exception("对列已满");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

链式存储结构

对列的链式存储结构,其实是线性表的单链表,只不过是限制了只能尾进头出。队头指针叫做头结点,队尾指针叫做尾节点。
对列的链式存储结构
java代码实现如下:

public class LinkQueue<T> {
    private int size;//对列大小
    private Node front;//头结点
    private Node rear;//尾节点

    private class Node<T> {
        private Node next;
        private T data;

        public Node(T data) {
            this.data = data;
        }
    }

    public LinkQueue() {
    }

    //初始化头结点等于尾节点
    public LinkQueue(T t) {
        this.front = this.rear = new Node(t);
        size++;
        System.out.println("入队数据:" + t);
    }

    //入队
    public void enQueue(T t) {
        //初始化节点
        Node<T> node = new Node<>(t);
        //表示对列为空,则插入的头结点与尾节点一直
        if (front == null) {
            rear = front = node;
        } else {
            //将尾节点的next指针指向新节点,并把新节点作为新节点。
            rear = rear.next = node;
        }
        System.out.println("入队数据:" + t);
        size++;
    }

    //出队
    public Object deQueue() {
        //表示队列中大于一个节点
        if (front != rear) {
            //将头结点赋值给next;
            Node oldFront = front;
            //把原头结点的next作为新的头结点。
            front = oldFront.next;
            //对列大小减一
            size--;
            System.out.println("出队数据:" + oldFront.data);
            return oldFront.data;//返回出队数据
        } else {
            Object data = front.data;
            //如果对列只有一个节点,直接出队,把头结点尾节点赋为空,返回出队数据。
            front = rear = null;
            size = 0;
            System.out.println("出队数据:" + data);
            return data;
        }
    }

    public int getSize() {
        System.out.println("队列大小:" + size);
        return size;
    }

    public void printQueue() {
        Node<T> mFront = front;
        if (mFront != null) {
            System.out.println("剩下节点:" + mFront.data);
            while (mFront.next != null) {
                mFront = mFront.next;
                System.out.println("剩下节点:" + mFront.data);
            }
        }

    }
}

java中的对列

java中的对列是一个接口Queue

package java.util;

public interface Queue<E> extends Collection<E> {
    boolean add(E var1);

    boolean offer(E var1);

    E remove();

    E poll();

    E element();

    E peek();
}

其中

public abstract class AbstractQueue<E>
    extends AbstractCollection<E>
    implements Queue<E> {
public class PriorityQueue<E> extends AbstractQueue<E>
    implements java.io.Serializable {
    }

具体分析一下PriorityQueue这个类。
入队

	//实现add方法最终也是调用了offer方法
 public boolean add(E e) {
        return offer(e);
    }
//入队方法
public boolean offer(E e) {
		//如果入队数据为空,抛空指针异常
        if (e == null)
            throw new NullPointerException();
        //计数器加一
        modCount++;
        int i = size;//获取当前已经存在的元素数量
        if (i >= queue.length)//当存在的数量大于或者等于存储的数组长度时,扩容
            grow(i + 1);
        size = i + 1;//实际长度加一
        if (i == 0)//如果列为空,直接放入队头位置,否则入队
            queue[0] = e;
        else
            siftUp(i, e);//这个队列是一个优先级对列,会通过比较器,把新加入的元素放到合适位置,实现队列元素的优先级。
        return true;
    }

先看扩容方法grow

private void grow(int minCapacity) {
        int oldCapacity = queue.length;//获取旧数组长度
        // 如果当前数组长度小于64,则长度扩大为2*oldCapacity +2,
        //否则oldCapacity +oldCapacity/2的长度
        int newCapacity = oldCapacity + ((oldCapacity < 64) ?
                                         (oldCapacity + 2) :
                                         (oldCapacity >> 1));
        // 如果新长度大于Integer.MAX_VALUE - 8; 则为Integer.MAX_VALUE 否则为
        //Integer.MAX_VALUE - 8
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
            //扩容copy新数组
        queue = Arrays.copyOf(queue, newCapacity);
    }

出队

public E poll() {
        if (size == 0)
            return null;
        int s = --size;//大小减一
        modCount++;//计数器加一
        E result = (E) queue[0];//拿队头元素
        E x = (E) queue[s];//获取最后元素
        queue[s] = null;//最后元素赋空
        if (s != 0)
            siftDown(0, x);//利用二叉树重新移动元素。
        return result;
    }

默认调用siftDownComparable方法利用二叉树移动元素:

private void siftDownComparable(int k, E x) {
        Comparable<? super E> key = (Comparable<? super E>)x;
       // 获取数组中间坐标
        int half = n >>> 1;           // loop while a non-leaf
        // k其实是half的父节点下标,若k >= half,则表明k在数组中已经没有子节点
        while (k < half) {
            // k的左子节点下标
            int child = (k << 1) + 1; // assume left child is least
            // 获取左子节点的值
            Object c = array[child];
            // 获取右子节点下标
            int right = child + 1;
            // 选取左右子节点的最小值
            if (right < n &&
                ((Comparable<? super T>) c).compareTo((T) array[right]) > 0)
                c = array[child = right];
            // key <= c说明已经按照升序排序,跳出循环
            if (key.compareTo((T) c) <= 0)
                break;
            // 否则,将子节点的值放在父节点上
            array[k] = c;
            // 将child当作下次比较的k
            k = child;
        }
        queue[k] = key;
    }

可以看到 PriorityBlockingQueue底层使用线性表的顺序存储结构来实现。

总结

对列的操作时间复杂度为常数时间,顺序存储结构要提前申请空间,必要时还需要扩容来防止溢出,同时还要防止假溢出现象。而链式存储结构,每次释放和申请节点都需要时间开销,如果在长度固定的情况下,建议用顺序存储结构,如果无法预知对列长度,则需要使用链式存储结构。

下一节 数据结构:树

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值