数据结构三、队列还有循环队列在这里

队列描述

  • 队列也是一种线性数据结构
  • 只能从一端(队尾)添加元素,从另一端(队首)取出元素
  • 队列是一种先进先出的数据结构 FIFO
  • 队列就像食堂排队
    • 从第一个到最后一个
    • 第一个打饭的人向出去,最后一个打饭的人后出去
    • 也就是先进先出
    • 第一个打饭的叫做队首,先出的那个
    • 最后一个打饭的叫做队尾,后进的那个

创建队列

1.先创建一个队列接口
public interface QueueInterface<T> {
    //判断队列有多少元素
    public int getSize();

    //判断队列是否为空
    public boolean isEmpty();

    //入队
    public void enqueue(T ele);

    //出队
    public T dequee();

    //查看出队(队首)元素是什么
    public T getFront();


}
2.实现队列
  • 先声明队,队列也是一个数组
    • 这里是调用博主自己写的动态数组来实现队列的
  • 在给出队列元素的数量
  • 判断队列存了多少元素,只需要返回队列的元素个数即可所以时间复杂度为O(1)
  • 判断队列是否为空,同上时间复杂度为O(1)
  • 入队,实际上就是给数组的尾部添加元素,所以时间复杂度为O(1)
  • 出队,一样是给数组的头部删除元素,所以时间复杂度为O(n)
  • 查看队首元素,就是数组的查询数组的头部元素,所以时间复杂度为O(1)
public class MyQueue<T> implements QueueInterface<T> {

    //数据容器
    private MyArray<T> data;
    //队列中元素数量
    private int size;

    //构造函数
    public MyQueue() {
        data = new MyArray<>();
        size = 0;
    }

    //判断队列有多少元素
    @Override
    public int getSize() {
        return size;
    }

