数组队列

数组队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。遵循先进先出(FIFO)原则。

【数组队列代码实现】

先定义一个Queue接口

public interface Queue<E> {
int getSize();
boolean isEmpty();
void enqueue(E e);
E dequeue();
E getFront();
}
import com.algorithm.week3.c1.Array;
public class ArrayQueue<E> implements Queue<E> { private Array<E> array; public ArrayQueue(int capacity) {
array = new Array<>(capacity);
} public ArrayQueue() {
array = new Array<>();
} @Override
public int getSize() {
return array.getSize();
} @Override
public boolean isEmpty() {
return array.isEmpty();
} public int getCapacity() {
return array.getCapacity();
} @Override
public void enqueue(E e) {
array.addLast(e);
} @Override
public E dequeue() {
return array.removeFirst(); // 取出队首元素
} @Override
public E getFront() {
return array.getFirst();
} @Override
public String toString() {
StringBuilder res = new StringBuilder();
res.append("Queue:");
res.append("Front [");
for (int i = 0; i < array.getSize(); i++) {
res.append(array.get(i));
if (i != array.getSize() - 1) {
res.append(", ");
}
}
res.append("] Tail"); return res.toString();
}
}

循环队列

百度百科:

  • 循环队列就是将队列存储空间的最后一个位置绕到第一个位置,形成逻辑上的环状空间,供队列循环使用。

  • 在循环队列结构中,当存储空间的最后一个位置已被使用而再要进入队运算时,只需要存储空间的第一个位置空闲,便可将元素加入到第一个位置,即将存储空间的第一个位置作为队尾。

  • 循环队列可以更简单防止伪溢出的发生,但队列大小是固定的。

  • 在循环队列中,当队列为空时,有 front == tail,而当所有队列空间全占满时,也有 front == tail

为了区别这两种情况,规定循环队列最多只能有 MaxSize-1个队列元素,当循环队列中只剩下一个空存储单元时,队列就已经满了。因此,队列判空的条件是 front == tail,而队列判满的条件是 front ==(tail+1) % MaxSize

​ 图1.空队列

​ 图2.插入元素,tail++

​ 图3.删除队头元素front++

​ 图4. 继续队尾添加元素

​ 图5. 第一个位置空闲,便可将元素加入到第一个位置

​ 图6.计算循环索引

public LoopQueue<E> implements Queue<E> {
private E[] data;
private int front, tail;
private int size; public LoopQueue(int capacity) {
data = new Object[capacity + 1]; // 循环队列中空闲了一个空间
front = 0;
tail = 0;
size = 0;
} public LoopQueue() {
this(10);
} public int getCapacity() {
return data.length - 1;
} @Override
public boolean isEmpty() {
return front == tail;
} @Override
public int getSize() {
return size;
} @Override
public void equeue(E e) {
// 首先判断队列是否满了
if (front == (tail + 1) % data.length) {
// 扩容
resize(getCapacity() * 2);
}
data[tail] = e;
tail = (tail + 1) % data.length;
size++;
} @Override
public E dequeue() {
if (isEmpty()) {
throw new IllegalArgumentException("Cannot dequeue an empty queue.");
}
E ret = data[front];
data[front] = null;
front = (front + 1) % data.length;
size--; // 缩容
if (size == getCapacity() / 4 && getCapacity() / 2 != 0) {
resize(getCapacity() / 2)
} return ret;
} public void resize(int newCapacity) {
E[] newData = (E[]) new Object[newCapacity + 1];
for (int i = 0; i < size; i++) {
newData[i] = data[(i + front) % data.length];
}
data = newData;
front = 0;
tail = size;
}
}

数组队列和循环队列比较

  • 复杂度

    • 循环队列:E dequeue(),O(1)
    • 数组队列:E dequeue(),O(n)

使用动态数组实现栈和队列

使用栈实现队列

使用队列实现栈

链表

链表的几个要点:

  1. 虚拟头结点,能解决很多问题(有时候短的链表,就会出现一些我们逻辑操作越界的问题)。
  2. 要注意先处理后面的结点,避免改指针时找不到它了,必要时用临时指针存着。
  3. 用好双指针!

链表的实现

public class LinkedList<E> {

