1 队列
队列(queue)是一种遵循先入先出规则的线性数据结构。顾名思义,队列模拟了排队现象,即新来的人不断加入队列尾部,而位于队列头部的人逐个离开
在队列中只能在队尾插入,在队首删除
方法名 | 描述 | 时间复杂度 |
---|---|---|
push() | 向队尾插入元素 | O(1) |
pop() | 队首元素出队 | O(1) |
peek() | 访问队首元素 | O(1) |
1.1 实现
1.1 使用带哨兵的单向循环链表实现
类属性和结点类(使用泛型)
public class LinkedListQueue<E> {
Node<E> head = new Node<>(null, null);
Node<E> tail = head;
private int size = 0; // 当前结点数
private int capacity = Integer.MAX_VALUE; // 总容量
static class Node<E> {
E value;
Node<E> next;
public Node(E value, Node<E> next) {
this.value = value;
this.next = next;
}
}
// 无参构造
public LinkedListQueue() {
tail.next = head;
}
// 有参构造
public LinkedListQueue(int capacity) {
this.capacity = capacity;
tail.next = head;
}
}
push()方法
// 向队列尾插入值,插入成功返回true,失败返回false
public boolean push(E value) {
if (isFull()) {
return false;
}
Node<E> added = new Node<>(value, head);
tail.next = added; // 此时新加入的元素就是队尾,把tail指向它
tail = added;
size++;
return true;
}
poll()方法
// 从队列头获取值,移除
public E poll() {
if (isEmpty()) {
return null;
}
Node<E> first = head.next;
head.next = first.next;
if (first == tail) {
tail = head; // 队内只有一个元素时,需要特殊判断
}
size--;
return first.value;
}
peek()方法
// 从队列头获取值,不移除
public E peek() {
if (isEmpty()) {
return null;
}
return head.next.value;
}
类内私有的方法
private boolean isEmpty() {
return head == tail;
}
private boolean isFull() {
return size == capacity;
}
迭代器
public Iterator<E> iterator() {
return new Iterator<E>() {
Node<E> p = head.next;// 越过哨兵迭代
@Override
public boolean hasNext() {
return p != head;
}
@Override
public E next() {
E value = p.value;
p = p.next;
return value;
}
};
}
测试
public static void main(String[] args) {
LinkedListQueue<Integer> queue = new LinkedListQueue<>();
System.out.println("第一次入队:" + "3");
queue.push(3);
System.out.println("第二次入队:" + "8");
queue.push(8);
System.out.println("第三次入队:" + "6");
queue.push(6);
System.out.println();
System.out.println("第一次出队:" + queue.poll());
System.out.println("第二次出队:" + queue.poll());
System.out.println("第三次出队:" + queue.poll());
}
1.2 使用循环数组实现
使用循环数组的好处:
1.对比普通数组,起点和终点更为自由,不用考虑数据移动
2.“环“意味着不会存在越界问题
3.数组性能更佳
4.循环数组比较适合实现有界队列、RingBuffer等
类属性
注意 array 数组实际大小要比给定的 capacity 多一个单位(以后用来装尾指针)
public class ArrayQueue<E> {
private E[] array;
private int head = 0; // 头指针和尾指针初始都是0
private int tail = 0;
public ArrayQueue(int capacity) {
array = (E[]) new Object[capacity + 1];
}
}
push()方法
为了 tail 不超过数组索引,需要限制长度,加1的时候要 mod 数组长度
public boolean push(E value) {
if (isFull()) {
return false;
}
array[tail] = value;
tail = (tail + 1) % array.length;
return true;
}
poll()方法
public E poll() {
if (isEmpty()) {
return null;
}
E value = array[head];
head = (head + 1) % array.length;
return value;
}
peek()方法
public E peek() {
if (isEmpty()) {
return null;
}
return array[head];
}
类内方法(判断队列是否空,是否满的条件)
public boolean isEmpty() {
return head == tail;
}
public boolean isFull() {
return (tail + 1) % array.length == head;
}
迭代器
public Iterator<E> iterator() {
return new Iterator<E>() {
int p = head;
@Override
public boolean hasNext() {
return p != tail;
}
@Override
public E next() {
E value = array[p];
p = (p + 1) % array.length;
return value;
}
};
}
测试:同链表
2 双端队列
双端队列(deque--double ended queue)两端都可以进行操作(删除和添加)
2.1 使用带哨兵的双向循环链表实现
类属性和结点类
public class LinkedListDeque<E> {
int size = 0;
int capacity;
Node<E> sentinel = new Node<>(null, null, null);
static class Node<E> {
Node<E> prev;
E value;
Node<E> next;
public Node(Node<E> prev, E value, Node<E> next) {
this.prev = prev;
this.value = value;
this.next = next;
}
}
public LinkedListDeque(int capacity) {
this.capacity = capacity;
sentinel.next = sentinel;
sentinel.prev = sentinel;
}
}
push()方法
分为两种,队首和队尾
public boolean pushFirst(E value) {
if (isFull()) {
return false;
}
Node<E> a = sentinel;
Node<E> b = sentinel.next;
Node<E> added = new Node<>(a, value, b);
a.next = added;
b.prev = added;
size++;
return true;
}
public boolean pushLast(E value) {
if (isFull()) {
return false;
}
Node<E> a = sentinel.prev;
Node<E> b = sentinel;
Node<E> added = new Node<>(a, value, b);
a.next = added;
b.prev = added;
size++;
return true;
}
注意双向链表每个结点都有两个指针,插入和删除的时候需要同时动三个结点:
a added b
pop()方法
public E popFirst() {
if (isEmpty()) {
return null;
}
Node<E> a = sentinel;
Node<E> removed = sentinel.next;
Node<E> b = removed.next;
a.next = b;
b.prev = a;
size--;
return removed.value;
}
public E popLast() {
if (isEmpty()) {
return null;
}
Node<E> b = sentinel;
Node<E> removed = sentinel.prev;
Node<E> a = removed.prev;
a.next = b;
b.prev = a;
size--;
return removed.value;
}
peek()方法
public E peekFirst() {
return sentinel.next.value;
}
public E peekLast() {
return sentinel.prev.value;
}
类内方法
public boolean isEmpty() {
return size == 0;
}
public boolean isFull() {
return size == capacity;
}
迭代器
public Iterator<E> iterator() {
return new Iterator<>() {
Node<E> p = sentinel.next;
@Override
public boolean hasNext() {
return p != sentinel;
}
@Override
public E next() {
E value = p.value;
p = p.next;
return value;
}
};
}
测试
public static void main(String[] args) {
LinkedListDeque<Integer> deque = new LinkedListDeque<>(10);
System.out.println("第一次前端入队:8"); // 8
deque.pushFirst(8);
System.out.println("第二次前端入队:5"); // 5 8
deque.pushFirst(5);
System.out.println("第三次后端入队:10"); // 5 8 10
deque.pushLast(10);
System.out.print("此时的双端队列:");
Iterator<Integer> it = deque.iterator();
while (it.hasNext()) {
System.out.print(it.next() + " ");
}
System.out.println("\n\n第一次后端出队:" + deque.popLast());
System.out.print("此时的双端队列:");
it = deque.iterator();
while (it.hasNext()) {
System.out.print(it.next() + " ");
}
System.out.println("\n\n第二次前端出队:" + deque.popFirst());
System.out.print("此时的双端队列:");
it = deque.iterator();
while (it.hasNext()) {
System.out.print(it.next() + " ");
}
System.out.println("\n\n第三次前端出队:" + deque.popFirst());
System.out.print("此时的双端队列:");
it = deque.iterator();
while (it.hasNext()) {
System.out.print(it.next() + " ");
}
}
2.2 使用循环数组实现
和 1.2 使用的数组是一样的循环数组,不同的是实现的队列
队列只能一头插入一头删除,所以 1.2 中 head 和 tail 指针不停加1再取模往后移动就可以
而双端队列两头都可以操作,所以删除元素时,head 减1到队尾;插入元素时 tail 加1到后面
类属性
public class ArrayDeque<E> {
E[] array;
int head;
int tail;
int capacity;
public ArrayDeque(int capacity) {
array = (E[]) new Object[capacity + 1];
this.capacity = capacity;
this.head = 0;
this.tail = 0;
}
}
push()方法
public boolean pushFirst(E value) {
if (isFull()) {
return false;
}
head = dec(head, array.length);
array[head] = value;
return true;
}
public boolean pushLast(E value) {
if (isFull()) {
return false;
}
array[tail] = value;
tail=inc(tail, array.length);
return true;
}
pop()方法
public E popFirst(){
if (isEmpty()){
return null ;
}
E value = array[head];
head = inc(head, array.length);
return value;
}
public E popLast(){
if (isEmpty()){
return null ;
}
tail =dec(tail, array.length);
return array[tail];
}
类内方法
public boolean isEmpty() {
return head == tail;
}
public boolean isFull() {
if (tail > head) {
return tail - head == array.length - 1;
} else if (tail < head) {
return head - tail == 1;
} else {
return false;
}
}
public int inc(int i, int length) {
if (i + 1 > length) {
return 0;
}
return i + 1;
}
public int dec(int i, int length) {
if (i - 1 < 0) {
return length - 1;
}
return i - 1;
}
为了头尾指针不超过数组索引范围,所以自定义头尾指针增加和减少的方式
inc(increment 增加)
dec(decrement 减少)
3 优先队列
详情观看大顶堆
使用大顶堆实现:
类属性:
public class PriorityQueue {
int[] array = new int[10];
int size = 0;
}
offer()方法
public boolean offer(int value) {
if (isFull()) {
return false;
}
array[size] = value;
shiftUp(size);
size++;
return true;
}
poll()方法
public int poll() {
if (isEmpty()) {
throw new NullPointerException();
}
int result = array[0];
array[0] = array[size - 1];
shiftDown(0);
size--;
return result;
}
peek()方法
public int peek(){
if (isEmpty()){
throw new NullPointerException();
}
return array[0];
}
shiftUp()上浮方法
private void shiftUp(int index) {
int child = index;
int parent = (index - 1) / 2;
while (child > 0) {
if (array[parent] < array[child]) {
swap(parent, child);
} else {
break;
}
child = parent;
parent = (child - 1) / 2;
}
}
用孩子节点去和父节点比较,孩子节点大就上浮
shiftDown()下潜方法
private void shiftDown(int index) {
int parent = index;
int leftChild = 2 * parent + 1;
int rightChild = leftChild + 1;
int max = parent;
if (leftChild < size && array[max] < array[leftChild]) {
max = leftChild;
}
if (rightChild < size && array[max] < array[rightChild]) {
max = rightChild;
}
if (max != parent) {
swap(max, parent);
shiftDown(max);
}
}
左右孩子如果比父节点大,就和父节点交换,然后父节点(比 max 小)下潜
工具方法
private void swap(int i, int j) {
// 注意传递的是索引,可以间接交换数组两个值
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
private boolean isFull() {
return size == array.length;
}
private boolean isEmpty() {
return size == 0;
}
测试
public static void main(String[] args) {
PriorityQueue priorityQueue = new PriorityQueue();
priorityQueue.offer(9);
priorityQueue.offer(65);
priorityQueue.offer(1);
priorityQueue.offer(754);
System.out.println(priorityQueue.poll());
System.out.println(priorityQueue.poll());
System.out.println(priorityQueue.poll());
System.out.println(priorityQueue.poll());
}