队列: 尾指针索引小于头指针的扩容问题

  队列,一种作为构思算法的辅助数据结构,和栈相反,遵循FIFO即先进先出原则。

  为了节省空间,提高底层数据结构的使用率(队列可以通过数组实现,也可以通过链表实现),常见的做法和其他数据结构扩容不同,并不是尾指针移至末尾就进行扩容,而是将尾指针移至0,从头开始(当然这要求底层数据结构如数组,起始的位置数据已经pop出去了,否则将进行扩容)。如下所示:

  如图我们再次pop一个3的时候,并不像其他数据结构一样,立即扩容,而是将队尾指针移至0,将3这个元素push到0索引的位置:

  这样做的根本目的就是为了节省空间,当然这会在算法实现上带来一系列难度。尤其在扩容的时候,假设我们在上图所示队列中,再push两个元素3,那么队尾指针到达2,对头指针不变,这个没有什么问题,但是在我们还想push一个元素的时候,这个时候如果像其他数据结构一样,进行简单的扩容是不能满足的。如下:

  如果只是简单的扩容,我们再想push元素的时候,队尾指针上移,但是上移之后和队头指针冲突,我们这样的扩容是不行的。

解决思路:

1.  和其他数据结构一样,尾指针到达数组(以下都以数组实现队列为例)末尾的时候,不掉头,直接进行扩容。虽然可能大部分程序猿实现队列都是掉头,但是我们要知道这样做的根本目的是节省空间,而不是什么规范和协议必须遵守。由此引来的问题就是空间浪费问题。

2. 那么为什么大多数程序猿都选择尾指针掉头呢,因为本文所提及的扩容问题,解决方案并不难,我们可以在copy的时候,将队头指针到数组末尾的数据和数组开始到队尾指针的位置分开copy,虽然复制了两次,但是数组复制我们都调用System.arraycopy,利用他们内存地址连续,快速操作,不用循环(可自行百度数组内部寻址)。

  如下所示,我们将队头至数组末尾的数据1,2,3,4,在扩容的时候,移至新数组的末尾,同时队头位置发生改变。

3. 为了节省空间,我们可以在每次push的时候,调用一个是否需要缩减容量的处理方法,比如,如果当前队列数组长度为20,但是实际数据量只有10,我们可以进行一次容量缩减,如果实在有此需要,建议将数组容量的1/2作为标准。

代码实现:

package com.dzh.learn.dataStructuresAndAlgorithm.dataStructures;

import java.util.Arrays;

/**
 * 队列
 * FIFO    first in first out  先进先出
 * 单向队列
 * @author caiwm
 */
public class MyQueue {

    private int[] arr;

    private int defCapacity = 10;

    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    /*** 实际长度 */
    private int size;

    /*** 队头 先出 */
    private int queueHead;

    /*** 队尾 后入 */
    private int queueEnd;

    public MyQueue(int initCapacity){
        if (initCapacity <= 0)
            throw new RuntimeException("Capacity must greater than zero.");
        arr = new int[initCapacity];
    }

    public MyQueue(){
        arr = new int[defCapacity];
    }

    public void push(int element){
        ensureCapacityInternal();
        // TODO 缩减整理 例如空闲空间大于1/2
        if (size == 0){
            queueHead = queueEnd = 0;
            arr[0] = element;
        } else{
            // 最上 返回0赋值
            if (queueEnd == (arr.length - 1)){
                arr[0] = element;
                queueEnd = 0;
            } else {
                arr[++queueEnd] = element;
            }
        }
        size++;
    }

    private void ensureCapacityInternal(){
        if (arr.length == size){
            grow();
        }
    }

    private void grow(){
        // int 最大值校验
        int capacityAdd = ensureExplicitCapacityAdd();
        if (queueEnd < queueHead){
            // 尾指针掉头 copy方案
            int[] arrNew = new int[arr.length + capacityAdd];
            int numMoved = arr.length - queueHead;
            System.arraycopy(arr, queueHead, arrNew, (queueHead + capacityAdd), numMoved);
            System.arraycopy(arr, 0, arrNew, 0, queueHead);
            queueHead += capacityAdd;
            arr = arrNew;
        } else {
            arr = Arrays.copyOf(arr, arr.length + capacityAdd);
        }
    }

    private int ensureExplicitCapacityAdd(){
        if (arr.length + 1 > MAX_ARRAY_SIZE)
            return ((arr.length + 1 > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE) -  arr.length;
        else
            return arr.length >> 1;
    }

    public int peek(){
        if (isEmpty())
            throwEmptyException();
        return arr[queueHead];
    }

    public int pop(){
        if (isEmpty())
            throwEmptyException();
        int i = arr[queueHead];
        arr[queueHead] = 0;  // 为了大家看得更直观,其实可以不需要,但是Object类型queue或者泛型queue,此处最好置为null
        if (queueHead == (arr.length - 1)){
            queueHead = 0;
        } else {
            queueHead++;
        }
        size--;
        return i;
    }

    public boolean isEmpty(){
        return size == 0;
    }

    public void throwEmptyException(){
        throw new RuntimeException("Queue is empty.");
    }
}

  

猜你喜欢

转载自blog.csdn.net/duoduo1636546/article/details/82896330
今日推荐