    public class Node {
public E e;
public Node next; public Node(E e, Node next) {
this.e = e;
this.next = next;
}
public Node(E e) {
this(e, null);
}
public Node() {
this(null, null);
}
@Override
public String toString() { return e.toString(); }
} private Node dummyHead;
private int size; public LinkedList() {
dummyHead = new Node(null, null); // 虚拟头结点
size = 0;
}
public int getSize() { return size; } public boolean isEmpty() { return size== 0;} public void add(int index, E e) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("Index error.");
}
Node prev = dummyHead;
for (int i = 0; i < index - 1; i++)
prev = prev.next;
Node node = new Node(e);
node.next = prev.next;
prev.next = node;
// prev.next = new Node(e, prev.next);
size++;
}
public void addFirst(E e) {
add(0, e);
}
public void addLast(E e) { add(size, e); } public E remove(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("Index error.");
}
Node prev = dummyHead;
// 找到待删除节点的前一结点
for (int i = 0; i < index; i++)
prev = prev.next;
Node retNode = prev.next;
prev.next = retNode.next;
retNode.next = null;
size --;
return retNode.e;
} public E removeFirst() {
return remove(0);
} public E removeLast() {
return remove(size - 1);
} public void set(int index, E e) {
if (index < 0 || index >= size)
throw new IllegalArugmentException("Index error"); Node cur = dummyHead.next;
for (int i = 0; i < index; i++)
cur = cur.next;
cur.e = e;
} public boolean contains(E e) {
Node cur = dummyHead.next;
while (cur != null) {
if (cur.next != null & cur.e.equals(e))
return true;
cur = cur.next;
}
return false;
}
@Override
public String toString() { StringBuilder sb = new StringBuilder(); Node cur = dummyHead.next;
while (cur != null) {
sb.append(cur + "->");
cur = cur.next;
}
sb.append("NULL");
return sb.toString();
}
}

用链表实现栈

Stack.java

public interface Stack<E> {
int getSize();
boolean isEmpty();
void push(E e);
E pop();
E peek();
}

LinkedListStack.java

public LinkedListStack<E> implements Stack<E> {
private LinkedList<E> list; // 先声明一个链表 public LinkedListStack() {
list = new LinkedList<>();
} @Override
public int getSize() { return list.getSize(); } @Override
public boolean isEmpty() { return list.isEmpty(); } @Override
public void push(E e) {
list.addFirst(e); // 插入栈顶
} @Override
public E pop() {
return list.removeFirst(); // 弹出栈顶元素
} @Override
public E peek() {
return list.getFirst(); // 查看栈顶元素
}
}

改进链表

  • 从head端删除元素,从tail端插入元素
  • 没有dummyHead,需注意链表为空的情况
// LinkedListQueue.java
public class LinkedListQueue<E> implements Queue<E> {
// 定义一个节点
private class Node {
public E e;
public Node next; public Node(E e, Node next) {
this.e = e;
this.next = next;
}
public Node(E e) {
this(e, null);
}
public Node() {
this(null, null);
}
} private Node head, tail;
private size; public LinkedListQueue() {} @Override
public int getSize() { return size; } @Override
public boolean isEmpty() { return size==0; } @Override
public void enqueue(E e) {
if (tail == null) {
tail = new Node(e);
head = tail;
} else {
tail.next = new Node(e);
tail = tail.next;
}
size++;
}
@Override
public E dequeue() {
if (isEmpty()) throw new IllegalArgumentException("queue is empty!"); Node retNode = head;
head = head.next;
retNode.next = null;
if (head == null)
tail = null;
size--;
return retNode.e;
}
@Override
public E getFront() {
if (isEmpty()) throw new IllegalArgumentException("queue is empty!");
return head.e;
}
}

链表的性能问题

虽然只在链表表头添加元素,时间复杂度O(1),同时,使用链表不需要resize,直觉上链表性能更好;

But,实际上,当数据量达一定程度,其性能是更差的,因为:

  • 对于链表,每添加一个元素,都需要重新创建一个Node类对象(需要进行一次new内存操作),这是非常慢的。

为什么即使有resize,对大规模数据,动态数组还是快于链表?

  • 对动态数组,一方面,每次resize容量倍增,但对大规模数据,实际上触发resize次数非常少的

  • resize的过程,一次申请一大片内存空间。链表每次申请一个空间

  • 申请一次10万的空间远远快于10万次1的空间。

  • 相较于堆内存空间的操作,动态数组的resize过程虽然还需赋值(旧数组元素拷贝到新数组),但该拷贝过程是远远快于对内存的操作的