    //判断队列是否为空
    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    //入队
    @Override
    public void enqueue(T ele) {
        try {
            data.addTail(ele);
            size++;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

    }

    //出队
    @Override
    public T dequee() {
        T result = null;
        try {
            result = data.removeHeader();
            size--;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return result;
    }

    //查看出队(队首)元素是什么
    @Override
    public T getFront() {
        try {
            return data.getHead();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public String toString() {
        StringBuilder result = new StringBuilder();
        result.append("队列中总共存" + size + "元素个数");
        result.append(" 队首 [");
        try {
            for (int i = 0; i < size; i++) {
                result.append(data.getEleByIndex(i));
                if (i != size - 1) {
                    result.append(",");
                }
            }
            result.append("] 队尾");
            return result.toString();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            return null;
        }

    }
}

循环队列描述

  • 其中dequeue()操作的时间复杂度为O(n),
    • 是因为在出队时,
    • 数组后面的元素都要进行前移。
    • 本质就是移除数组的头部元素,
  • 为了解决前移的问题,
    • 可以使用front记录队首位置,在出队的时候只需要front后移就可
    • 使用tail记录入队元素的位置,tail永远指向待添加元素的位置,入队时只需要tail后移即可
    • 这就时循环队列。
  • 就像食堂打饭一样
    • 普通队列就是排队打饭,打一个,走一个
    • 循环队列即就是食堂大妈那个小车车,打一个阿姨前进一步

创建循环队列

  • 有三种情况
    • 第一种:
      • 当队列为空时,队首和队尾都指向索引为0的位置
      • 添加一个元素tail后移一次,tail永远指向我们的队尾位置
      • 即front = tail 队列为空
    • 第二种:
      • 如果初始队列长度为8,此时索引为0,1俩个位置的元素已经出队,用原始的方法需要扩容操作才能继续进行入队操作,
      • 但是前面俩个位置由于已经释放元素,属于空闲区域,我们也可以向0,1为索引的位置进行入队操作.
      • 如此操作的话就可以继续循环操作.不进行扩容,岂不是效率更高,时间复杂度为O(1),这样的操作,我们称其为循环数
    • 第三种:
      • 即 (tail + 1)%length = front时,我们把这种情况称作为队列已满
      • 就是为了避免front = tail 队列为空情况的出现
      • 但是这样子我们会浪费一个空间,
      • 浪费空间的目的是用空间来换取时间
实现循环队列
  • 实现我们自己写的队列接口
    • 队列接口在本章上面
public class MyLoopQueue<T> implements QueueInterface<T> {

}
  • 声明数组容器,这里阿三用的是java里面的原生数组
  • 定义队列的队首下标和队尾下标
  • 给予有参无参构造方法
  • 判断循环队列是否为空,用上述创建循环队列的第一种情况front == tail为空
 //数据容器
    private T[] data;

    //循环队列的长度,即下标
    private int front, tail, size;

    //队列中的容量,有参构造方法
    public MyLoopQueue(int capacity) {
        data = (T[]) new Object[capacity + 1];
        front = 0;
        tail = 0;
        size = 0;
    }

    //构造函数
    public MyLoopQueue() {
        this(10);
    }

    //判断队列有多少元素
    @Override
    public int getSize() {
        return size;
    }

    //判断队列是否为空
    @Override
    public boolean isEmpty() {
        return front == tail;
    }
  • 队列扩容
    • 先声明一个object类型的数组,然后对其转型
    • 长度加一是为了用空间来换时间
    • 然后for循环,将原队列赋值给新队列中
      • newData[i] = data[(front + i) % data.length];
      • 赋值给新队列,原队列的顺序不能改变,所以在这里队首始终为索引为0的位置.原队首front可能指向索引为2的位置.
      • eg:
        • 0 null 1 2 3
        • 此时队首索引为2,队首的值为1.队尾索引为0值为0
        • null为空间浪费,这里用空间来换时间
        • 在(tail + 1) % data.length == front时就需要进行队列扩容
        • newData[i] = data[(front + i) % data.length];
        • newData[0] = data[2%5],此时新队列队首的索引更改为0,从0开始向新队列进行赋值
        • 直至赋完所有的值,在将新队列赋给老队列即扩容成功
//扩容
    private void resize(int newCapacity) {
        T[] newData = (T[]) new Object[newCapacity + 1];
        for (int i = 0; i < size; i++) {
            newData[i] = data[(front + i) % data.length];
        }
        tail = size;
        data = newData;
        front = 0;
    }
  • 入队
    • 是向队尾加一个元素
    • 先判断队列是否已满,若满则进行扩容,调用上述扩容方法,
    • 若不满,则将元素插入队尾,元素个数++
    • 若队列下标前部为空,后部已满则将插入的元素放置在队列前部,构成循环队列
    • eg:
      • 此队列长度为5即
      • null null 1 2 3
      • 此时就将元素带插入的元素ele()放置索引为0的位置上构成循环队列
      • ele null 1 2 3
      • 此时队首索引为2,队首的值为1.队尾索引为0值为ele
  • 时间复杂度为O(1)
    • 同队列,
    • 这里也是均摊复杂度,会有扩容的影响
//入队
    @Override
    public void enqueue(T ele) {
        if ((tail + 1) % data.length == front) {
            resize(2 * data.length);
        }
        data[tail] = ele;
        size++;
        tail = (tail + 1) % data.length;
    }
  • 队列出队
    • 是向队首去除一个元素
    • 先判断队列是否为空
    • 长度–
    • 将队首指针后移,
    • 若长度小于队列长度的四分之一
  • 时间复杂度为O(1)
    • 不同于队列,因为有个tail
    • 但此O(1)为均摊复杂度,会有扩容的影响
 //出队
    @Override
    public T dequee() {
        if (front == tail) {
            return null;
        }
        T result = data[front];
        size--;
        front = (front + 1) % data.length;
        //缩容
        if (size == data.length / 4 && data.length / 2 > 0) {
            resize(data.length / 2);
        }
        return result;
    }
  • 查看队首元素
    • 先判断队列是否为空
    • 不为空则返回front为索引的元素
    • 为空返回null
  • 时间复杂度为O(1)
    //查看出队(队首)元素是什么
    @Override
    public T getFront() {
        if (front != tail) {
            return data[front];
        }
        return null;
    }

  • 重写toString返回
    • 这里为了打印循环队列所以重新toString
    • 这里result.append(data[(front + i) % data.length]);是一个易错点
    • 这里必须是data.length不能是size
    • size为队列元素的个数若为此,打印时队首队尾顺序会出错
 @Override
    public String toString() {
        StringBuilder result = new StringBuilder();
        result.append("队列中总共存" + size + "元素个数");
        result.append(" 队首 [");
        for (int i = 0; i < size; i++) {
            result.append(data[(front + i) % data.length]);
            if (i != size - 1) {
                result.append(",");
            }
        }
        result.append("] 队尾");
        return result.toString();


    }

队列与循环队列在出队的比较

  • 写一个输出的方法
  • 输出出队结束时间-入队时间
  • 传的参数是接口类型的
 public static void compareQueue(QueueInterface queue) {
        long StartTime = System.nanoTime();
        int num = 100000;
        Random random = new Random();
        for (int i = 0; i < num; i++) {
            queue.enqueue(random.nextInt(100));
        }
        while (!queue.isEmpty()) {
            queue.dequee();
        }
        long endTime = System.nanoTime();
        System.out.println((endTime - StartTime) / 1000000000.0);
    }
  • 书写main方法
  • 将我们自己写的队列与循环队列输入进去
  • 入队出队的元素为10万的时候,会发现循环队列的出队比普通队列的时间快多了
 public static void main(String[] args) {
        QueueInterface myQueue = new MyQueue<>();
        compareQueue(myQueue);
        myQueue = new MyLoopQueue();
        compareQueue(myQueue);

    }

队列和循环队列的出队时间比较

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值