Java数据结构之队列与OJ题
作者:程序猿教你打篮球
什么是队列?
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)
入队列:进行插入操作的一端称为队尾!
出队列:进行删除操作的一 端称为队头!
队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构, 出队列在数组头上出数据,效率会比较低。
初识Queue
认识一下Queue
如图可见,Queue本质上是一个接口,被Deque(双端队列)继承,被LinkedList实现,所以Queue底层是通过链表来实现的,而Queue是不能实例化对象的, 所以我们想使用队列,则需要new一个LinkedeList对象,实现向上转型,这样就可以使用Queue中的方法了:
add 和 offer 都是入队列,这两个区别是当使用add时,一些队列有大小限制,如果想在一个满的队列中加入新的元素时,调用 add() 方法就会抛出一个 unchecked 异常,而调用 offer() 方法会返回 false。因此就可以在程序中进行有效的判断!
简单使用下Queue
public static void main(String[] args) { Queue<Integer> queue = new LinkedList<>(); queue.offer(1); queue.offer(2); queue.offer(3); System.out.println(queue.peek()); //获取对头元素 -> 1 System.out.println(queue.poll()); //出队头元素 -> 1 System.out.println(queue.peek()); //获取对头元素 -> 2 System.out.println(queue.isEmpty()); //判断队列是否为null -> false queue.clear(); //清空队列 System.out.println(queue.isEmpty()); //判断队列是否为null -> true }
模拟实现Queue
构造方法和成员属性
public class MyQueue { private class Node { private int val; //数据域 private Node next; //指针域名 private Node(int val) { this.val = val; } } //队头入,队尾出 private Node head; //队头引用 private Node last; //队尾引用 private int size; //队列有效数据个数 }
offer 方法
// 队尾入队列 public boolean offer(int val) { Node newNode = new Node(val); // 如果是第一次入队列 if (this.head == null) { this.head = newNode; this.last = newNode; } else { this.last.next = newNode; this.last = this.last.next; } this.size++; return true; }
poll 方法
// 队头出队列 public int poll() { Node node = this.head; // 如果队列为null if (this.head == null) { throw new MyQueueIsEmptyException("队列为空!"); } else { this.head = this.head.next; } this.size--; return node.val; }
peek 方法
// 取队头元素 public int peek() { // 如果队列为null if (this.head == null) { throw new MyQueueIsEmptyException("队列为空!"); } else { return this.head.val; } }
至于size和empty方法,就交给各位小伙伴实现了,由于有了前面链表的基础,队列实现起来是比较简单的,所以更多希望阅读文章的初学者能下来多自己手写以下,画画图。
队列相关的OJ题
设计循环队列 (来源:LeetCode 难度:中等)
题目:设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。
循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。
你的实现应该支持如下操作:
MyCircularQueue(k): 构造器,设置队列长度为 k 。
Front: 从队首获取元素。如果队列为空,返回 -1 。
Rear: 获取队尾元素。如果队列为空,返回 -1 。
enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。
deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。
isEmpty(): 检查循环队列是否为空。
isFull(): 检查循环队列是否已满。
解题思路:对于这道题来说,我们有很多种解题思路,但我们要注意一点,如何区分队列为空和队列满的情况?这里可以添加size属性记录,也可使用标记,也可也空一个位置出来区分,那么今天我们就空一个位置,那么如何区分队列空和队列满呢?见下图:
对于循环队列的实现我们采用的时数组方案,有front和rear分别存储队头下标和队尾元素的后一个下标。至于更多需要注意细节的地方,比如修正front和rear的位置时,在代码中有对应的注释,查看即可:
public class MyCircularQueue { private int array[]; //存放数据的数组 private int front; // 队头下标 private int rear; // 队尾下标 public MyCircularQueue(int k) { this.array = new int[k + 1]; //因为我们要浪费一个空间,所以需要多开辟一个空间 } // 入队列 public boolean enQueue(int value) { // 如果队列满的情况 if (isFull()) { return false; } // 没有满就往队尾入元素 this.array[this.rear] = value; // rear往前走一步,需要空出一个位置,所以当rear走到length-1时,需要修正下rear this.rear = (this.rear + 1) % this.array.length; return true; } // 出队列 public boolean deQueue() { // 如果队列为null的情况 if (isEmpty()) { return false; } // 从队头出队列,需要修正队头的位置 this.front = (this.front + 1) % this.array.length; return true; } // 取队头元素 public int Front() { // 如果队列为null的情况 if (isEmpty()) { return -1; } return this.array[this.front]; //返回队头元素 } // 取队尾元素 public int Rear() { // 如果队列为null的情况 if (isEmpty()) { return -1; } // 如果rear为0的情况,我们需要特殊处理 int index = this.rear == 0 ? this.array.length - 1 : this.rear - 1; return this.array[index]; //返回队尾元素 } // 判断队列是否为空 public boolean isEmpty() { // 当front和rear相遇了,则表示队列中没有元素 return this.front == this.rear; } // 判断队列是否满了 public boolean isFull() { // 因为我们会浪费一个空间,所以rear+1等于front就满了 // 但是我们要rear防止越界,所以要进行修正:(this.rear + 1) % this.array.length return (this.rear + 1) % this.array.length == this.front; } }
用队列实现栈 (来源:LeetCode 难度:简单)
题目:请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。
实现 MyStack 类:
void push(int x) 将元素 x 压入栈顶。
int pop() 移除并返回栈顶元素。
int top() 返回栈顶元素。
boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。
解题思路:这道题的解题思路并不难,我们只需要定义两个队列,入栈的时候往不为null的队列入,出栈的时候先将不为空的队列的size()-1个元素全部放到为空的队列中,剩余最后一个元素直接出栈即可,由于取栈顶元素不用删除元素,所以剩余的最后一个元素还要放入另一个队列中,至于更详细的内容可以配合下面代码和注释阅读:
class MyStack { private Queue<Integer> qu1; private Queue<Integer> qu2; public MyStack() { this.qu1 = new LinkedList<>(); this.qu2 = new LinkedList<>(); } public void push(int x) { // 两个队列都为null的情况 if (this.qu1.isEmpty() && this.qu2.isEmpty()) { this.qu1.offer(x); return; } // 哪个队列不为null往哪个队列中入元素 if (!this.qu1.isEmpty()) { this.qu1.offer(x); } else { this.qu2.offer(x); } } public int pop() { // 如果两个队列都为空的情况下,就不能出队操作 if (empty()) { return -1; } // 先将不为空的队列的size-1个元素全部放到为空的队列中 if (!this.qu1.isEmpty()) { while (qu1.size() - 1 != 0) { qu2.offer(qu1.poll()); } return qu1.poll(); //返回最后一个元素 } else { while (qu2.size() - 1 != 0) { qu1.offer(qu2.poll()); } return qu2.poll(); //返回最后一个元素 } } public int top() { // 如果队列为null if (empty()) { return -1; } int ret = 0; //保留剩余最后一个栈顶元素的变量 // 先将不为空的队列的size-1个元素全部放到为空的队列中 if (!this.qu1.isEmpty()) { while (qu1.size() - 1 != 0) { qu2.offer(qu1.poll()); } ret = qu1.peek(); qu2.offer(qu1.poll()); } else { while (qu2.size() - 1 != 0) { qu1.offer(qu2.poll()); } ret = qu2.peek(); qu1.offer(qu2.poll()); //取栈顶元素不能删除掉,还得入另一个队列中去 } return ret; } public boolean empty() { return this.qu1.isEmpty() && this.qu2.isEmpty(); //两个队列都为空,栈才为空 } }
用栈实现队列 (来源:LeetCode 难度:简单)
题目:请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty)。
实现 MyQueue 类:
void push(int x) 将元素 x 推到队列的末尾
int pop() 从队列的开头移除并返回元素
int peek() 返回队列开头的元素
boolean empty() 如果队列为空,返回 true ;否则,返回 false
解题思路:这道题我们可以这样做,定义两个栈,分别是pushStack和popStack,入队统一都入到pushStack栈中,出队头元素都从popStack中出,如果popStack是空的情况,就先将pushStack栈中所有的元素放入popStack中,再出栈顶元素。 至于判断队列是否为空,需要满足 pushStack和popStack都为空,队列才为空!
class MyQueue { private Stack<Integer> pushStack; private Stack<Integer> popStack; public MyQueue() { this.pushStack = new Stack<>(); this.popStack = new Stack<>(); } public void push(int x) { // 入队列都在pushStack中 this.pushStack.push(x); } public int pop() { // 出队列都从popStack出 if (popStack.empty()) { // 把pushStack栈中的元素都放到popStack栈中 while (!pushStack.empty()) { popStack.push(pushStack.pop()); } } if (!popStack.empty()) { return popStack.pop(); } else { return -1; } } public int peek() { // 取队头元素都从popStack中取 if (popStack.empty()) { // 把pushStack栈中的元素都放到popStack栈中 while (!pushStack.empty()) { popStack.push(pushStack.pop()); } } if (!popStack.empty()) { return popStack.peek(); } else { return -1; } } public boolean empty() { return pushStack.empty() && popStack.empty(); } }
最小栈 (来源:LeetCode 难度:中等)
题目:设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
实现 MinStack 类:
MinStack() 初始化堆栈对象。
void push(int val) 将元素val推入堆栈。
void pop() 删除堆栈顶部的元素。
int top() 获取堆栈顶部的元素。
int getMin() 获取堆栈中的最小元素。
解题思路:这道题一看就需要用到两个栈,一个栈入数据,一个栈始终压入最小值,如何操作呢?这里我们可以定义两个栈,分别是stack和minStack,入栈的时候入stack这个栈,如果是第一次入栈,则当前入栈元素为最小值,同时也需要入minStack栈中,如果后续入栈元素小于或等于minStack栈顶元素,则也需要入minStack栈,如果比minStack栈顶元素大,则不需要入minStack栈了,出栈的时候,判断stack栈如果与minStack栈元素相等,则minStack也要出栈顶元素!
public class MinStack { private Stack<Integer> stack; private Stack<Integer> minStack; public MinStack() { this.stack = new Stack<>(); this.minStack = new Stack<>(); } public void push(int val) { // 如果stack为null,表示第一次入栈, // 此时入栈的元素也是此时栈中最小值,也要入minStack栈 if (this.stack.isEmpty()) { this.stack.push(val); this.minStack.push(val); return; } this.stack.push(val); // 如果stack栈顶元素小于等于minStack栈顶元素,则也需要入minStack栈中 if (this.stack.peek() <= this.minStack.peek()) { this.minStack.push(val); } } // 出栈 public void pop() { if (this.stack.isEmpty()) { return; } // 如果出栈元素与minStack元素相等,minStack也要出栈 if (this.stack.pop().equals(this.minStack.peek())) { this.minStack.pop(); } } // 取栈顶元素 public int top() { if (this.stack.isEmpty()) { return -1; } else { return this.stack.peek(); } } // 取栈中最小元素 public int getMin() { if (this.stack.isEmpty()) { return -1; } else { return this.minStack.peek(); } } }
到此这篇关于Java 数据结构之队列与OJ题的文章就介绍到这了,更多相关Java数据结构 队列与OJ题内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!