📚 队列-DS笔记的更多相关文章

  1. 单调栈&单调队列学习笔记!

    ummm,,,都是单调系列就都一起学了算了思想应该都差不多呢qwq 其实感觉这俩没有什么可说的鸭QAQ就是维护一个单调的东西,区别在于单调栈是一段进一段出然后单调队列是一段进另一段出?没了 好趴辣重点 ...

  2. 📚 选择排序和插入排序区别-DS笔记

    选择排序法 A[i...n)未排序,A[0...i)已排序 A[i...n]中最小值要放到A[i]的位置 复杂度 \(O(n^2)\) 第一层循环n次 第二层循环:i=0,n次:i=1,n-1次... ...

  3. the little schemer 笔记(8)

    第八章 lambda the ultimate 还记得我们第五章末的rember和insertL吗 我们用equal?替换了eq? 你能用你eq?或者equal?写一个函数rember-f吗 还不能, ...

  4. Java:阻塞队列

    Java:阻塞队列 本笔记是根据bilibili上 尚硅谷 的课程 Java大厂面试题第二季 而做的笔记 1. 概述 概念 队列 队列就可以想成是一个数组,从一头进入,一头出去,排队买饭 阻塞队列 B ...

  5. 【Python】分布式任务队列Celery使用参考资料

    Python-Celery Homepage | Celery: Distributed Task Queue User Guide - Celery 4.0.2 documentation Task ...

  6. 终于懂了:WM_PAINT 与 WM_ERASEBKGND(三种情况:用户操作,UpdateWindow,InvalidateRect产生的效果并不相同),并且用Delphi代码验证 good

    一直对这两个消息的关系不是太了解,借重新深刻学习windows编程的机会研究一番. 1)当窗口从无效变为有效时,比方将部分覆盖的窗口恢复时会重绘窗口时:程序首先会通过发送其他消息调用DefWindow ...

  7. Linux进程间通信IPC学习笔记之消息队列(SVR4)

    Linux进程间通信IPC学习笔记之消息队列(SVR4)

  8. JavaSE中Collection集合框架学习笔记(2)——拒绝重复内容的Set和支持队列操作的Queue

    前言:俗话说“金三银四铜五”,不知道我要在这段时间找工作会不会很艰难.不管了,工作三年之后就当给自己放个暑假. 面试当中Collection(集合)是基础重点.我在网上看了几篇讲Collection的 ...

  9. 课堂笔记及知识点----栈和队列(2018/10/24(am))

    栈: Stack<int>  xt=new Stack<int>() ; 先进后出,后进先出,水杯结构,顺序表类似 常用方法:   .pop---->出栈,弹栈     ...

随机推荐

  1. 34、python并发编程之多进程(操作篇)

    目录: 一 multiprocessing模块介绍 二 Process类的介绍 三 Process类的使用 四 守护进程 五 进程同步(锁) 六 队列(推荐使用) 七 管道 八 共享数据 九 信号量( ...

  2. Solution -「APIO 2016」「洛谷 P3643」划艇

    \(\mathcal{Description}\)   Link & 双倍经验.   给定 \(n\) 个区间 \([a_i,b_i)\)(注意原题是闭区间,这里只为方便后文描述),求 \(\ ...

  3. 操作系统实验一:进程管理(含成功运行C语言源代码)

    目录 操作系统实验一:进程管理 1.实验目的 2.实验内容 3.实验准备 3.1.1进程的含义 3.1.2进程的状态 3.1.3进程状态之间的转换 3.2 进程控制块PCB 3.2.1进程控制块的作用 ...

  4. 探秘inter()方法

    最近在阅读<深入理解Jav虚拟机>的运行时常量池章节,看到"java语言并不要求常量池一定只有编译器才能产生...运行期间也可以将新的常量放入常量池,这种特性被开发人员利用得比较 ...

  5. Rust所有权及引用

    Rust 所有权和借用 Rust之所以可以成为万众瞩目的语言, 就是因为其内存安全性. 在以往内存安全几乎全都是通过GC的方式实现, 但是GC会引来性能.CPU以及Stop The World等问题, ...

  6. ensp常用命令

    display ip int brief 查看接口ip地址display interface brief 查看接口的简要信息display current-configuration 显示当前配置文件 ...

  7. 二十七 集合!!!!!!!!set 二十八 文件

    0: 不知道啊 去除重复元素? 1:frozenset 2:len(x) 3:直接一个错的报 4:好像一样       错  完全不一样的说  set = {[1,2]}这是想干嘛  :列表怎么可以进 ...

  8. cpu 时间片消耗表|cpu消耗表

    1秒=1000毫秒(ms)=1,000,000 微秒(μs)=1,000,000,000 纳秒(ns)=1,000,000,000,000 皮秒(ps) 1个CPU时钟周期=时钟频率 2GHz CPU ...

  9. 【windows 操作系统】异步

    转载:https://cloud.tencent.com/developer/article/1744660 二.异步与多线程 1)基本概念 1. 并发:在操作系统中,是指一个时间段中有几个程序都处于 ...

  10. Oracle的发展历程

    我们学习的是ORACLE(甲骨文)公司(就是收购Sun公司的甲骨文公司)的Oracle数据库(Oracle Database).Oracle数据库是关系型数据库中的大型数据库,存储量大,而且也非常安全 ...